// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0


#include <QTest>
#include <QtTest/private/qtesthelpers_p.h>

#include <private/qgraphicsitem_p.h>
#include <private/qgraphicsview_p.h>
#include <private/qgraphicsscene_p.h>
#include <private/qinputdevice_p.h>
#include <QRandomGenerator>
#include <QStyleOptionGraphicsItem>
#include <QAbstractTextDocumentLayout>
#include <QBitmap>
#include <QCursor>
#include <QScreen>
#include <QLabel>
#include <QDial>
#include <QGraphicsItem>
#include <QGraphicsScene>
#include <QGraphicsSceneEvent>
#include <QGraphicsView>
#include <QGraphicsWidget>
#include <QGraphicsProxyWidget>
#include <QPainter>
#include <QScrollBar>
#include <QVBoxLayout>
#include <QGraphicsEffect>
#include <QPushButton>
#include <QLineEdit>
#include <QGraphicsLinearLayout>
#include <QTransform>
#include <QSharedPointer>
#include <float.h>
#include <QStyleHints>
#include <QPainterPath>
#include <QSignalSpy>
#include <QTimer>

#include <QtGui/private/qeventpoint_p.h>

#include <QtWidgets/private/qapplication_p.h>

using AbstractGraphicsShapeItemPtr = QSharedPointer<QAbstractGraphicsShapeItem>;
using GraphicsItems = QList<QGraphicsItem *>;
using GraphicsItemsList = QList<QGraphicsItem *>;

Q_DECLARE_METATYPE(AbstractGraphicsShapeItemPtr)
Q_DECLARE_METATYPE(QGraphicsItem::GraphicsItemFlags)
Q_DECLARE_METATYPE(QPainterPath)
Q_DECLARE_METATYPE(QSizeF)
Q_DECLARE_METATYPE(QTransform)

#if defined(Q_OS_WIN)
#include <qt_windows.h>
#define Q_CHECK_PAINTEVENTS \
    if (::SwitchDesktop(::GetThreadDesktop(::GetCurrentThreadId())) == 0) \
        QSKIP("The Graphics View doesn't get the paint events");
#else
#define Q_CHECK_PAINTEVENTS
#endif

#if defined(Q_OS_MAC)
// On mac (cocoa) we always get full update.
// So check that the expected region is contained inside the actual
#define COMPARE_REGIONS(ACTUAL, EXPECTED) QVERIFY((EXPECTED).subtracted(ACTUAL).isEmpty())
#else
#define COMPARE_REGIONS QTRY_COMPARE
#endif

static QGraphicsRectItem staticItem; //QTBUG-7629, we should not crash at exit.

static void sendMousePress(QGraphicsScene *scene, const QPointF &point, Qt::MouseButton button = Qt::LeftButton)
{
    QGraphicsSceneMouseEvent event(QEvent::GraphicsSceneMousePress);
    event.setScenePos(point);
    event.setButton(button);
    event.setButtons(button);
    QCoreApplication::sendEvent(scene, &event);
}

static void sendMouseMove(QGraphicsScene *scene, const QPointF &point,
                          Qt::MouseButton button = Qt::NoButton, Qt::MouseButtons /* buttons */ = {})
{
    QGraphicsSceneMouseEvent event(QEvent::GraphicsSceneMouseMove);
    event.setScenePos(point);
    event.setButton(button);
    event.setButtons(button);
    QCoreApplication::sendEvent(scene, &event);
}

static void sendMouseRelease(QGraphicsScene *scene, const QPointF &point, Qt::MouseButton button = Qt::LeftButton)
{
    QGraphicsSceneMouseEvent event(QEvent::GraphicsSceneMouseRelease);
    event.setScenePos(point);
    event.setButton(button);
    QCoreApplication::sendEvent(scene, &event);
}

static void sendMouseClick(QGraphicsScene *scene, const QPointF &point, Qt::MouseButton button = Qt::LeftButton)
{
    sendMousePress(scene, point, button);
    sendMouseRelease(scene, point, button);
}

static void sendKeyPress(QGraphicsScene *scene, Qt::Key key)
{
    QKeyEvent keyEvent(QEvent::KeyPress, key, Qt::NoModifier);
    QCoreApplication::sendEvent(scene, &keyEvent);
}

static void sendKeyRelease(QGraphicsScene *scene, Qt::Key key)
{
    QKeyEvent keyEvent(QEvent::KeyRelease, key, Qt::NoModifier);
    QCoreApplication::sendEvent(scene, &keyEvent);
}

static void sendKeyClick(QGraphicsScene *scene, Qt::Key key)
{
    sendKeyPress(scene, key);
    sendKeyRelease(scene, key);
}

class EventSpy : public QGraphicsWidget
{
    Q_OBJECT
public:
    EventSpy(QObject *watched, QEvent::Type type)
        : spied(type)
    {
        watched->installEventFilter(this);
    }

    EventSpy(QGraphicsScene *scene, QGraphicsItem *watched, QEvent::Type type)
        : spied(type)
    {
        scene->addItem(this);
        watched->installSceneEventFilter(this);
    }

    int count() const { return _count; }

protected:
    bool eventFilter(QObject *watched, QEvent *event) override
    {
        Q_UNUSED(watched);
        if (event->type() == spied)
            ++_count;
        return false;
    }

    bool sceneEventFilter(QGraphicsItem *watched, QEvent *event) override
    {
        Q_UNUSED(watched);
        if (event->type() == spied)
            ++_count;
        return false;
    }

    int _count = 0;
    const QEvent::Type spied;
};

class EventSpy2 : public QGraphicsWidget
{
    Q_OBJECT
public:
    EventSpy2(QObject *watched)
    {
        watched->installEventFilter(this);
    }

    EventSpy2(QGraphicsScene *scene, QGraphicsItem *watched)
    {
        scene->addItem(this);
        watched->installSceneEventFilter(this);
    }

    QMap<QEvent::Type, int> counts;

protected:
    bool eventFilter(QObject *watched, QEvent *event) override
    {
        Q_UNUSED(watched);
        ++counts[event->type()];
        return false;
    }

    bool sceneEventFilter(QGraphicsItem *watched, QEvent *event) override
    {
        Q_UNUSED(watched);
        ++counts[event->type()];
        return false;
    }
};

class EventTester : public QGraphicsItem
{
public:
    using QGraphicsItem::QGraphicsItem;

    void setGeometry(const QRectF &rect)
    {
        prepareGeometryChange();
        br = rect;
        update();
    }

    QRectF boundingRect() const override
    { return br; }

    void paint(QPainter *painter, const QStyleOptionGraphicsItem *o, QWidget *) override
    {
        hints = painter->renderHints();
        painter->setBrush(brush);
        painter->drawRect(boundingRect());
        lastExposedRect = o->exposedRect;
        ++repaints;
    }

    bool sceneEvent(QEvent *event) override
    {
        events << event->type();
        return QGraphicsItem::sceneEvent(event);
    }

    void reset()
    {
        events.clear();
        hints = QPainter::RenderHints{};
        repaints = 0;
        lastExposedRect = QRectF();
    }

    QList<QEvent::Type> events;
    QPainter::RenderHints hints;
    int repaints = 0;
    QRectF br = QRectF(-10, -10, 20, 20);
    QRectF lastExposedRect;
    QBrush brush;
};

class MyGraphicsView : public QGraphicsView
{
    Q_OBJECT
public:
    QRegion paintedRegion;
    int repaints = 0;

    using QGraphicsView::QGraphicsView;

    void paintEvent(QPaintEvent *e) override
    {
        paintedRegion += e->region();
        ++repaints;
        QGraphicsView::paintEvent(e);
    }
    void reset()
    {
        repaints = 0;
        paintedRegion = QRegion();
    }
};

class tst_QGraphicsItem : public QObject
{
    Q_OBJECT

private slots:
    void cleanup();
    void construction();
    void constructionWithParent();
    void destruction();
    void deleteChildItem();
    void scene();
    void parentItem();
    void setParentItem();
    void children();
    void flags();
    void inputMethodHints();
    void toolTip();
    void visible();
    void isVisibleTo();
    void explicitlyVisible();
    void enabled();
    void explicitlyEnabled();
    void selected();
    void selected2();
    void selected_group();
    void selected_textItem();
    void selected_multi();
    void acceptedMouseButtons();
    void acceptHoverEvents();
    void childAcceptsHoverEvents();
    void hasFocus();
    void pos();
    void scenePos();
    void matrix();
    void sceneTransform();
    void setTransform();
    void zValue();
    void shape();
    void contains();
    void collidesWith_item();
    void collidesWith_path_data();
    void collidesWith_path();
    void collidesWithItemWithClip();
    void isObscuredBy();
    void isObscured();
    void mapFromToParent();
    void mapFromToScene();
    void mapFromToItem();
    void mapRectFromToParent_data();
    void mapRectFromToParent();
    void isAncestorOf();
    void commonAncestorItem();
    void data();
    void type();
    void graphicsitem_cast();
    void hoverEventsGenerateRepaints();
    void boundingRects_data();
    void boundingRects();
    void boundingRects2();
    void sceneBoundingRect();
    void childrenBoundingRect();
    void childrenBoundingRectTransformed();
    void childrenBoundingRect2();
    void childrenBoundingRect3();
    void childrenBoundingRect4();
    void childrenBoundingRect5();
    void group();
    void setGroup();
    void setGroup2();
    void nestedGroups();
    void warpChildrenIntoGroup();
    void removeFromGroup();
    void handlesChildEvents();
    void handlesChildEvents2();
    void handlesChildEvents3();
    void filtersChildEvents();
    void filtersChildEvents2();
    void ensureVisible();
#ifndef QT_NO_CURSOR
    void cursor();
#endif
    //void textControlGetterSetter();
    void defaultItemTest_QGraphicsLineItem();
    void defaultItemTest_QGraphicsPixmapItem();
    void defaultItemTest_QGraphicsTextItem();
    void defaultItemTest_QGraphicsEllipseItem();
    void itemChange();
    void sceneEventFilter();
    void prepareGeometryChange();
    void paint();
    void deleteItemInEventHandlers();
    void itemClipsToShape();
    void itemClipsChildrenToShape();
    void itemClipsChildrenToShape2();
    void itemClipsChildrenToShape3();
    void itemClipsChildrenToShape4();
    void itemClipsChildrenToShape5();
    void itemClipsTextChildToShape();
    void itemClippingDiscovery();
    void itemContainsChildrenInShape();
    void itemContainsChildrenInShape2();
    void ancestorFlags();
    void untransformable();
#ifndef QT_NO_CONTEXTMENU
    void contextMenuEventPropagation();
#endif // QT_NO_CONTEXTMENU
    void itemIsMovable();
    void boundingRegion_data();
    void boundingRegion();
    void itemTransform_parentChild();
    void itemTransform_siblings();
    void itemTransform_unrelated();
    void opacity_data();
    void opacity();
    void opacity2();
    void opacityZeroUpdates();
    void itemStacksBehindParent();
    void nestedClipping();
    void nestedClippingTransforms();
    void sceneTransformCache();
    void tabChangesFocus();
    void tabChangesFocus_data();
    void cacheMode();
    void cacheMode2();
    void updateCachedItemAfterMove();
    void deviceTransform_data();
    void deviceTransform();
    void update();
    void setTransformProperties_data();
    void setTransformProperties();
    void itemUsesExtendedStyleOption();
    void itemSendsGeometryChanges();
    void moveItem();
    void moveLineItem();
    void sorting_data();
    void sorting();
    void itemHasNoContents();
    void hitTestUntransformableItem();
    void hitTestGraphicsEffectItem();
    void focusProxy();
    void subFocus();
    void focusProxyDeletion();
    void negativeZStacksBehindParent();
    void setGraphicsEffect();
    void panel();
    void addPanelToActiveScene();
    void panelWithFocusItems();
    void activate();
    void setActivePanelOnInactiveScene();
    void activationOnShowHide();
    void deactivateInactivePanel();
    void moveWhileDeleting();
    void ensureDirtySceneTransform();
    void focusScope();
    void focusScope2();
    void focusScopeItemChangedWhileScopeDoesntHaveFocus();
    void stackBefore();
    void sceneModality();
    void panelModality();
    void mixedModality();
    void modality_hover();
    void modality_mouseGrabber();
    void modality_clickFocus();
    void modality_keyEvents();
    void itemIsInFront();
    void scenePosChange();
#if QT_CONFIG(shortcut)
    void textItem_shortcuts();
#endif
    void scroll();
    void focusHandling_data();
    void focusHandling();
    void touchEventPropagation_data();
    void touchEventPropagation();
    void touchEventTransformation_data();
    void touchEventTransformation();
    void deviceCoordinateCache_simpleRotations();
    void resolvePaletteForItemChildren();

    // task specific tests below me
    void task141694_textItemEnsureVisible();
    void task128696_textItemEnsureMovable();
    void ensureUpdateOnTextItem();
    void task177918_lineItemUndetected();
    void task240400_clickOnTextItem_data();
    void task240400_clickOnTextItem();
    void task243707_addChildBeforeParent();
    void task197802_childrenVisibility();
    void QTBUG_4233_updateCachedWithSceneRect();
    void QTBUG_5418_textItemSetDefaultColor();
    void QTBUG_6738_missingUpdateWithSetParent();
    void QTBUG_7714_fullUpdateDiscardingOpacityUpdate2();
    void QT_2653_fullUpdateDiscardingOpacityUpdate();
    void QT_2649_focusScope();
    void sortItemsWhileAdding();
    void doNotMarkFullUpdateIfNotInScene();
    void itemDiesDuringDraggingOperation();
    void QTBUG_12112_focusItem();
    void QTBUG_13473_sceneposchange();
    void QTBUG_16374_crashInDestructor();
    void QTBUG_20699_focusScopeCrash();
    void QTBUG_30990_rightClickSelection();
    void QTBUG_21618_untransformable_sceneTransform();

private:
    GraphicsItems paintedItems;
    QPointingDevice *m_touchDevice = QTest::createTouchDevice();
};

void tst_QGraphicsItem::cleanup()
{
    QVERIFY(QApplication::topLevelWidgets().isEmpty());
}

template <class I>
static inline I *createBlackShapeItem()
{
    auto result = new I;
    result->setPen(QPen(Qt::black, 0));
    return result;
}

void tst_QGraphicsItem::construction()
{
    for (int i = 0; i < 7; ++i) {
        QGraphicsItem *item = nullptr;
        switch (i) {
        case 0:
            item = createBlackShapeItem<QGraphicsEllipseItem>();
            QCOMPARE(item->type(), int(QGraphicsEllipseItem::Type));
            QCOMPARE(qgraphicsitem_cast<QGraphicsEllipseItem *>(item), item);
            QCOMPARE(qgraphicsitem_cast<QGraphicsRectItem *>(item), nullptr);
            QCOMPARE(item->flags(), 0);
            break;
        case 1:
            item = createBlackShapeItem<QGraphicsLineItem>();
            QCOMPARE(int(item->type()), int(QGraphicsLineItem::Type));
            QCOMPARE(qgraphicsitem_cast<QGraphicsLineItem *>(item), item);
            QCOMPARE(qgraphicsitem_cast<QGraphicsRectItem *>(item), nullptr);
            QCOMPARE(item->flags(), 0);
            break;
        case 2:
            item = createBlackShapeItem<QGraphicsPathItem>();
            QCOMPARE(int(item->type()), int(QGraphicsPathItem::Type));
            QCOMPARE(qgraphicsitem_cast<QGraphicsPathItem *>(item), item);
            QCOMPARE(qgraphicsitem_cast<QGraphicsRectItem *>(item), nullptr);
            QCOMPARE(item->flags(), 0);
            break;
        case 3:
            item = new QGraphicsPixmapItem;
            QCOMPARE(int(item->type()), int(QGraphicsPixmapItem::Type));
            QCOMPARE(qgraphicsitem_cast<QGraphicsPixmapItem *>(item), item);
            QCOMPARE(qgraphicsitem_cast<QGraphicsRectItem *>(item), nullptr);
            QCOMPARE(item->flags(), 0);
            break;
        case 4:
            item = createBlackShapeItem<QGraphicsPolygonItem>();
            QCOMPARE(int(item->type()), int(QGraphicsPolygonItem::Type));
            QCOMPARE(qgraphicsitem_cast<QGraphicsPolygonItem *>(item), item);
            QCOMPARE(qgraphicsitem_cast<QGraphicsRectItem *>(item), nullptr);
            QCOMPARE(item->flags(), 0);
            break;
        case 5:
            item = createBlackShapeItem<QGraphicsRectItem>();
            QCOMPARE(int(item->type()), int(QGraphicsRectItem::Type));
            QCOMPARE(qgraphicsitem_cast<QGraphicsRectItem *>(item), item);
            QCOMPARE(qgraphicsitem_cast<QGraphicsLineItem *>(item), nullptr);
            QCOMPARE(item->flags(), 0);
            break;
        case 6:
            item = new QGraphicsTextItem;
            QCOMPARE(int(item->type()), int(QGraphicsTextItem::Type));
            QCOMPARE(qgraphicsitem_cast<QGraphicsTextItem *>(item), item);
            QCOMPARE(qgraphicsitem_cast<QGraphicsRectItem *>(item), nullptr);
            // This is the only item that uses an extended style option.
            QCOMPARE(item->flags(), QGraphicsItem::GraphicsItemFlags(QGraphicsItem::ItemUsesExtendedStyleOption));
            break;
        default:
            qFatal("You broke the logic, please fix!");
        }

        QCOMPARE(item->scene(), nullptr);
        QCOMPARE(item->parentItem(), nullptr);
        QVERIFY(item->childItems().isEmpty());
        QVERIFY(item->isVisible());
        QVERIFY(item->isEnabled());
        QVERIFY(!item->isSelected());
        QCOMPARE(item->acceptedMouseButtons(), Qt::MouseButtons(0x1f));
        if (item->type() == QGraphicsTextItem::Type)
            QVERIFY(item->acceptHoverEvents());
        else
            QVERIFY(!item->acceptHoverEvents());
        QVERIFY(!item->hasFocus());
        QCOMPARE(item->pos(), QPointF());
        QCOMPARE(item->transform(), QTransform());
        QCOMPARE(item->sceneTransform(), QTransform());
        QCOMPARE(item->zValue(), qreal(0));
        QCOMPARE(item->sceneBoundingRect(), QRectF());
        QCOMPARE(item->shape(), QPainterPath());
        QVERIFY(!item->contains(QPointF(0, 0)));
        QVERIFY(!item->collidesWithItem(nullptr));
        QVERIFY(item->collidesWithItem(item));
        QVERIFY(!item->collidesWithPath(QPainterPath()));
        QVERIFY(!item->isAncestorOf(nullptr));
        QVERIFY(!item->isAncestorOf(item));
        QCOMPARE(item->data(0), QVariant());
        delete item;
    }
}

class BoundingRectItem : public QGraphicsRectItem
{
public:
    BoundingRectItem(QGraphicsItem *parent = nullptr)
        : QGraphicsRectItem(0, 0, parent ? 200 : 100, parent ? 200 : 100,
                            parent)
    {
        setPen(QPen(Qt::black, 0));
    }

    QRectF boundingRect() const override
    {
        QRectF tmp = QGraphicsRectItem::boundingRect();
        const auto children = childItems();
        for (QGraphicsItem *child : children)
            tmp |= child->boundingRect(); // <- might be pure virtual
        return tmp;
    }
};

void tst_QGraphicsItem::constructionWithParent()
{
    // This test causes a crash if item1 calls item2's pure virtuals before the
    // object has been constructed.
    QGraphicsItem *item0 = new BoundingRectItem;
    QGraphicsItem *item1 = new BoundingRectItem;
    QGraphicsScene scene;
    scene.addItem(item0);
    scene.addItem(item1);
    QGraphicsItem *item2 = new BoundingRectItem(item1);
    QCOMPARE(item1->childItems(), GraphicsItemsList{item2});
    QCOMPARE(item1->boundingRect(), QRectF(0, 0, 200, 200));

    item2->setParentItem(item0);
    QCOMPARE(item0->childItems(), GraphicsItemsList{item2});
    QCOMPARE(item0->boundingRect(), QRectF(0, 0, 200, 200));
}

static int itemDeleted = 0;
class Item : public QGraphicsRectItem
{
public:
    ~Item()
    { ++itemDeleted; }
};

void tst_QGraphicsItem::destruction()
{
    QCOMPARE(itemDeleted, 0);
    {
        QGraphicsItem *parent = new QGraphicsRectItem;
        Item *child = new Item;
        child->setParentItem(parent);
        QCOMPARE(child->parentItem(), parent);
        delete parent;
        QCOMPARE(itemDeleted, 1);
    }
    {
        QGraphicsItem *parent = new QGraphicsRectItem;
        Item *child = new Item;
        child->setParentItem(parent);
        QCOMPARE(parent->childItems().size(), 1);
        delete child;
        QCOMPARE(parent->childItems().size(), 0);
        delete parent;
        QCOMPARE(itemDeleted, 2);
    }
    {
        QGraphicsScene scene;
        QGraphicsItem *parent = new QGraphicsRectItem;
        Item *child = new Item;
        QCOMPARE(child->parentItem(), nullptr);
        child->setParentItem(parent);
        QCOMPARE(child->parentItem(), parent);
        scene.addItem(parent);
        QCOMPARE(child->parentItem(), parent);
        delete parent;
        QCOMPARE(itemDeleted, 3);
    }
    {
        QGraphicsScene scene;
        QGraphicsItem *parent = new QGraphicsRectItem;
        Item *child = new Item;
        child->setParentItem(parent);
        scene.addItem(parent);
        QCOMPARE(child->scene(), &scene);
        QCOMPARE(parent->childItems().size(), 1);
        delete child;
        QCOMPARE(parent->childItems().size(), 0);
        delete parent;
        QCOMPARE(itemDeleted, 4);
    }
    {
        QGraphicsScene scene;
        QGraphicsItem *parent = new QGraphicsRectItem;
        Item *child = new Item;
        child->setParentItem(parent);
        scene.addItem(parent);
        QCOMPARE(child->scene(), &scene);
        scene.removeItem(parent);
        QCOMPARE(child->scene(), nullptr);
        delete parent;
        QCOMPARE(itemDeleted, 5);
    }
    {
        QGraphicsScene scene;
        QGraphicsItem *parent = new QGraphicsRectItem;
        Item *child = new Item;
        child->setParentItem(parent);
        QCOMPARE(child->scene(), nullptr);
        QCOMPARE(parent->scene(), nullptr);
        scene.addItem(parent);
        QCOMPARE(child->scene(), &scene);
        scene.removeItem(child);
        QCOMPARE(child->scene(), nullptr);
        QCOMPARE(parent->scene(), &scene);
        QCOMPARE(child->parentItem(), nullptr);
        QVERIFY(parent->childItems().isEmpty());
        delete parent;
        QCOMPARE(itemDeleted, 5);
        delete child;
        QCOMPARE(itemDeleted, 6);
    }
    {
        QGraphicsScene scene;
        QGraphicsItem *parent = new QGraphicsRectItem;
        Item *child = new Item;
        child->setParentItem(parent);
        scene.addItem(parent);
        scene.removeItem(child);
        scene.removeItem(parent);
        delete child;
        delete parent;
        QCOMPARE(itemDeleted, 7);
    }
    {
        QGraphicsScene scene;
        QGraphicsItem *parent = new QGraphicsRectItem;
        Item *child = new Item;
        child->setParentItem(parent);
        scene.addItem(parent);
        QGraphicsScene scene2;
        scene2.addItem(parent);
        delete parent;
        QCOMPARE(itemDeleted, 8);
    }
    {
        QGraphicsScene scene;
        QGraphicsItem *parent = new QGraphicsRectItem;
        Item *child = new Item;
        child->setParentItem(parent);
        scene.addItem(parent);
        QCOMPARE(child->scene(), &scene);
        QGraphicsScene scene2;
        scene2.addItem(parent);
        QCOMPARE(child->scene(), &scene2);
        scene.addItem(parent);
        QCOMPARE(child->scene(), &scene);
        scene2.addItem(parent);
        QCOMPARE(child->scene(), &scene2);
        delete parent;
        QCOMPARE(itemDeleted, 9);
    }
    {
        QGraphicsScene scene;
        QGraphicsItem *parent = new QGraphicsRectItem;
        Item *child = new Item;
        child->setParentItem(parent);
        scene.addItem(parent);
        QCOMPARE(child->scene(), &scene);
        QGraphicsScene scene2;
        scene2.addItem(child);
        QCOMPARE(child->scene(), &scene2);
        delete parent;
        QCOMPARE(itemDeleted, 9);
        delete child;
        QCOMPARE(itemDeleted, 10);
    }
    {
        QGraphicsScene scene;
        QGraphicsItem *root = new QGraphicsRectItem;
        QGraphicsItem *parent = root;
        QGraphicsItem *middleItem = nullptr;
        for (int i = 0; i < 99; ++i) {
            Item *child = new Item;
            child->setParentItem(parent);
            parent = child;
            if (i == 50)
                middleItem = parent;
        }
        scene.addItem(root);

        QCOMPARE(scene.items().size(), 100);

        QGraphicsScene scene2;
        scene2.addItem(middleItem);

        delete middleItem;
        QCOMPARE(itemDeleted, 59);
    }
    QCOMPARE(itemDeleted, 109);
    {
        QGraphicsScene *scene = new QGraphicsScene;
        QGraphicsRectItem *parent = new QGraphicsRectItem;
        Item *child = new Item;
        child->setParentItem(parent);
        parent->setVisible(false);
        scene->addItem(parent);
        QCOMPARE(child->parentItem(), static_cast<QGraphicsItem*>(parent));
        delete scene;
        QCOMPARE(itemDeleted, 110);
    }
}

void tst_QGraphicsItem::deleteChildItem()
{
    QGraphicsScene scene;
    QGraphicsItem *rect = scene.addRect(QRectF());
    QGraphicsItem *child1 = new QGraphicsRectItem(rect);
    QGraphicsItem *child2 = new QGraphicsRectItem(rect);
    QGraphicsItem *child3 = new QGraphicsRectItem(rect);
    Q_UNUSED(child3);
    delete child1;
    child2->setParentItem(nullptr);
    delete child2;
}

void tst_QGraphicsItem::scene()
{
    QGraphicsRectItem *item = new QGraphicsRectItem;
    QCOMPARE(item->scene(), nullptr);

    QGraphicsScene scene;
    scene.addItem(item);
    QCOMPARE(item->scene(), &scene);

    QGraphicsScene scene2;
    scene2.addItem(item);
    QCOMPARE(item->scene(), &scene2);

    scene2.removeItem(item);
    QCOMPARE(item->scene(), nullptr);

    delete item;
}

void tst_QGraphicsItem::parentItem()
{
    QGraphicsRectItem item;
    QCOMPARE(item.parentItem(), nullptr);

    QGraphicsRectItem *item2 = new QGraphicsRectItem(QRectF(), &item);
    QCOMPARE(item2->parentItem(), &item);
    item2->setParentItem(&item);
    QCOMPARE(item2->parentItem(), &item);
    item2->setParentItem(nullptr);
    QCOMPARE(item2->parentItem(), nullptr);

    delete item2;
}

void tst_QGraphicsItem::setParentItem()
{
    QGraphicsScene scene;
    const QScopedPointer<QGraphicsItem> item(scene.addRect(QRectF(0, 0, 10, 10)));
    QCOMPARE(item->scene(), &scene);

    const QScopedPointer<QGraphicsRectItem> child(new QGraphicsRectItem);
    QCOMPARE(child->scene(), nullptr);

    // This implicitly adds the item to the parent's scene
    child->setParentItem(item.data());
    QCOMPARE(child->scene(), &scene);

    // This just makes it a toplevel
    child->setParentItem(nullptr);
    QCOMPARE(child->scene(), &scene);

    // Add the child back to the parent, then remove the parent from the scene
    child->setParentItem(item.data());
    scene.removeItem(item.data());
    QCOMPARE(child->scene(), nullptr);
}

void tst_QGraphicsItem::children()
{
    QGraphicsRectItem item;
    QVERIFY(item.childItems().isEmpty());

    QGraphicsRectItem *item2 = new QGraphicsRectItem(QRectF(), &item);
    QCOMPARE(item.childItems().size(), 1);
    QCOMPARE(item.childItems().constFirst(), item2);
    QVERIFY(item2->childItems().isEmpty());

    delete item2;
    QVERIFY(item.childItems().isEmpty());
}

void tst_QGraphicsItem::flags()
{
    QGraphicsRectItem *item = new QGraphicsRectItem(QRectF(-10, -10, 20, 20));
    QCOMPARE(item->flags(), 0);

    QGraphicsScene scene;
    QEvent activate(QEvent::WindowActivate);
    QCoreApplication::sendEvent(&scene, &activate);

    scene.addItem(item);

    {
        // Focus
        item->setFlag(QGraphicsItem::ItemIsFocusable, false);
        QVERIFY(!item->hasFocus());
        item->setFocus();
        QVERIFY(!item->hasFocus());

        item->setFlag(QGraphicsItem::ItemIsFocusable, true);
        QVERIFY(!item->hasFocus());
        item->setFocus();
        QVERIFY(item->hasFocus());
        QVERIFY(scene.hasFocus());

        item->setFlag(QGraphicsItem::ItemIsFocusable, false);
        QVERIFY(!item->hasFocus());
        QVERIFY(scene.hasFocus());
    }
    {
        // Selectable
        item->setFlag(QGraphicsItem::ItemIsSelectable, false);
        QVERIFY(!item->isSelected());
        item->setSelected(true);
        QVERIFY(!item->isSelected());

        item->setFlag(QGraphicsItem::ItemIsSelectable, true);
        QVERIFY(!item->isSelected());
        item->setSelected(true);
        QVERIFY(item->isSelected());
        item->setFlag(QGraphicsItem::ItemIsSelectable, false);
        QVERIFY(!item->isSelected());
    }
    {
        // Movable
        item->setFlag(QGraphicsItem::ItemIsMovable, false);
        QGraphicsSceneMouseEvent event(QEvent::GraphicsSceneMousePress);
        event.setScenePos(QPointF(0, 0));
        event.setButton(Qt::LeftButton);
        event.setButtons(Qt::LeftButton);
        QCoreApplication::sendEvent(&scene, &event);
        QCOMPARE(scene.mouseGrabberItem(), nullptr); // mouse grabber is reset

        QGraphicsSceneMouseEvent event2(QEvent::GraphicsSceneMouseMove);
        event2.setScenePos(QPointF(10, 10));
        event2.setButton(Qt::LeftButton);
        event2.setButtons(Qt::LeftButton);
        QCoreApplication::sendEvent(&scene, &event2);
        QCOMPARE(item->pos(), QPointF());

        QGraphicsSceneMouseEvent event3(QEvent::GraphicsSceneMouseRelease);
        event3.setScenePos(QPointF(10, 10));
        event3.setButtons({});
        QCoreApplication::sendEvent(&scene, &event3);
        QCOMPARE(scene.mouseGrabberItem(), nullptr);

        item->setFlag(QGraphicsItem::ItemIsMovable, true);
        QGraphicsSceneMouseEvent event4(QEvent::GraphicsSceneMousePress);
        event4.setScenePos(QPointF(0, 0));
        event4.setButton(Qt::LeftButton);
        event4.setButtons(Qt::LeftButton);
        QCoreApplication::sendEvent(&scene, &event4);
        QCOMPARE(scene.mouseGrabberItem(), item);
        QGraphicsSceneMouseEvent event5(QEvent::GraphicsSceneMouseMove);
        event5.setScenePos(QPointF(10, 10));
        event5.setButton(Qt::LeftButton);
        event5.setButtons(Qt::LeftButton);
        QCoreApplication::sendEvent(&scene, &event5);
        QCOMPARE(item->pos(), QPointF(10, 10));
    }
    {
        const QScopedPointer<QGraphicsItem> clippingParent(new QGraphicsRectItem);
        clippingParent->setFlag(QGraphicsItem::ItemClipsChildrenToShape, true);

        const QScopedPointer<QGraphicsItem> nonClippingParent(new QGraphicsRectItem);
        nonClippingParent->setFlag(QGraphicsItem::ItemClipsChildrenToShape, false);

        QGraphicsItem* child = new QGraphicsRectItem(nonClippingParent.data());
        QVERIFY(!child->isClipped());

        child->setParentItem(clippingParent.data());
        QVERIFY(child->isClipped());

        child->setParentItem(nonClippingParent.data());
        QVERIFY(!child->isClipped());
    }
}

class ImhTester : public QGraphicsItem
{
    QRectF boundingRect() const override { return QRectF(); }
    void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *) override {}
};

void tst_QGraphicsItem::inputMethodHints()
{
    if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation))
        QSKIP("Window activation is not supported");

    ImhTester *item = new ImhTester;
    item->setFlag(QGraphicsItem::ItemAcceptsInputMethod, true);
    item->setFlag(QGraphicsItem::ItemIsFocusable, true);
    QCOMPARE(item->inputMethodHints(), Qt::ImhNone);
    ImhTester *item2 = new ImhTester;
    item2->setFlag(QGraphicsItem::ItemAcceptsInputMethod, true);
    item2->setFlag(QGraphicsItem::ItemIsFocusable, true);
    Qt::InputMethodHints imHints = item2->inputMethodHints();
    imHints |= Qt::ImhHiddenText;
    item2->setInputMethodHints(imHints);
    QGraphicsScene scene;
    scene.addItem(item);
    scene.addItem(item2);
    QGraphicsView view(&scene);
    QApplicationPrivate::setActiveWindow(&view);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));
    QVERIFY(QTest::qWaitForWindowActive(&view));
    item->setFocus();
    QTRY_VERIFY(item->hasFocus());
    QCOMPARE(view.inputMethodHints(), item->inputMethodHints());
    item2->setFocus();
    QTRY_VERIFY(item2->hasFocus());
    QCOMPARE(view.inputMethodHints(), item2->inputMethodHints());
    item->setFlag(QGraphicsItem::ItemAcceptsInputMethod, false);
    item->setFocus();
    QTRY_VERIFY(item->hasFocus());
    //Focus has changed but the new item doesn't accept input method, no hints.
    QCOMPARE(view.inputMethodHints(), 0);
    item2->setFocus();
    QTRY_VERIFY(item2->hasFocus());
    QCOMPARE(view.inputMethodHints(), item2->inputMethodHints());
    imHints = item2->inputMethodHints();
    imHints |= (Qt::ImhNoAutoUppercase | Qt::ImhNoPredictiveText);
    item2->setInputMethodHints(imHints);
    QCOMPARE(view.inputMethodHints(), item2->inputMethodHints());
    QGraphicsProxyWidget *widget = new QGraphicsProxyWidget;
    QLineEdit *edit = new QLineEdit;
    edit->setEchoMode(QLineEdit::Password);
    scene.addItem(widget);
    widget->setFocus();
    QTRY_VERIFY(widget->hasFocus());
    //No widget on the proxy, so no hints
    QCOMPARE(view.inputMethodHints(), 0);
    widget->setWidget(edit);
    //View should match with the line edit
    QCOMPARE(view.inputMethodHints(), edit->inputMethodHints());
}

void tst_QGraphicsItem::toolTip()
{
    if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation))
        QSKIP("Window activation is not supported");

    QString toolTip = "Qt rocks!";

    QGraphicsRectItem *item = new QGraphicsRectItem(QRectF(0, 0, 100, 100));
    item->setPen(QPen(Qt::red, 1));
    item->setBrush(QBrush(Qt::blue));
    QVERIFY(item->toolTip().isEmpty());
    item->setToolTip(toolTip);
    QCOMPARE(item->toolTip(), toolTip);

    QGraphicsScene scene;
    scene.addItem(item);

    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.setFixedSize(200, 200);
    view.show();
    QApplicationPrivate::setActiveWindow(&view);
    QVERIFY(QTest::qWaitForWindowExposed(&view));
    QVERIFY(QTest::qWaitForWindowActive(&view));
    {
        QHelpEvent helpEvent(QEvent::ToolTip, view.viewport()->rect().topLeft(),
                             view.viewport()->mapToGlobal(view.viewport()->rect().topLeft()));
        QCoreApplication::sendEvent(view.viewport(), &helpEvent);
        QTest::qWait(250);

        bool foundView = false;
        bool foundTipLabel = false;
        const auto topLevels = QApplication::topLevelWidgets();
        for (auto widget : topLevels) {
            if (widget == &view)
                foundView = true;
            if (widget->inherits("QTipLabel"))
                foundTipLabel = true;
        }
        QVERIFY(foundView);
        QVERIFY(!foundTipLabel);
    }

    {
        QHelpEvent helpEvent(QEvent::ToolTip, view.viewport()->rect().center(),
                             view.viewport()->mapToGlobal(view.viewport()->rect().center()));
        QCoreApplication::sendEvent(view.viewport(), &helpEvent);
        QTest::qWait(250);

        bool foundView = false;
        bool foundTipLabel = false;
        const auto topLevels = QApplication::topLevelWidgets();
        for (auto widget : topLevels) {
            if (widget == &view)
                foundView = true;
            if (widget->inherits("QTipLabel"))
                foundTipLabel = true;
        }
        QVERIFY(foundView);
        QVERIFY(foundTipLabel);
    }

    {
        QHelpEvent helpEvent(QEvent::ToolTip, view.viewport()->rect().topLeft(),
                             view.viewport()->mapToGlobal(view.viewport()->rect().topLeft()));
        QCoreApplication::sendEvent(view.viewport(), &helpEvent);
        QTest::qWait(1000);

        bool foundView = false;
        bool foundTipLabel = false;
        const auto topLevels = QApplication::topLevelWidgets();
        for (auto widget : topLevels) {
            if (widget == &view)
                foundView = true;
            if (widget->inherits("QTipLabel") && widget->isVisible())
                foundTipLabel = true;
        }
        QVERIFY(foundView);
        QVERIFY(!foundTipLabel);
    }
}

void tst_QGraphicsItem::visible()
{
    QGraphicsItem *item = new QGraphicsRectItem(QRectF(-10, -10, 20, 20));
    item->setFlag(QGraphicsItem::ItemIsMovable);
    QVERIFY(item->isVisible());
    item->setVisible(false);
    QVERIFY(!item->isVisible());
    item->setVisible(true);
    QVERIFY(item->isVisible());

    QGraphicsScene scene;
    QEvent activate(QEvent::WindowActivate);
    QCoreApplication::sendEvent(&scene, &activate);

    scene.addItem(item);
    QVERIFY(item->isVisible());
    QCOMPARE(scene.items(QPointF(0, 0)).value(0, nullptr), item);
    item->setVisible(false);
    QVERIFY(scene.items(QPointF(0, 0)).isEmpty());
    item->setVisible(true);
    QCOMPARE(scene.items(QPointF(0, 0)).value(0, nullptr), item);

    QGraphicsSceneMouseEvent event(QEvent::GraphicsSceneMousePress);
    event.setButton(Qt::LeftButton);
    event.setScenePos(QPointF(0, 0));
    QCoreApplication::sendEvent(&scene, &event);
    QCOMPARE(scene.mouseGrabberItem(), item);
    item->setVisible(false);
    QCOMPARE(scene.mouseGrabberItem(), nullptr);
    item->setVisible(true);
    QCOMPARE(scene.mouseGrabberItem(), nullptr);

    item->setFlag(QGraphicsItem::ItemIsFocusable);
    item->setFocus();
    QVERIFY(item->hasFocus());
    item->setVisible(false);
    QVERIFY(!item->hasFocus());
    item->setVisible(true);
    QVERIFY(!item->hasFocus());
}

void tst_QGraphicsItem::isVisibleTo()
{
    QGraphicsScene scene;
    QGraphicsItem *parent = scene.addRect(QRectF(0, 0, 100, 100));
    QGraphicsItem *child = scene.addRect(QRectF(25, 25, 50, 50));
    QGraphicsItem *grandChild = scene.addRect(QRectF(50, 50, 50, 50));
    QGraphicsItem *stranger = scene.addRect(100, 100, 100, 100);

    child->setParentItem(parent);
    grandChild->setParentItem(child);

    QVERIFY(grandChild->isVisible());
    QVERIFY(grandChild->isVisibleTo(grandChild));
    QVERIFY(grandChild->isVisibleTo(child));
    QVERIFY(grandChild->isVisibleTo(parent));
    QVERIFY(grandChild->isVisibleTo(nullptr));
    QVERIFY(child->isVisible());
    QVERIFY(child->isVisibleTo(child));
    QVERIFY(child->isVisibleTo(parent));
    QVERIFY(child->isVisibleTo(nullptr));
    QVERIFY(parent->isVisible());
    QVERIFY(parent->isVisibleTo(parent));
    QVERIFY(parent->isVisibleTo(nullptr));
    QVERIFY(!parent->isVisibleTo(child));
    QVERIFY(!child->isVisibleTo(grandChild));
    QVERIFY(!grandChild->isVisibleTo(stranger));
    QVERIFY(!child->isVisibleTo(stranger));
    QVERIFY(!parent->isVisibleTo(stranger));
    QVERIFY(!stranger->isVisibleTo(grandChild));
    QVERIFY(!stranger->isVisibleTo(child));
    QVERIFY(!stranger->isVisibleTo(parent));

    // Case 1: only parent is explicitly hidden
    parent->hide();

    QVERIFY(!grandChild->isVisible());
    QVERIFY(grandChild->isVisibleTo(grandChild));
    QVERIFY(grandChild->isVisibleTo(child));
    QVERIFY(grandChild->isVisibleTo(parent));
    QVERIFY(!grandChild->isVisibleTo(nullptr));
    QVERIFY(!child->isVisible());
    QVERIFY(child->isVisibleTo(child));
    QVERIFY(child->isVisibleTo(parent));
    QVERIFY(!child->isVisibleTo(nullptr));
    QVERIFY(!parent->isVisible());
    QVERIFY(!parent->isVisibleTo(parent));
    QVERIFY(!parent->isVisibleTo(nullptr));
    QVERIFY(!parent->isVisibleTo(child));
    QVERIFY(!child->isVisibleTo(grandChild));
    QVERIFY(!grandChild->isVisibleTo(stranger));
    QVERIFY(!child->isVisibleTo(stranger));
    QVERIFY(!parent->isVisibleTo(stranger));
    QVERIFY(!stranger->isVisibleTo(grandChild));
    QVERIFY(!stranger->isVisibleTo(child));
    QVERIFY(!stranger->isVisibleTo(parent));

    // Case 2: only child is hidden
    parent->show();
    child->hide();

    QVERIFY(!grandChild->isVisible());
    QVERIFY(grandChild->isVisibleTo(grandChild));
    QVERIFY(grandChild->isVisibleTo(child));
    QVERIFY(!grandChild->isVisibleTo(parent));
    QVERIFY(!grandChild->isVisibleTo(nullptr));
    QVERIFY(!child->isVisible());
    QVERIFY(!child->isVisibleTo(child));
    QVERIFY(!child->isVisibleTo(parent));
    QVERIFY(!child->isVisibleTo(nullptr));
    QVERIFY(parent->isVisible());
    QVERIFY(parent->isVisibleTo(parent));
    QVERIFY(parent->isVisibleTo(nullptr));
    QVERIFY(!parent->isVisibleTo(child));
    QVERIFY(!child->isVisibleTo(grandChild));
    QVERIFY(!grandChild->isVisibleTo(stranger));
    QVERIFY(!child->isVisibleTo(stranger));
    QVERIFY(!parent->isVisibleTo(stranger));
    QVERIFY(!stranger->isVisibleTo(grandChild));
    QVERIFY(!stranger->isVisibleTo(child));
    QVERIFY(!stranger->isVisibleTo(parent));

    // Case 3: only grand child is hidden
    child->show();
    grandChild->hide();

    QVERIFY(!grandChild->isVisible());
    QVERIFY(!grandChild->isVisibleTo(grandChild));
    QVERIFY(!grandChild->isVisibleTo(child));
    QVERIFY(!grandChild->isVisibleTo(parent));
    QVERIFY(!grandChild->isVisibleTo(nullptr));
    QVERIFY(child->isVisible());
    QVERIFY(child->isVisibleTo(child));
    QVERIFY(child->isVisibleTo(parent));
    QVERIFY(child->isVisibleTo(nullptr));
    QVERIFY(parent->isVisible());
    QVERIFY(parent->isVisibleTo(parent));
    QVERIFY(parent->isVisibleTo(nullptr));
    QVERIFY(!parent->isVisibleTo(child));
    QVERIFY(!child->isVisibleTo(grandChild));
    QVERIFY(!grandChild->isVisibleTo(stranger));
    QVERIFY(!child->isVisibleTo(stranger));
    QVERIFY(!parent->isVisibleTo(stranger));
    QVERIFY(!stranger->isVisibleTo(grandChild));
    QVERIFY(!stranger->isVisibleTo(child));
    QVERIFY(!stranger->isVisibleTo(parent));
}

void tst_QGraphicsItem::explicitlyVisible()
{
    QGraphicsScene scene;
    QGraphicsItem *parent = scene.addRect(QRectF(0, 0, 100, 100));
    QGraphicsItem *child = scene.addRect(QRectF(25, 25, 50, 50));
    child->setParentItem(parent);

    QVERIFY(parent->isVisible());
    QVERIFY(child->isVisible());

    parent->hide();

    QVERIFY(!parent->isVisible());
    QVERIFY(!child->isVisible());

    parent->show();
    child->hide();

    QVERIFY(parent->isVisible());
    QVERIFY(!child->isVisible());

    parent->hide();

    QVERIFY(!parent->isVisible());
    QVERIFY(!child->isVisible());

    parent->show();

    QVERIFY(parent->isVisible());
    QVERIFY(!child->isVisible()); // <- explicitly hidden

    child->show();

    QVERIFY(child->isVisible());

    parent->hide();

    QVERIFY(!parent->isVisible());
    QVERIFY(!child->isVisible()); // <- explicit show doesn't work

    parent->show();

    QVERIFY(parent->isVisible());
    QVERIFY(child->isVisible()); // <- no longer explicitly hidden

    // ------------------- Reparenting ------------------------------

    QGraphicsItem *parent2 = scene.addRect(-50, -50, 200, 200);
    QVERIFY(parent2->isVisible());

    // Reparent implicitly hidden item to a visible parent.
    parent->hide();
    QVERIFY(!parent->isVisible());
    QVERIFY(!child->isVisible());
    child->setParentItem(parent2);
    QVERIFY(parent2->isVisible());
    QVERIFY(child->isVisible());

    // Reparent implicitly hidden item to a hidden parent.
    child->setParentItem(parent);
    parent2->hide();
    child->setParentItem(parent2);
    QVERIFY(!parent2->isVisible());
    QVERIFY(!child->isVisible());

    // Reparent explicitly hidden item to a visible parent.
    child->hide();
    parent->show();
    child->setParentItem(parent);
    QVERIFY(parent->isVisible());
    QVERIFY(!child->isVisible());

    // Reparent explicitly hidden item to a hidden parent.
    child->setParentItem(parent2);
    QVERIFY(!parent2->isVisible());
    QVERIFY(!child->isVisible());

    // Reparent explicitly hidden item to a visible parent.
    parent->show();
    child->setParentItem(parent);
    QVERIFY(parent->isVisible());
    QVERIFY(!child->isVisible());

    // Reparent visible item to a hidden parent.
    child->show();
    parent2->hide();
    child->setParentItem(parent2);
    QVERIFY(!parent2->isVisible());
    QVERIFY(!child->isVisible());
    parent2->show();
    QVERIFY(parent2->isVisible());
    QVERIFY(child->isVisible());

    // Reparent implicitly hidden child to root.
    parent2->hide();
    QVERIFY(!child->isVisible());
    child->setParentItem(nullptr);
    QVERIFY(child->isVisible());

    // Reparent an explicitly hidden child to root.
    child->hide();
    child->setParentItem(parent2);
    parent2->show();
    QVERIFY(!child->isVisible());
    child->setParentItem(nullptr);
    QVERIFY(!child->isVisible());
}

void tst_QGraphicsItem::enabled()
{
    QGraphicsRectItem *item = new QGraphicsRectItem(QRectF(-10, -10, 20, 20));
    item->setFlag(QGraphicsItem::ItemIsMovable);
    QVERIFY(item->isEnabled());
    item->setEnabled(false);
    QVERIFY(!item->isEnabled());
    item->setEnabled(true);
    QVERIFY(item->isEnabled());
    item->setEnabled(false);
    item->setFlag(QGraphicsItem::ItemIsFocusable);
    QGraphicsScene scene;
    QEvent activate(QEvent::WindowActivate);
    QCoreApplication::sendEvent(&scene, &activate);

    scene.addItem(item);
    item->setFocus();
    QVERIFY(!item->hasFocus());
    item->setEnabled(true);
    item->setFocus();
    QVERIFY(item->hasFocus());
    item->setEnabled(false);
    QVERIFY(!item->hasFocus());

    QGraphicsSceneMouseEvent event(QEvent::GraphicsSceneMousePress);
    event.setButton(Qt::LeftButton);
    event.setScenePos(QPointF(0, 0));
    QCoreApplication::sendEvent(&scene, &event);
    QCOMPARE(scene.mouseGrabberItem(), nullptr);
    item->setEnabled(true);
    QCoreApplication::sendEvent(&scene, &event);
    QCOMPARE(scene.mouseGrabberItem(), item);
    item->setEnabled(false);
    QCOMPARE(scene.mouseGrabberItem(), nullptr);
}

void tst_QGraphicsItem::explicitlyEnabled()
{
    QGraphicsScene scene;
    QGraphicsItem *parent = scene.addRect(QRectF(0, 0, 100, 100));
    QGraphicsItem *child = scene.addRect(QRectF(25, 25, 50, 50));
    child->setParentItem(parent);

    QVERIFY(parent->isEnabled());
    QVERIFY(child->isEnabled());

    parent->setEnabled(false);

    QVERIFY(!parent->isEnabled());
    QVERIFY(!child->isEnabled());

    parent->setEnabled(true);
    child->setEnabled(false);

    QVERIFY(parent->isEnabled());
    QVERIFY(!child->isEnabled());

    parent->setEnabled(false);

    QVERIFY(!parent->isEnabled());
    QVERIFY(!child->isEnabled());

    parent->setEnabled(true);

    QVERIFY(parent->isEnabled());
    QVERIFY(!child->isEnabled()); // <- explicitly disabled

    child->setEnabled(true);

    QVERIFY(child->isEnabled());

    parent->setEnabled(false);

    QVERIFY(!parent->isEnabled());
    QVERIFY(!child->isEnabled()); // <- explicit enabled doesn't work

    parent->setEnabled(true);

    QVERIFY(parent->isEnabled());
    QVERIFY(child->isEnabled()); // <- no longer explicitly disabled

    // ------------------- Reparenting ------------------------------

    QGraphicsItem *parent2 = scene.addRect(-50, -50, 200, 200);
    QVERIFY(parent2->isEnabled());

    // Reparent implicitly hidden item to a enabled parent.
    parent->setEnabled(false);
    QVERIFY(!parent->isEnabled());
    QVERIFY(!child->isEnabled());
    child->setParentItem(parent2);
    QVERIFY(parent2->isEnabled());
    QVERIFY(child->isEnabled());

    // Reparent implicitly hidden item to a hidden parent.
    child->setParentItem(parent);
    parent2->setEnabled(false);
    child->setParentItem(parent2);
    QVERIFY(!parent2->isEnabled());
    QVERIFY(!child->isEnabled());

    // Reparent explicitly hidden item to a enabled parent.
    child->setEnabled(false);
    parent->setEnabled(true);
    child->setParentItem(parent);
    QVERIFY(parent->isEnabled());
    QVERIFY(!child->isEnabled());

    // Reparent explicitly hidden item to a hidden parent.
    child->setParentItem(parent2);
    QVERIFY(!parent2->isEnabled());
    QVERIFY(!child->isEnabled());

    // Reparent explicitly hidden item to a enabled parent.
    parent->setEnabled(true);
    child->setParentItem(parent);
    QVERIFY(parent->isEnabled());
    QVERIFY(!child->isEnabled());

    // Reparent enabled item to a hidden parent.
    child->setEnabled(true);
    parent2->setEnabled(false);
    child->setParentItem(parent2);
    QVERIFY(!parent2->isEnabled());
    QVERIFY(!child->isEnabled());
    parent2->setEnabled(true);
    QVERIFY(parent2->isEnabled());
    QVERIFY(child->isEnabled());

    // Reparent implicitly hidden child to root.
    parent2->setEnabled(false);
    QVERIFY(!child->isEnabled());
    child->setParentItem(nullptr);
    QVERIFY(child->isEnabled());

    // Reparent an explicitly hidden child to root.
    child->setEnabled(false);
    child->setParentItem(parent2);
    parent2->setEnabled(true);
    QVERIFY(!child->isEnabled());
    child->setParentItem(nullptr);
    QVERIFY(!child->isEnabled());
}

class SelectChangeItem : public QGraphicsRectItem
{
public:
    SelectChangeItem() : QGraphicsRectItem(-50, -50, 100, 100) { setBrush(Qt::blue); }
    QList<bool> values;

protected:
    QVariant itemChange(GraphicsItemChange change, const QVariant &value) override
    {
        if (change == ItemSelectedChange)
            values << value.toBool();
        return QGraphicsRectItem::itemChange(change, value);
    }
};

void tst_QGraphicsItem::selected()
{
    SelectChangeItem *item = new SelectChangeItem;
    item->setFlag(QGraphicsItem::ItemIsSelectable);
    QVERIFY(!item->isSelected());
    QVERIFY(item->values.isEmpty());
    item->setSelected(true);
    QCOMPARE(item->values.size(), 1);
    QCOMPARE(item->values.constLast(), true);
    QVERIFY(item->isSelected());
    item->setSelected(false);
    QCOMPARE(item->values.size(), 2);
    QCOMPARE(item->values.constLast(), false);
    QVERIFY(!item->isSelected());
    item->setSelected(true);
    QCOMPARE(item->values.size(), 3);
    item->setEnabled(false);
    QCOMPARE(item->values.size(), 4);
    QCOMPARE(item->values.constLast(), false);
    QVERIFY(!item->isSelected());
    item->setEnabled(true);
    QCOMPARE(item->values.size(), 4);
    item->setSelected(true);
    QCOMPARE(item->values.size(), 5);
    QCOMPARE(item->values.constLast(), true);
    QVERIFY(item->isSelected());
    item->setVisible(false);
    QCOMPARE(item->values.size(), 6);
    QCOMPARE(item->values.constLast(), false);
    QVERIFY(!item->isSelected());
    item->setVisible(true);
    QCOMPARE(item->values.size(), 6);
    item->setSelected(true);
    QCOMPARE(item->values.size(), 7);
    QCOMPARE(item->values.constLast(), true);
    QVERIFY(item->isSelected());

    QGraphicsScene scene(-100, -100, 200, 200);
    scene.addItem(item);
    QCOMPARE(scene.selectedItems(), GraphicsItemsList{item});
    item->setSelected(false);
    QVERIFY(scene.selectedItems().isEmpty());
    item->setSelected(true);
    QCOMPARE(scene.selectedItems(), GraphicsItemsList{item});
    item->setSelected(false);
    QVERIFY(scene.selectedItems().isEmpty());

    // Interactive selection
    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.setFixedSize(250, 250);
    view.show();

    QVERIFY(QTest::qWaitForWindowExposed(&view));
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();

    scene.clearSelection();
    QCOMPARE(item->values.size(), 10);
    QCOMPARE(item->values.constLast(), false);
    QVERIFY(!item->isSelected());

    // Click inside and check that it's selected
    QTest::mouseMove(view.viewport());
    QTest::mouseClick(view.viewport(), Qt::LeftButton, {}, view.mapFromScene(item->scenePos()));
    QCOMPARE(item->values.size(), 11);
    QCOMPARE(item->values.constLast(), true);
    QVERIFY(item->isSelected());

    // Click outside and check that it's not selected
    QTest::mouseClick(view.viewport(), Qt::LeftButton, {}, view.mapFromScene(item->scenePos() + QPointF(item->boundingRect().width(), item->boundingRect().height())));
    QCOMPARE(item->values.size(), 12);
    QCOMPARE(item->values.constLast(), false);
    QVERIFY(!item->isSelected());

    SelectChangeItem *item2 = new SelectChangeItem;
    item2->setFlag(QGraphicsItem::ItemIsSelectable);
    item2->setPos(100, 0);
    scene.addItem(item2);

    // Click inside and check that it's selected
    QTest::mouseClick(view.viewport(), Qt::LeftButton, {}, view.mapFromScene(item->scenePos()));
    QCOMPARE(item->values.size(), 13);
    QCOMPARE(item->values.constLast(), true);
    QVERIFY(item->isSelected());

    // Click inside item2 and check that it's selected, and item is not
    QTest::mouseClick(view.viewport(), Qt::LeftButton, {}, view.mapFromScene(item2->scenePos()));
    QCOMPARE(item->values.size(), 14);
    QCOMPARE(item->values.constLast(), false);
    QVERIFY(!item->isSelected());
    QCOMPARE(item2->values.size(), 1);
    QCOMPARE(item2->values.constLast(), true);
    QVERIFY(item2->isSelected());
}

void tst_QGraphicsItem::selected2()
{
    // Selecting an item, then moving another previously caused a crash.
    QGraphicsScene scene;
    QGraphicsItem *line1 = scene.addRect(QRectF(0, 0, 100, 100));
    line1->setPos(-105, 0);
    line1->setFlag(QGraphicsItem::ItemIsSelectable);

    QGraphicsItem *line2 = scene.addRect(QRectF(0, 0, 100, 100));
    line2->setFlag(QGraphicsItem::ItemIsMovable);

    line1->setSelected(true);

    {
        QGraphicsSceneMouseEvent mousePress(QEvent::GraphicsSceneMousePress);
        mousePress.setScenePos(QPointF(50, 50));
        mousePress.setButton(Qt::LeftButton);
        QCoreApplication::sendEvent(&scene, &mousePress);
        QVERIFY(mousePress.isAccepted());
    }
    {
        QGraphicsSceneMouseEvent mouseMove(QEvent::GraphicsSceneMouseMove);
        mouseMove.setScenePos(QPointF(60, 60));
        mouseMove.setButton(Qt::LeftButton);
        mouseMove.setButtons(Qt::LeftButton);
        QCoreApplication::sendEvent(&scene, &mouseMove);
        QVERIFY(mouseMove.isAccepted());
    }
}

void tst_QGraphicsItem::selected_group()
{
    QGraphicsScene scene;
    QGraphicsItem *item1 = scene.addRect(QRectF());
    QGraphicsItem *item2 = scene.addRect(QRectF());
    item1->setFlag(QGraphicsItem::ItemIsSelectable);
    item2->setFlag(QGraphicsItem::ItemIsSelectable);
    scene.addRect(QRectF())->setParentItem(item1);
    QGraphicsItem *leaf = scene.addRect(QRectF());
    leaf->setFlag(QGraphicsItem::ItemIsSelectable);
    leaf->setParentItem(item2);

    QGraphicsItemGroup *group = scene.createItemGroup(GraphicsItemsList{item1, item2});
    QCOMPARE(group->scene(), &scene);
    group->setFlag(QGraphicsItem::ItemIsSelectable);
    const auto items = scene.items();
    for (QGraphicsItem *item : items) {
        if (item == group)
            QVERIFY(!item->group());
        else
            QCOMPARE(item->group(), group);
    }

    QVERIFY(group->handlesChildEvents());
    QVERIFY(!group->isSelected());
    group->setSelected(false);
    QVERIFY(!group->isSelected());
    group->setSelected(true);
    QVERIFY(group->isSelected());

    const auto itemIsSelected = [](const QGraphicsItem *item) { return item->isSelected(); };
    QVERIFY(std::all_of(items.cbegin(), items.cend(), itemIsSelected));
    group->setSelected(false);
    QVERIFY(!group->isSelected());
    QVERIFY(std::none_of(items.cbegin(), items.cend(), itemIsSelected));
    leaf->setSelected(true);
    QVERIFY(std::all_of(items.cbegin(), items.cend(), itemIsSelected));
    leaf->setSelected(false);
    QVERIFY(std::none_of(items.cbegin(), items.cend(), itemIsSelected));

    leaf->setSelected(true);
    QGraphicsScene scene2;
    scene2.addItem(item1);
    QVERIFY(!item1->isSelected());
    QVERIFY(item2->isSelected());
}

void tst_QGraphicsItem::selected_textItem()
{
    QGraphicsScene scene;
    QGraphicsTextItem *text = scene.addText(QLatin1String("Text"));
    text->setFlag(QGraphicsItem::ItemIsSelectable);

    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));

    QTRY_VERIFY(!text->isSelected());
    QTest::mouseClick(view.viewport(), Qt::LeftButton, {},
                      view.mapFromScene(text->mapToScene(0, 0)));
    QTRY_VERIFY(text->isSelected());

    text->setSelected(false);
    text->setTextInteractionFlags(Qt::TextEditorInteraction);

    QTest::mouseClick(view.viewport(), Qt::LeftButton, {},
                      view.mapFromScene(text->mapToScene(0, 0)));
    QTRY_VERIFY(text->isSelected());
}

void tst_QGraphicsItem::selected_multi()
{
    // Test multiselection behavior
    QGraphicsScene scene;

    // Create two disjoint items
    QGraphicsItem *item1 = scene.addRect(QRectF(-10, -10, 20, 20));
    QGraphicsItem *item2 = scene.addRect(QRectF(-10, -10, 20, 20));
    item1->setPos(-15, 0);
    item2->setPos(15, 20);

    // Make both items selectable
    item1->setFlag(QGraphicsItem::ItemIsSelectable);
    item2->setFlag(QGraphicsItem::ItemIsSelectable);

    // Create and show a view
    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.show();
    view.fitInView(scene.sceneRect());
    QVERIFY(QTest::qWaitForWindowExposed(&view));
    if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation))
        QVERIFY(QTest::qWaitForWindowActive(&view));

    QVERIFY(!item1->isSelected());
    QVERIFY(!item2->isSelected());

    // Click on item1
    QTest::mouseClick(view.viewport(), Qt::LeftButton, {}, view.mapFromScene(item1->scenePos()));
    QVERIFY(item1->isSelected());
    QVERIFY(!item2->isSelected());

    // Click on item2
    QTest::mouseClick(view.viewport(), Qt::LeftButton, {}, view.mapFromScene(item2->scenePos()));
    QVERIFY(item2->isSelected());
    QVERIFY(!item1->isSelected());

    // Ctrl-click on item1
    QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::ControlModifier, view.mapFromScene(item1->scenePos()));
    QVERIFY(item2->isSelected());
    QVERIFY(item1->isSelected());

    // Ctrl-click on item1 again
    QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::ControlModifier, view.mapFromScene(item1->scenePos()));
    QVERIFY(item2->isSelected());
    QVERIFY(!item1->isSelected());

    // Ctrl-click on item2
    QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::ControlModifier, view.mapFromScene(item2->scenePos()));
    QVERIFY(!item2->isSelected());
    QVERIFY(!item1->isSelected());

    // Click on item1
    QTest::mouseClick(view.viewport(), Qt::LeftButton, {}, view.mapFromScene(item1->scenePos()));
    QVERIFY(item1->isSelected());
    QVERIFY(!item2->isSelected());

    // Click on scene
    QTest::mouseClick(view.viewport(), Qt::LeftButton, {}, view.mapFromScene(0, 0));
    QVERIFY(!item1->isSelected());
    QVERIFY(!item2->isSelected());

    // Click on item1
    QTest::mouseClick(view.viewport(), Qt::LeftButton, {}, view.mapFromScene(item1->scenePos()));
    QVERIFY(item1->isSelected());
    QVERIFY(!item2->isSelected());

    // Ctrl-click on scene
    QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::ControlModifier, view.mapFromScene(0, 0));
    QVERIFY(item1->isSelected());
    QVERIFY(!item2->isSelected());

    // Click on scene
    QTest::mouseClick(view.viewport(), Qt::LeftButton, {}, view.mapFromScene(0, 0));
    QVERIFY(!item1->isSelected());
    QVERIFY(!item2->isSelected());

    // Click on item1
    QTest::mouseClick(view.viewport(), Qt::LeftButton, {}, view.mapFromScene(item1->scenePos()));
    QVERIFY(item1->isSelected());
    QVERIFY(!item2->isSelected());

    // Press on item2
    QTest::mousePress(view.viewport(), Qt::LeftButton, {}, view.mapFromScene(item2->scenePos()));
    QVERIFY(!item1->isSelected());
    QVERIFY(item2->isSelected());

    // Release on item2
    QTest::mouseRelease(view.viewport(), Qt::LeftButton, {}, view.mapFromScene(item2->scenePos()));
    QVERIFY(!item1->isSelected());
    QVERIFY(item2->isSelected());

    // Click on item1
    QTest::mouseClick(view.viewport(), Qt::LeftButton, {}, view.mapFromScene(item1->scenePos()));
    QVERIFY(item1->isSelected());
    QVERIFY(!item2->isSelected());

    // Ctrl-click on item1
    QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::ControlModifier, view.mapFromScene(item1->scenePos()));
    QVERIFY(!item1->isSelected());
    QVERIFY(!item2->isSelected());

    // Ctrl-press on item1
    QTest::mousePress(view.viewport(), Qt::LeftButton, Qt::ControlModifier, view.mapFromScene(item1->scenePos()));
    QVERIFY(!item1->isSelected());
    QVERIFY(!item2->isSelected());

    {
        // Ctrl-move on item1
        const QPoint item1Point = view.mapFromScene(item1->scenePos()) + QPoint(1, 0);
        QMouseEvent event(QEvent::MouseMove, item1Point, view.viewport()->mapToGlobal(item1Point), Qt::LeftButton, Qt::LeftButton, Qt::ControlModifier);
        QCoreApplication::sendEvent(view.viewport(), &event);
        QVERIFY(!item1->isSelected());
        QVERIFY(!item2->isSelected());
    }

    // Release on item1
    QTest::mouseRelease(view.viewport(), Qt::LeftButton, Qt::ControlModifier, view.mapFromScene(item1->scenePos()));
    QVERIFY(item1->isSelected());
    QVERIFY(!item2->isSelected());

    item1->setFlag(QGraphicsItem::ItemIsMovable);
    item1->setSelected(false);

    // Ctrl-press on item1
    QTest::mousePress(view.viewport(), Qt::LeftButton, Qt::ControlModifier, view.mapFromScene(item1->scenePos()));
    QVERIFY(!item1->isSelected());
    QVERIFY(!item2->isSelected());

    {
        // Ctrl-move on item1
        const QPoint item1Point = view.mapFromScene(item1->scenePos()) + QPoint(1, 0);
        QMouseEvent event(QEvent::MouseMove, item1Point, view.viewport()->mapToGlobal(item1Point), Qt::LeftButton, Qt::LeftButton, Qt::ControlModifier);
        QCoreApplication::sendEvent(view.viewport(), &event);
        QVERIFY(item1->isSelected());
        QVERIFY(!item2->isSelected());
    }

    // Release on item1
    QTest::mouseRelease(view.viewport(), Qt::LeftButton, Qt::ControlModifier, view.mapFromScene(item1->scenePos()));
    QVERIFY(item1->isSelected());
    QVERIFY(!item2->isSelected());
}

void tst_QGraphicsItem::acceptedMouseButtons()
{
    QGraphicsScene scene;
    QGraphicsRectItem *item1 = scene.addRect(QRectF(-10, -10, 20, 20));
    QGraphicsRectItem *item2 = scene.addRect(QRectF(-10, -10, 20, 20));
    item2->setZValue(1);

    item1->setFlag(QGraphicsItem::ItemIsMovable);
    item2->setFlag(QGraphicsItem::ItemIsMovable);

    QCOMPARE(item1->acceptedMouseButtons(), Qt::MouseButtons(0x1f));
    QCOMPARE(item2->acceptedMouseButtons(), Qt::MouseButtons(0x1f));

    QGraphicsSceneMouseEvent event(QEvent::GraphicsSceneMousePress);
    event.setButton(Qt::LeftButton);
    event.setScenePos(QPointF(0, 0));
    QCoreApplication::sendEvent(&scene, &event);
    QCOMPARE(scene.mouseGrabberItem(), item2);
    item2->setAcceptedMouseButtons({ });
    QCOMPARE(scene.mouseGrabberItem(), nullptr);
    QCoreApplication::sendEvent(&scene, &event);
    QCOMPARE(scene.mouseGrabberItem(), item1);
}

class HoverItem : public QGraphicsRectItem
{
public:
    HoverItem(const QRectF &rect) : QGraphicsRectItem(rect) { }

    int hoverInCount = 0;
    int hoverMoveCount = 0;
    int hoverOutCount = 0;
protected:
    void hoverEnterEvent(QGraphicsSceneHoverEvent *) override
    { ++hoverInCount; }

    void hoverMoveEvent(QGraphicsSceneHoverEvent *) override
    { ++hoverMoveCount; }

    void hoverLeaveEvent(QGraphicsSceneHoverEvent *) override
    { ++hoverOutCount; }
};

void tst_QGraphicsItem::acceptHoverEvents()
{
    QGraphicsScene scene;
    HoverItem *item1 = new HoverItem(QRectF(-10, -10, 20, 20));
    HoverItem *item2 = new HoverItem(QRectF(-5, -5, 10, 10));
    scene.addItem(item1);
    scene.addItem(item2);
    item2->setZValue(1);

    QVERIFY(!item1->acceptHoverEvents());
    QVERIFY(!item2->acceptHoverEvents());
    item1->setAcceptHoverEvents(true);
    item2->setAcceptHoverEvents(true);

    QGraphicsSceneMouseEvent event(QEvent::GraphicsSceneMouseMove);
    event.setScenePos(QPointF(-100, -100));
    QCoreApplication::sendEvent(&scene, &event);
    event.setScenePos(QPointF(-2.5, -2.5));
    QCoreApplication::sendEvent(&scene, &event);

    QCOMPARE(item1->hoverInCount, 0);
    QCOMPARE(item2->hoverInCount, 1);

    item1->setAcceptHoverEvents(false);
    item2->setAcceptHoverEvents(false);

    event.setScenePos(QPointF(-100, -100));
    QCoreApplication::sendEvent(&scene, &event);
    event.setScenePos(QPointF(-2.5, -2.5));
    QCoreApplication::sendEvent(&scene, &event);

    QCOMPARE(item1->hoverInCount, 0);
    QCOMPARE(item2->hoverInCount, 1);

    item1->setAcceptHoverEvents(true);
    item2->setAcceptHoverEvents(false);

    event.setScenePos(QPointF(-100, -100));
    QCoreApplication::sendEvent(&scene, &event);
    event.setScenePos(QPointF(-2.5, -2.5));
    QCoreApplication::sendEvent(&scene, &event);

    QCOMPARE(item1->hoverInCount, 1);
    QCOMPARE(item2->hoverInCount, 1);
}

void tst_QGraphicsItem::childAcceptsHoverEvents()
{
    QGraphicsScene scene;
    HoverItem *item1 = new HoverItem(QRectF(-10, -10, 20, 20));
    HoverItem *item2 = new HoverItem(QRectF(-5, -5, 10, 10));

    scene.addItem(item1);
    scene.addItem(item2);
    item2->setParentItem(item1);
    item2->setAcceptHoverEvents(true);

    QGraphicsSceneMouseEvent event(QEvent::GraphicsSceneMouseMove);
    event.setScenePos(QPointF(-100, -100));
    QCoreApplication::sendEvent(&scene, &event);
    QCOMPARE(item2->hoverInCount, 0);
    QCOMPARE(item2->hoverMoveCount, 0);
    QCOMPARE(item2->hoverOutCount, 0);
    QCOMPARE(item1->hoverInCount, 0);
    QCOMPARE(item1->hoverMoveCount, 0);
    QCOMPARE(item1->hoverOutCount, 0);

    event.setScenePos(QPointF(-2.5, -2.5));
    QCoreApplication::sendEvent(&scene, &event);

    QCOMPARE(item2->hoverInCount, 1);
    QCOMPARE(item2->hoverMoveCount, 1);
    QCOMPARE(item2->hoverOutCount, 0);
    QCOMPARE(item1->hoverInCount, 0);
    QCOMPARE(item1->hoverMoveCount, 0);
    QCOMPARE(item1->hoverOutCount, 0);

    event.setScenePos(QPointF(0, 0));
    QCoreApplication::sendEvent(&scene, &event);

    QCOMPARE(item2->hoverInCount, 1);
    QCOMPARE(item2->hoverMoveCount, 2);
    QCOMPARE(item2->hoverOutCount, 0);
    QCOMPARE(item1->hoverInCount, 0);
    QCOMPARE(item1->hoverMoveCount, 0);
    QCOMPARE(item1->hoverOutCount, 0);

    event.setScenePos(QPointF(-7, -7));
    QCoreApplication::sendEvent(&scene, &event);

    QCOMPARE(item2->hoverInCount, 1);
    QCOMPARE(item2->hoverMoveCount, 2);
    QCOMPARE(item2->hoverOutCount, 1);
    QCOMPARE(item1->hoverInCount, 0);
    QCOMPARE(item1->hoverMoveCount, 0);
    QCOMPARE(item1->hoverOutCount, 0);

    event.setScenePos(QPointF(0, 0));
    QCoreApplication::sendEvent(&scene, &event);

    QCOMPARE(item2->hoverInCount, 2);
    QCOMPARE(item2->hoverMoveCount, 3);
    QCOMPARE(item2->hoverOutCount, 1);
    QCOMPARE(item1->hoverInCount, 0);
    QCOMPARE(item1->hoverMoveCount, 0);
    QCOMPARE(item1->hoverOutCount, 0);

    HoverItem *item0 = new HoverItem(QRectF(-20, -20, 20, 20));
    scene.addItem(item0);
    item1->setParentItem(item0);
    item0->setAcceptHoverEvents(true);

    event.setScenePos(QPointF(-100, -100));
    QCoreApplication::sendEvent(&scene, &event);

    event.setScenePos(QPointF(-15, -15));
    QCoreApplication::sendEvent(&scene, &event);

    QCOMPARE(item2->hoverInCount, 2);
    QCOMPARE(item2->hoverMoveCount, 3);
    QCOMPARE(item2->hoverOutCount, 2);
    QCOMPARE(item1->hoverInCount, 0);
    QCOMPARE(item1->hoverMoveCount, 0);
    QCOMPARE(item1->hoverOutCount, 0);
    QCOMPARE(item0->hoverInCount, 1);
    QCOMPARE(item0->hoverMoveCount, 1);
    QCOMPARE(item0->hoverOutCount, 0);
}

void tst_QGraphicsItem::hasFocus()
{
    QGraphicsLineItem *line = new QGraphicsLineItem;
    QVERIFY(!line->hasFocus());
    line->setFocus();
    QVERIFY(!line->hasFocus());

    QGraphicsScene scene;
    QEvent activate(QEvent::WindowActivate);
    QCoreApplication::sendEvent(&scene, &activate);

    scene.addItem(line);

    line->setFocus();
    QVERIFY(!line->hasFocus());
    line->setFlag(QGraphicsItem::ItemIsFocusable);
    line->setFocus();
    QVERIFY(line->hasFocus());

    QGraphicsScene scene2;
    QCoreApplication::sendEvent(&scene2, &activate);

    scene2.addItem(line);
    QVERIFY(!line->hasFocus());

    QCOMPARE(scene.focusItem(), nullptr);
    QCOMPARE(scene2.focusItem(), nullptr);

    line->setFocus();
    QVERIFY(line->hasFocus());
    line->clearFocus();
    QVERIFY(!line->hasFocus());

    QGraphicsLineItem *line2 = new QGraphicsLineItem;
    line2->setFlag(QGraphicsItem::ItemIsFocusable);
    scene2.addItem(line2);

    line2->setFocus();
    QVERIFY(!line->hasFocus());
    QVERIFY(line2->hasFocus());
    line->setFocus();
    QVERIFY(line->hasFocus());
    QVERIFY(!line2->hasFocus());
}

void tst_QGraphicsItem::pos()
{
    QGraphicsItem *child = new QGraphicsLineItem;
    QGraphicsItem *parent = new QGraphicsLineItem;

    QCOMPARE(child->pos(), QPointF());
    QCOMPARE(parent->pos(), QPointF());

    child->setParentItem(parent);
    child->setPos(10, 10);

    QCOMPARE(child->pos(), QPointF(10, 10));

    parent->setPos(10, 10);

    QCOMPARE(parent->pos(), QPointF(10, 10));
    QCOMPARE(child->pos(), QPointF(10, 10));

    delete child;
    delete parent;
}

void tst_QGraphicsItem::scenePos()
{
    QGraphicsItem *child = new QGraphicsLineItem;
    QGraphicsItem *parent = new QGraphicsLineItem;

    QCOMPARE(child->scenePos(), QPointF());
    QCOMPARE(parent->scenePos(), QPointF());

    child->setParentItem(parent);
    child->setPos(10, 10);

    QCOMPARE(child->scenePos(), QPointF(10, 10));

    parent->setPos(10, 10);

    QCOMPARE(parent->scenePos(), QPointF(10, 10));
    QCOMPARE(child->scenePos(), QPointF(20, 20));

    parent->setPos(20, 20);

    QCOMPARE(parent->scenePos(), QPointF(20, 20));
    QCOMPARE(child->scenePos(), QPointF(30, 30));

    delete child;
    delete parent;
}

void tst_QGraphicsItem::matrix()
{
    QGraphicsLineItem line;
    QCOMPARE(line.transform(), QTransform());
    line.setTransform(QTransform().rotate(90));
    QCOMPARE(line.transform(), QTransform().rotate(90));
    line.setTransform(QTransform().rotate(90));
    QCOMPARE(line.transform(), QTransform().rotate(90));
    line.setTransform(QTransform().rotate(90), true);
    QCOMPARE(line.transform(), QTransform().rotate(180));
    line.setTransform(QTransform().rotate(-90), true);
    QCOMPARE(line.transform(), QTransform().rotate(90));
    line.resetTransform();
    QCOMPARE(line.transform(), QTransform());

    line.setTransform(QTransform().rotate(90), true);
    QCOMPARE(line.transform(), QTransform().rotate(90));
    line.setTransform(QTransform().rotate(90), true);
    QCOMPARE(line.transform(), QTransform().rotate(90).rotate(90));
    line.resetTransform();

    line.setTransform(QTransform::fromScale(2, 4), true);
    QCOMPARE(line.transform(), QTransform::fromScale(2, 4));
    line.setTransform(QTransform::fromScale(2, 4), true);
    QCOMPARE(line.transform(), QTransform::fromScale(2, 4).scale(2, 4));
    line.resetTransform();

    line.setTransform(QTransform().shear(2, 4), true);
    QCOMPARE(line.transform(), QTransform().shear(2, 4));
    line.setTransform(QTransform().shear(2, 4), true);
    QCOMPARE(line.transform(), QTransform().shear(2, 4).shear(2, 4));
    line.resetTransform();

    line.setTransform(QTransform::fromTranslate(10, 10), true);
    QCOMPARE(line.transform(), QTransform::fromTranslate(10, 10));
    line.setTransform(QTransform::fromTranslate(10, 10), true);
    QCOMPARE(line.transform(), QTransform::fromTranslate(10, 10).translate(10, 10));
    line.resetTransform();
}

void tst_QGraphicsItem::sceneTransform()
{
    QGraphicsLineItem *parent = new QGraphicsLineItem;
    QGraphicsLineItem *child = new QGraphicsLineItem(QLineF(), parent);

    QCOMPARE(parent->sceneTransform(), QTransform());
    QCOMPARE(child->sceneTransform(), QTransform());

    parent->setTransform(QTransform::fromTranslate(10, 10), true);
    QCOMPARE(parent->sceneTransform(), QTransform().translate(10, 10));
    QCOMPARE(child->sceneTransform(), QTransform().translate(10, 10));

    child->setTransform(QTransform::fromTranslate(10, 10), true);
    QCOMPARE(parent->sceneTransform(), QTransform().translate(10, 10));
    QCOMPARE(child->sceneTransform(), QTransform().translate(20, 20));

    parent->setTransform(QTransform().rotate(90), true);
    QCOMPARE(parent->sceneTransform(), QTransform().translate(10, 10).rotate(90));
    QCOMPARE(child->sceneTransform(), QTransform().translate(10, 10).rotate(90).translate(10, 10));

    delete child;
    delete parent;
}

void tst_QGraphicsItem::setTransform()
{
    QGraphicsScene scene;
    QSignalSpy spy(&scene, &QGraphicsScene::changed);
    QRectF unrotatedRect(-12, -34, 56, 78);
    QGraphicsRectItem item(unrotatedRect, nullptr);
    item.setPen(QPen(Qt::black, 0));
    scene.addItem(&item);
    scene.update(scene.sceneRect());
    QCoreApplication::processEvents();

    QCOMPARE(spy.size(), 1);

    item.setTransform(QTransform().rotate(qreal(12.34)));
    QRectF rotatedRect = scene.sceneRect();
    QVERIFY(unrotatedRect != rotatedRect);
    scene.update(scene.sceneRect());
    QCoreApplication::processEvents();

    QCOMPARE(spy.size(), 2);

    item.setTransform(QTransform());

    scene.update(scene.sceneRect());
    QCoreApplication::processEvents();

    QCOMPARE(spy.size(), 3);
    QList<QRectF> rlist = qvariant_cast<QList<QRectF> >(spy.last().at(0));

    QCOMPARE(rlist.size(), 2);
    // From item.setMatrix() (clearing rotated rect), from scene.update() (updating scene rect),
    // squashed into one
    QCOMPARE(rlist.at(0), rotatedRect);
    QCOMPARE(rlist.at(1), unrotatedRect); // From post-update      (update current state)
}

static GraphicsItems _paintedItems;
class PainterItem : public QGraphicsItem
{
protected:
    QRectF boundingRect() const override
    { return QRectF(-10, -10, 20, 20); }

    void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override
    {
        _paintedItems << this;
        painter->fillRect(boundingRect(), Qt::red);
    }
};

void tst_QGraphicsItem::zValue()
{
    Q_CHECK_PAINTEVENTS

    QGraphicsScene scene;

    QGraphicsItem *item1 = new PainterItem;
    QGraphicsItem *item2 = new PainterItem;
    QGraphicsItem *item3 = new PainterItem;
    QGraphicsItem *item4 = new PainterItem;
    scene.addItem(item1);
    scene.addItem(item2);
    scene.addItem(item3);
    scene.addItem(item4);
    item2->setZValue(-3);
    item4->setZValue(-2);
    item1->setZValue(-1);
    item3->setZValue(0);

    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));

    QApplication::processEvents();

    QTRY_VERIFY(!_paintedItems.isEmpty());
    QVERIFY((_paintedItems.size() % 4) == 0);
    for (int i = 0; i < 3; ++i)
        QVERIFY(_paintedItems.at(i)->zValue() < _paintedItems.at(i + 1)->zValue());
}

void tst_QGraphicsItem::shape()
{
    QGraphicsLineItem line(QLineF(-10, -10, 20, 20));
    line.setPen(QPen(Qt::black, 0));

    // We unfortunately need this hack as QPainterPathStroker will set a width of 1.0
    // if we pass a value of 0.0 to QPainterPathStroker::setWidth()
    const qreal penWidthZero = qreal(0.00000001);

    QPainterPathStroker ps;
    ps.setWidth(penWidthZero);

    QPainterPath path(line.line().p1());
    path.lineTo(line.line().p2());
    QPainterPath p = ps.createStroke(path);
    p.addPath(path);
    QCOMPARE(line.shape(), p);

    QPen linePen;
    linePen.setWidthF(5.0);
    linePen.setCapStyle(Qt::RoundCap);
    line.setPen(linePen);

    ps.setCapStyle(line.pen().capStyle());
    ps.setWidth(line.pen().widthF());
    p = ps.createStroke(path);
    p.addPath(path);
    QCOMPARE(line.shape(), p);

    linePen.setCapStyle(Qt::FlatCap);
    line.setPen(linePen);
    ps.setCapStyle(line.pen().capStyle());
    p = ps.createStroke(path);
    p.addPath(path);
    QCOMPARE(line.shape(), p);

    linePen.setCapStyle(Qt::SquareCap);
    line.setPen(linePen);
    ps.setCapStyle(line.pen().capStyle());
    p = ps.createStroke(path);
    p.addPath(path);
    QCOMPARE(line.shape(), p);

    QGraphicsRectItem rect(QRectF(-10, -10, 20, 20));
    rect.setPen(QPen(Qt::black, 0));
    QPainterPathStroker ps1;
    ps1.setWidth(penWidthZero);
    path = QPainterPath();
    path.addRect(rect.rect());
    p = ps1.createStroke(path);
    p.addPath(path);
    QCOMPARE(rect.shape(), p);

    QGraphicsEllipseItem ellipse(QRectF(-10, -10, 20, 20));
    ellipse.setPen(QPen(Qt::black, 0));
    QPainterPathStroker ps2;
    ps2.setWidth(ellipse.pen().widthF() <= 0.0 ? penWidthZero : ellipse.pen().widthF());
    path = QPainterPath();
    path.addEllipse(ellipse.rect());
    p = ps2.createStroke(path);
    p.addPath(path);
    QCOMPARE(ellipse.shape(), p);

    QPainterPathStroker ps3;
    ps3.setWidth(penWidthZero);
    p = ps3.createStroke(path);
    p.addPath(path);
    QGraphicsPathItem pathItem(path);
    pathItem.setPen(QPen(Qt::black, 0));
    QCOMPARE(pathItem.shape(), p);

    QRegion region(QRect(0, 0, 300, 200));
    region = region.subtracted(QRect(50, 50, 200, 100));

    QImage image(300, 200, QImage::Format_ARGB32_Premultiplied);
    image.fill(0);
    QPainter painter(&image);
    painter.setClipRegion(region);
    painter.fillRect(0, 0, 300, 200, Qt::green);
    painter.end();
    QPixmap pixmap = QPixmap::fromImage(image);

    QGraphicsPixmapItem pixmapItem(pixmap);
    path = QPainterPath();
    path.addRegion(region);

    {
        QBitmap bitmap(300, 200);
        bitmap.clear();
        QPainter painter(&bitmap);
        painter.setClipRegion(region);
        painter.fillRect(0, 0, 300, 200, Qt::color1);
        painter.end();

        QBitmap bitmap2(300, 200);
        bitmap2.clear();
        painter.begin(&bitmap2);
        painter.setClipPath(pixmapItem.shape());
        painter.fillRect(0, 0, 300, 200, Qt::color1);
        painter.end();

        QCOMPARE(bitmap.toImage(), bitmap2.toImage());
    }

    QPolygonF poly;
    poly << QPointF(0, 0) << QPointF(10, 0) << QPointF(0, 10);
    QGraphicsPolygonItem polygon(poly);
    polygon.setPen(QPen(Qt::black, 0));
    path = QPainterPath();
    path.addPolygon(poly);

    QPainterPathStroker ps4;
    ps4.setWidth(penWidthZero);
    p = ps4.createStroke(path);
    p.addPath(path);
    QCOMPARE(polygon.shape(), p);
}

void tst_QGraphicsItem::contains()
{
    if (sizeof(qreal) != sizeof(double))
        QSKIP("Skipped due to rounding errors");

    // Rect
    QGraphicsRectItem rect(QRectF(-10, -10, 20, 20));
    QVERIFY(!rect.contains(QPointF(-11, -10)));
    QVERIFY(rect.contains(QPointF(-10, -10)));
    QVERIFY(!rect.contains(QPointF(-11, 0)));
    QVERIFY(rect.contains(QPointF(-10, 0)));
    QVERIFY(rect.contains(QPointF(0, -10)));
    QVERIFY(rect.contains(QPointF(0, 0)));
    QVERIFY(rect.contains(QPointF(9, 9)));

    // Ellipse
    QGraphicsEllipseItem ellipse(QRectF(-10, -10, 20, 20));
    QVERIFY(!ellipse.contains(QPointF(-10, -10)));
    QVERIFY(ellipse.contains(QPointF(-9, 0)));
    QVERIFY(ellipse.contains(QPointF(0, -9)));
    QVERIFY(ellipse.contains(QPointF(0, 0)));
    QVERIFY(!ellipse.contains(QPointF(9, 9)));

    // Line
    QGraphicsLineItem line(QLineF(-10, -10, 20, 20));
    QVERIFY(!line.contains(QPointF(-10, 0)));
    QVERIFY(!line.contains(QPointF(0, -10)));
    QVERIFY(!line.contains(QPointF(10, 0)));
    QVERIFY(!line.contains(QPointF(0, 10)));
    QVERIFY(line.contains(QPointF(0, 0)));
    QVERIFY(line.contains(QPointF(-9, -9)));
    QVERIFY(line.contains(QPointF(9, 9)));

    // Polygon
    QGraphicsPolygonItem polygon(QPolygonF()
                                 << QPointF(0, 0)
                                 << QPointF(10, 0)
                                 << QPointF(0, 10));
    QVERIFY(polygon.contains(QPointF(1, 1)));
    QVERIFY(polygon.contains(QPointF(4, 4)));
    QVERIFY(polygon.contains(QPointF(1, 4)));
    QVERIFY(polygon.contains(QPointF(4, 1)));
    QVERIFY(!polygon.contains(QPointF(8, 8)));
    QVERIFY(polygon.contains(QPointF(1, 8)));
    QVERIFY(polygon.contains(QPointF(8, 1)));
}

void tst_QGraphicsItem::collidesWith_item()
{
    // Rectangle
    QGraphicsRectItem rect(QRectF(-10, -10, 20, 20));
    QGraphicsRectItem rect2(QRectF(-10, -10, 20, 20));
    QVERIFY(rect.collidesWithItem(&rect2));
    QVERIFY(rect2.collidesWithItem(&rect));
    rect2.setPos(21, 21);
    QVERIFY(!rect.collidesWithItem(&rect2));
    QVERIFY(!rect2.collidesWithItem(&rect));
    rect2.setPos(-21, -21);
    QVERIFY(!rect.collidesWithItem(&rect2));
    QVERIFY(!rect2.collidesWithItem(&rect));
    rect2.setPos(-17, -17);
    QVERIFY(rect.collidesWithItem(&rect2));
    QVERIFY(rect2.collidesWithItem(&rect));

    QGraphicsEllipseItem ellipse(QRectF(-10, -10, 20, 20));
    QGraphicsEllipseItem ellipse2(QRectF(-10, -10, 20, 20));
    QVERIFY(ellipse.collidesWithItem(&ellipse2));
    QVERIFY(ellipse2.collidesWithItem(&ellipse));
    ellipse2.setPos(21, 21);
    QVERIFY(!ellipse.collidesWithItem(&ellipse2));
    QVERIFY(!ellipse2.collidesWithItem(&ellipse));
    ellipse2.setPos(-21, -21);
    QVERIFY(!ellipse.collidesWithItem(&ellipse2));
    QVERIFY(!ellipse2.collidesWithItem(&ellipse));

    ellipse2.setPos(-17, -17);
    QVERIFY(!ellipse.collidesWithItem(&ellipse2));
    QVERIFY(!ellipse2.collidesWithItem(&ellipse));

    {
        QGraphicsScene scene;
        QGraphicsRectItem rect(20, 20, 100, 100, nullptr);
        scene.addItem(&rect);
        QGraphicsRectItem rect2(40, 40, 50, 50, nullptr);
        scene.addItem(&rect2);
        rect2.setZValue(1);
        QGraphicsLineItem line(0, 0, 200, 200, nullptr);
        scene.addItem(&line);
        line.setZValue(2);

        QCOMPARE(scene.items().size(), 3);

        QList<QGraphicsItem *> col1 = rect.collidingItems();
        QCOMPARE(col1.size(), 2);
        QCOMPARE(col1.constFirst(), &line);
        QCOMPARE(col1.constLast(), &rect2);

        QList<QGraphicsItem *> col2 = rect2.collidingItems();
        QCOMPARE(col2.size(), 2);
        QCOMPARE(col2.constFirst(), &line);
        QCOMPARE(col2.constLast(), &rect);

        QList<QGraphicsItem *> col3 = line.collidingItems();
        QCOMPARE(col3.size(), 2);
        QCOMPARE(col3.constFirst(), &rect2);
        QCOMPARE(col3.constLast(), &rect);
    }
}

void tst_QGraphicsItem::collidesWith_path_data()
{
    QTest::addColumn<QPointF>("pos");
    QTest::addColumn<QTransform>("transform");
    QTest::addColumn<QPainterPath>("shape");
    QTest::addColumn<bool>("rectCollides");
    QTest::addColumn<bool>("ellipseCollides");

    QTest::newRow("nothing") << QPointF(0, 0) << QTransform() << QPainterPath() << false << false;

    QPainterPath rect;
    rect.addRect(0, 0, 20, 20);

    QTest::newRow("rect1") << QPointF(0, 0) << QTransform() << rect << true << true;
    QTest::newRow("rect2") << QPointF(0, 0) << QTransform::fromTranslate(21, 21) << rect << false << false;
    QTest::newRow("rect3") << QPointF(21, 21) << QTransform() << rect << false << false;
}

void tst_QGraphicsItem::collidesWith_path()
{
    QFETCH(QPointF, pos);
    QFETCH(QTransform, transform);
    QFETCH(QPainterPath, shape);
    QFETCH(bool, rectCollides);
    QFETCH(bool, ellipseCollides);

    QGraphicsRectItem rect(QRectF(0, 0, 20, 20));
    QGraphicsEllipseItem ellipse(QRectF(0, 0, 20, 20));

    rect.setPos(pos);
    rect.setTransform(transform);

    ellipse.setPos(pos);
    ellipse.setTransform(transform);

    QPainterPath mappedShape = rect.sceneTransform().inverted().map(shape);

    if (rectCollides)
        QVERIFY(rect.collidesWithPath(mappedShape));
    else
        QVERIFY(!rect.collidesWithPath(mappedShape));

    if (ellipseCollides)
        QVERIFY(ellipse.collidesWithPath(mappedShape));
    else
        QVERIFY(!ellipse.collidesWithPath(mappedShape));
}

void tst_QGraphicsItem::collidesWithItemWithClip()
{
    QGraphicsScene scene;

    QGraphicsEllipseItem *ellipse = scene.addEllipse(0, 0, 100, 100);
    ellipse->setFlag(QGraphicsItem::ItemClipsChildrenToShape);
    QGraphicsEllipseItem *ellipse2 = scene.addEllipse(0, 0, 10, 10);
    ellipse2->setParentItem(ellipse);
    QGraphicsEllipseItem *ellipse3 = scene.addEllipse(0, 0, 10, 10);
    ellipse3->setParentItem(ellipse);
    QGraphicsEllipseItem *ellipse5 = scene.addEllipse(50, 50, 10, 10);
    ellipse5->setParentItem(ellipse);
    QGraphicsEllipseItem *ellipse4 = scene.addEllipse(0, 0, 10, 10);

    QVERIFY(ellipse2->collidesWithItem(ellipse3));
    QVERIFY(ellipse3->collidesWithItem(ellipse2));
    QVERIFY(!ellipse2->collidesWithItem(ellipse));
    QVERIFY(!ellipse->collidesWithItem(ellipse2));
    QVERIFY(!ellipse4->collidesWithItem(ellipse));
    QVERIFY(!ellipse4->collidesWithItem(ellipse2));
    QVERIFY(!ellipse4->collidesWithItem(ellipse3));
    QVERIFY(!ellipse->collidesWithItem(ellipse4));
    QVERIFY(!ellipse2->collidesWithItem(ellipse4));
    QVERIFY(!ellipse3->collidesWithItem(ellipse4));
    QVERIFY(ellipse->collidesWithItem(ellipse5));
    QVERIFY(ellipse5->collidesWithItem(ellipse));
}

class MyItem : public QGraphicsEllipseItem
{
public:
    bool isObscuredBy(const QGraphicsItem *item) const override
    {
        auto myItem = qgraphicsitem_cast<const MyItem *>(item);
        if (!myItem)
            return QGraphicsEllipseItem::isObscuredBy(item);

        if (item->zValue() <= zValue())
            return false;

        QRectF r = rect();
        QPointF topMid = (r.topRight() + r.topLeft()) / 2;
        QPointF botMid = (r.bottomRight() + r.bottomLeft()) / 2;
        QPointF leftMid = (r.topLeft() + r.bottomLeft()) / 2;
        QPointF rightMid = (r.topRight() + r.bottomRight()) / 2;

        QPainterPath mappedShape = item->mapToItem(this, item->opaqueArea());

        return mappedShape.contains(topMid) && mappedShape.contains(botMid)
            && mappedShape.contains(leftMid) && mappedShape.contains(rightMid);
    }

    QPainterPath opaqueArea() const override
    {
        return shape();
    }

    enum { Type = UserType + 1 };

    int type() const override { return Type; }
};

void tst_QGraphicsItem::isObscuredBy()
{
    QGraphicsScene scene;

    MyItem myitem1, myitem2;

    myitem1.setRect(QRectF(50, 50, 40, 200));
    myitem1.setTransform(QTransform().rotate(67), true);

    myitem2.setRect(QRectF(25, 25, 20, 20));
    myitem2.setZValue(-1.0);
    scene.addItem(&myitem1);
    scene.addItem(&myitem2);

    QVERIFY(!myitem2.isObscuredBy(&myitem1));
    QVERIFY(!myitem1.isObscuredBy(&myitem2));

    myitem2.setRect(QRectF(-50, 85, 20, 20));
    QVERIFY(myitem2.isObscuredBy(&myitem1));
    QVERIFY(!myitem1.isObscuredBy(&myitem2));

    myitem2.setRect(QRectF(-30, 70, 20, 20));
    QVERIFY(!myitem2.isObscuredBy(&myitem1));
    QVERIFY(!myitem1.isObscuredBy(&myitem2));

    QGraphicsRectItem rect1, rect2;

    rect1.setRect(QRectF(-40, -40, 50, 50));
    rect1.setBrush(QBrush(Qt::red));
    rect2.setRect(QRectF(-30, -20, 20, 20));
    rect2.setZValue(-1.0);
    rect2.setBrush(QBrush(Qt::blue));

    QVERIFY(rect2.isObscuredBy(&rect1));
    QVERIFY(!rect1.isObscuredBy(&rect2));

    rect2.setPos(QPointF(-20, -25));

    QVERIFY(!rect2.isObscuredBy(&rect1));
    QVERIFY(!rect1.isObscuredBy(&rect2));

    rect2.setPos(QPointF(-100, -100));

    QVERIFY(!rect2.isObscuredBy(&rect1));
    QVERIFY(!rect1.isObscuredBy(&rect2));
}

class OpaqueItem : public QGraphicsRectItem
{
protected:
    QPainterPath opaqueArea() const override
    {
        return shape();
    }
};

void tst_QGraphicsItem::isObscured()
{
    if (sizeof(qreal) != sizeof(double))
        QSKIP("Skipped due to rounding errors");

    OpaqueItem *item1 = new OpaqueItem;
    item1->setRect(0, 0, 100, 100);
    item1->setZValue(0);

    OpaqueItem *item2 = new OpaqueItem;
    item2->setZValue(1);
    item2->setRect(0, 0, 100, 100);

    QGraphicsScene scene;
    scene.addItem(item1);
    scene.addItem(item2);

    QVERIFY(item1->isObscured());
    QVERIFY(item1->isObscuredBy(item2));
    QVERIFY(item1->isObscured(QRectF(0, 0, 50, 50)));
    QVERIFY(item1->isObscured(QRectF(50, 0, 50, 50)));
    QVERIFY(item1->isObscured(QRectF(50, 50, 50, 50)));
    QVERIFY(item1->isObscured(QRectF(0, 50, 50, 50)));
    QVERIFY(item1->isObscured(0, 0, 50, 50));
    QVERIFY(item1->isObscured(50, 0, 50, 50));
    QVERIFY(item1->isObscured(50, 50, 50, 50));
    QVERIFY(item1->isObscured(0, 50, 50, 50));
    QVERIFY(!item2->isObscured());
    QVERIFY(!item2->isObscuredBy(item1));
    QVERIFY(!item2->isObscured(QRectF(0, 0, 50, 50)));
    QVERIFY(!item2->isObscured(QRectF(50, 0, 50, 50)));
    QVERIFY(!item2->isObscured(QRectF(50, 50, 50, 50)));
    QVERIFY(!item2->isObscured(QRectF(0, 50, 50, 50)));
    QVERIFY(!item2->isObscured(0, 0, 50, 50));
    QVERIFY(!item2->isObscured(50, 0, 50, 50));
    QVERIFY(!item2->isObscured(50, 50, 50, 50));
    QVERIFY(!item2->isObscured(0, 50, 50, 50));

    item2->moveBy(50, 0);

    QVERIFY(!item1->isObscured());
    QVERIFY(!item1->isObscuredBy(item2));
    QVERIFY(!item1->isObscured(QRectF(0, 0, 50, 50)));
    QVERIFY(item1->isObscured(QRectF(50, 0, 50, 50)));
    QVERIFY(item1->isObscured(QRectF(50, 50, 50, 50)));
    QVERIFY(!item1->isObscured(QRectF(0, 50, 50, 50)));
    QVERIFY(!item1->isObscured(0, 0, 50, 50));
    QVERIFY(item1->isObscured(50, 0, 50, 50));
    QVERIFY(item1->isObscured(50, 50, 50, 50));
    QVERIFY(!item1->isObscured(0, 50, 50, 50));
    QVERIFY(!item2->isObscured());
    QVERIFY(!item2->isObscuredBy(item1));
    QVERIFY(!item2->isObscured(QRectF(0, 0, 50, 50)));
    QVERIFY(!item2->isObscured(QRectF(50, 0, 50, 50)));
    QVERIFY(!item2->isObscured(QRectF(50, 50, 50, 50)));
    QVERIFY(!item2->isObscured(QRectF(0, 50, 50, 50)));
    QVERIFY(!item2->isObscured(0, 0, 50, 50));
    QVERIFY(!item2->isObscured(50, 0, 50, 50));
    QVERIFY(!item2->isObscured(50, 50, 50, 50));
    QVERIFY(!item2->isObscured(0, 50, 50, 50));
}

void tst_QGraphicsItem::mapFromToParent()
{
    QPainterPath path1;
    path1.addRect(0, 0, 200, 200);

    QPainterPath path2;
    path2.addRect(0, 0, 100, 100);

    QPainterPath path3;
    path3.addRect(0, 0, 50, 50);

    QPainterPath path4;
    path4.addRect(0, 0, 25, 25);

    QGraphicsItem *item1 = new QGraphicsPathItem(path1);
    QGraphicsItem *item2 = new QGraphicsPathItem(path2, item1);
    QGraphicsItem *item3 = new QGraphicsPathItem(path3, item2);
    QGraphicsItem *item4 = new QGraphicsPathItem(path4, item3);

    item1->setPos(10, 10);
    item2->setPos(10, 10);
    item3->setPos(10, 10);
    item4->setPos(10, 10);

    for (int i = 0; i < 4; ++i) {
        QTransform transform;
        transform.rotate(i * 90);
        transform.translate(i * 100, -i * 100);
        transform.scale(2, 4);
        item1->setTransform(transform);

        QCOMPARE(item1->mapToParent(QPointF(0, 0)), item1->pos() + transform.map(QPointF(0, 0)));
        QCOMPARE(item2->mapToParent(QPointF(0, 0)), item2->pos());
        QCOMPARE(item3->mapToParent(QPointF(0, 0)), item3->pos());
        QCOMPARE(item4->mapToParent(QPointF(0, 0)), item4->pos());
        QCOMPARE(item1->mapToParent(QPointF(10, -10)), item1->pos() + transform.map(QPointF(10, -10)));
        QCOMPARE(item2->mapToParent(QPointF(10, -10)), item2->pos() + QPointF(10, -10));
        QCOMPARE(item3->mapToParent(QPointF(10, -10)), item3->pos() + QPointF(10, -10));
        QCOMPARE(item4->mapToParent(QPointF(10, -10)), item4->pos() + QPointF(10, -10));
        QCOMPARE(item1->mapToParent(QPointF(-10, 10)), item1->pos() + transform.map(QPointF(-10, 10)));
        QCOMPARE(item2->mapToParent(QPointF(-10, 10)), item2->pos() + QPointF(-10, 10));
        QCOMPARE(item3->mapToParent(QPointF(-10, 10)), item3->pos() + QPointF(-10, 10));
        QCOMPARE(item4->mapToParent(QPointF(-10, 10)), item4->pos() + QPointF(-10, 10));
        QCOMPARE(item1->mapFromParent(item1->pos()), transform.inverted().map(QPointF(0, 0)));
        QCOMPARE(item2->mapFromParent(item2->pos()), QPointF(0, 0));
        QCOMPARE(item3->mapFromParent(item3->pos()), QPointF(0, 0));
        QCOMPARE(item4->mapFromParent(item4->pos()), QPointF(0, 0));
        QCOMPARE(item1->mapFromParent(item1->pos() + QPointF(10, -10)),
                 transform.inverted().map(QPointF(10, -10)));
        QCOMPARE(item2->mapFromParent(item2->pos() + QPointF(10, -10)), QPointF(10, -10));
        QCOMPARE(item3->mapFromParent(item3->pos() + QPointF(10, -10)), QPointF(10, -10));
        QCOMPARE(item4->mapFromParent(item4->pos() + QPointF(10, -10)), QPointF(10, -10));
        QCOMPARE(item1->mapFromParent(item1->pos() + QPointF(-10, 10)),
                 transform.inverted().map(QPointF(-10, 10)));
        QCOMPARE(item2->mapFromParent(item2->pos() + QPointF(-10, 10)), QPointF(-10, 10));
        QCOMPARE(item3->mapFromParent(item3->pos() + QPointF(-10, 10)), QPointF(-10, 10));
        QCOMPARE(item4->mapFromParent(item4->pos() + QPointF(-10, 10)), QPointF(-10, 10));
    }

    delete item1;
}

void tst_QGraphicsItem::mapFromToScene()
{
    QGraphicsItem *item1 = new QGraphicsPathItem(QPainterPath());
    QGraphicsItem *item2 = new QGraphicsPathItem(QPainterPath(), item1);
    QGraphicsItem *item3 = new QGraphicsPathItem(QPainterPath(), item2);
    QGraphicsItem *item4 = new QGraphicsPathItem(QPainterPath(), item3);

    item1->setPos(100, 100);
    item2->setPos(100, 100);
    item3->setPos(100, 100);
    item4->setPos(100, 100);
    QCOMPARE(item1->pos(), QPointF(100, 100));
    QCOMPARE(item2->pos(), QPointF(100, 100));
    QCOMPARE(item3->pos(), QPointF(100, 100));
    QCOMPARE(item4->pos(), QPointF(100, 100));
    QCOMPARE(item1->pos(), item1->mapToParent(0, 0));
    QCOMPARE(item2->pos(), item2->mapToParent(0, 0));
    QCOMPARE(item3->pos(), item3->mapToParent(0, 0));
    QCOMPARE(item4->pos(), item4->mapToParent(0, 0));
    QCOMPARE(item1->mapToParent(10, 10), QPointF(110, 110));
    QCOMPARE(item2->mapToParent(10, 10), QPointF(110, 110));
    QCOMPARE(item3->mapToParent(10, 10), QPointF(110, 110));
    QCOMPARE(item4->mapToParent(10, 10), QPointF(110, 110));
    QCOMPARE(item1->mapToScene(0, 0), QPointF(100, 100));
    QCOMPARE(item2->mapToScene(0, 0), QPointF(200, 200));
    QCOMPARE(item3->mapToScene(0, 0), QPointF(300, 300));
    QCOMPARE(item4->mapToScene(0, 0), QPointF(400, 400));
    QCOMPARE(item1->mapToScene(10, 0), QPointF(110, 100));
    QCOMPARE(item2->mapToScene(10, 0), QPointF(210, 200));
    QCOMPARE(item3->mapToScene(10, 0), QPointF(310, 300));
    QCOMPARE(item4->mapToScene(10, 0), QPointF(410, 400));
    QCOMPARE(item1->mapFromScene(100, 100), QPointF(0, 0));
    QCOMPARE(item2->mapFromScene(200, 200), QPointF(0, 0));
    QCOMPARE(item3->mapFromScene(300, 300), QPointF(0, 0));
    QCOMPARE(item4->mapFromScene(400, 400), QPointF(0, 0));
    QCOMPARE(item1->mapFromScene(110, 100), QPointF(10, 0));
    QCOMPARE(item2->mapFromScene(210, 200), QPointF(10, 0));
    QCOMPARE(item3->mapFromScene(310, 300), QPointF(10, 0));
    QCOMPARE(item4->mapFromScene(410, 400), QPointF(10, 0));

    // Rotate item1 90 degrees clockwise
    QTransform transform; transform.rotate(90);
    item1->setTransform(transform);
    QCOMPARE(item1->pos(), item1->mapToParent(0, 0));
    QCOMPARE(item2->pos(), item2->mapToParent(0, 0));
    QCOMPARE(item3->pos(), item3->mapToParent(0, 0));
    QCOMPARE(item4->pos(), item4->mapToParent(0, 0));
    QCOMPARE(item1->mapToParent(10, 0), QPointF(100, 110));
    QCOMPARE(item2->mapToParent(10, 0), QPointF(110, 100));
    QCOMPARE(item3->mapToParent(10, 0), QPointF(110, 100));
    QCOMPARE(item4->mapToParent(10, 0), QPointF(110, 100));
    QCOMPARE(item1->mapToScene(0, 0), QPointF(100, 100));
    QCOMPARE(item2->mapToScene(0, 0), QPointF(0, 200));
    QCOMPARE(item3->mapToScene(0, 0), QPointF(-100, 300));
    QCOMPARE(item4->mapToScene(0, 0), QPointF(-200, 400));
    QCOMPARE(item1->mapToScene(10, 0), QPointF(100, 110));
    QCOMPARE(item2->mapToScene(10, 0), QPointF(0, 210));
    QCOMPARE(item3->mapToScene(10, 0), QPointF(-100, 310));
    QCOMPARE(item4->mapToScene(10, 0), QPointF(-200, 410));
    QCOMPARE(item1->mapFromScene(100, 100), QPointF(0, 0));
    QCOMPARE(item2->mapFromScene(0, 200), QPointF(0, 0));
    QCOMPARE(item3->mapFromScene(-100, 300), QPointF(0, 0));
    QCOMPARE(item4->mapFromScene(-200, 400), QPointF(0, 0));
    QCOMPARE(item1->mapFromScene(100, 110), QPointF(10, 0));
    QCOMPARE(item2->mapFromScene(0, 210), QPointF(10, 0));
    QCOMPARE(item3->mapFromScene(-100, 310), QPointF(10, 0));
    QCOMPARE(item4->mapFromScene(-200, 410), QPointF(10, 0));

    // Rotate item2 90 degrees clockwise
    item2->setTransform(transform);
    QCOMPARE(item1->pos(), item1->mapToParent(0, 0));
    QCOMPARE(item2->pos(), item2->mapToParent(0, 0));
    QCOMPARE(item3->pos(), item3->mapToParent(0, 0));
    QCOMPARE(item4->pos(), item4->mapToParent(0, 0));
    QCOMPARE(item1->mapToParent(10, 0), QPointF(100, 110));
    QCOMPARE(item2->mapToParent(10, 0), QPointF(100, 110));
    QCOMPARE(item3->mapToParent(10, 0), QPointF(110, 100));
    QCOMPARE(item4->mapToParent(10, 0), QPointF(110, 100));
    QCOMPARE(item1->mapToScene(0, 0), QPointF(100, 100));
    QCOMPARE(item2->mapToScene(0, 0), QPointF(0, 200));
    QCOMPARE(item3->mapToScene(0, 0), QPointF(-100, 100));
    QCOMPARE(item4->mapToScene(0, 0), QPointF(-200, 0));
    QCOMPARE(item1->mapToScene(10, 0), QPointF(100, 110));
    QCOMPARE(item2->mapToScene(10, 0), QPointF(-10, 200));
    QCOMPARE(item3->mapToScene(10, 0), QPointF(-110, 100));
    QCOMPARE(item4->mapToScene(10, 0), QPointF(-210, 0));
    QCOMPARE(item1->mapFromScene(100, 100), QPointF(0, 0));
    QCOMPARE(item2->mapFromScene(0, 200), QPointF(0, 0));
    QCOMPARE(item3->mapFromScene(-100, 100), QPointF(0, 0));
    QCOMPARE(item4->mapFromScene(-200, 0), QPointF(0, 0));
    QCOMPARE(item1->mapFromScene(100, 110), QPointF(10, 0));
    QCOMPARE(item2->mapFromScene(-10, 200), QPointF(10, 0));
    QCOMPARE(item3->mapFromScene(-110, 100), QPointF(10, 0));
    QCOMPARE(item4->mapFromScene(-210, 0), QPointF(10, 0));

    // Translate item3 50 points, then rotate 90 degrees counterclockwise
    QTransform transform2;
    transform2.translate(50, 0);
    transform2.rotate(-90);
    item3->setTransform(transform2);
    QCOMPARE(item1->pos(), item1->mapToParent(0, 0));
    QCOMPARE(item2->pos(), item2->mapToParent(0, 0));
    QCOMPARE(item3->pos(), item3->mapToParent(0, 0) - QPointF(50, 0));
    QCOMPARE(item4->pos(), item4->mapToParent(0, 0));
    QCOMPARE(item1->mapToParent(10, 0), QPointF(100, 110));
    QCOMPARE(item2->mapToParent(10, 0), QPointF(100, 110));
    QCOMPARE(item3->mapToParent(10, 0), QPointF(150, 90));
    QCOMPARE(item4->mapToParent(10, 0), QPointF(110, 100));
    QCOMPARE(item1->mapToScene(0, 0), QPointF(100, 100));
    QCOMPARE(item2->mapToScene(0, 0), QPointF(0, 200));
    QCOMPARE(item3->mapToScene(0, 0), QPointF(-150, 100));
    QCOMPARE(item4->mapToScene(0, 0), QPointF(-250, 200));
    QCOMPARE(item1->mapToScene(10, 0), QPointF(100, 110));
    QCOMPARE(item2->mapToScene(10, 0), QPointF(-10, 200));
    QCOMPARE(item3->mapToScene(10, 0), QPointF(-150, 110));
    QCOMPARE(item4->mapToScene(10, 0), QPointF(-250, 210));
    QCOMPARE(item1->mapFromScene(100, 100), QPointF(0, 0));
    QCOMPARE(item2->mapFromScene(0, 200), QPointF(0, 0));
    QCOMPARE(item3->mapFromScene(-150, 100), QPointF(0, 0));
    QCOMPARE(item4->mapFromScene(-250, 200), QPointF(0, 0));
    QCOMPARE(item1->mapFromScene(100, 110), QPointF(10, 0));
    QCOMPARE(item2->mapFromScene(-10, 200), QPointF(10, 0));
    QCOMPARE(item3->mapFromScene(-150, 110), QPointF(10, 0));
    QCOMPARE(item4->mapFromScene(-250, 210), QPointF(10, 0));

    delete item1;
}

void tst_QGraphicsItem::mapFromToItem()
{
    QGraphicsItem *item1 = new QGraphicsPathItem;
    QGraphicsItem *item2 = new QGraphicsPathItem;
    QGraphicsItem *item3 = new QGraphicsPathItem;
    QGraphicsItem *item4 = new QGraphicsPathItem;

    item1->setPos(-100, -100);
    item2->setPos(100, -100);
    item3->setPos(100, 100);
    item4->setPos(-100, 100);

    QCOMPARE(item1->mapFromItem(item2, 0, 0), QPointF(200, 0));
    QCOMPARE(item2->mapFromItem(item3, 0, 0), QPointF(0, 200));
    QCOMPARE(item3->mapFromItem(item4, 0, 0), QPointF(-200, 0));
    QCOMPARE(item4->mapFromItem(item1, 0, 0), QPointF(0, -200));
    QCOMPARE(item1->mapFromItem(item4, 0, 0), QPointF(0, 200));
    QCOMPARE(item2->mapFromItem(item1, 0, 0), QPointF(-200, 0));
    QCOMPARE(item3->mapFromItem(item2, 0, 0), QPointF(0, -200));
    QCOMPARE(item4->mapFromItem(item3, 0, 0), QPointF(200, 0));

    QTransform transform;
    transform.translate(100, 100);
    item1->setTransform(transform);

    QCOMPARE(item1->mapFromItem(item2, 0, 0), QPointF(100, -100));
    QCOMPARE(item2->mapFromItem(item3, 0, 0), QPointF(0, 200));
    QCOMPARE(item3->mapFromItem(item4, 0, 0), QPointF(-200, 0));
    QCOMPARE(item4->mapFromItem(item1, 0, 0), QPointF(100, -100));
    QCOMPARE(item1->mapFromItem(item4, 0, 0), QPointF(-100, 100));
    QCOMPARE(item2->mapFromItem(item1, 0, 0), QPointF(-100, 100));
    QCOMPARE(item3->mapFromItem(item2, 0, 0), QPointF(0, -200));
    QCOMPARE(item4->mapFromItem(item3, 0, 0), QPointF(200, 0));

    transform.rotate(90);
    item1->setTransform(transform);
    item2->setTransform(transform);
    item3->setTransform(transform);
    item4->setTransform(transform);

    QCOMPARE(item1->mapFromItem(item2, 0, 0), QPointF(0, -200));
    QCOMPARE(item2->mapFromItem(item3, 0, 0), QPointF(200, 0));
    QCOMPARE(item3->mapFromItem(item4, 0, 0), QPointF(0, 200));
    QCOMPARE(item4->mapFromItem(item1, 0, 0), QPointF(-200, 0));
    QCOMPARE(item1->mapFromItem(item4, 0, 0), QPointF(200, 0));
    QCOMPARE(item2->mapFromItem(item1, 0, 0), QPointF(0, 200));
    QCOMPARE(item3->mapFromItem(item2, 0, 0), QPointF(-200, 0));
    QCOMPARE(item4->mapFromItem(item3, 0, 0), QPointF(0, -200));
    QCOMPARE(item1->mapFromItem(item2, 10, -5), QPointF(10, -205));
    QCOMPARE(item2->mapFromItem(item3, 10, -5), QPointF(210, -5));
    QCOMPARE(item3->mapFromItem(item4, 10, -5), QPointF(10, 195));
    QCOMPARE(item4->mapFromItem(item1, 10, -5), QPointF(-190, -5));
    QCOMPARE(item1->mapFromItem(item4, 10, -5), QPointF(210, -5));
    QCOMPARE(item2->mapFromItem(item1, 10, -5), QPointF(10, 195));
    QCOMPARE(item3->mapFromItem(item2, 10, -5), QPointF(-190, -5));
    QCOMPARE(item4->mapFromItem(item3, 10, -5), QPointF(10, -205));

    QCOMPARE(item1->mapFromItem(nullptr, 10, -5), item1->mapFromScene(10, -5));
    QCOMPARE(item2->mapFromItem(nullptr, 10, -5), item2->mapFromScene(10, -5));
    QCOMPARE(item3->mapFromItem(nullptr, 10, -5), item3->mapFromScene(10, -5));
    QCOMPARE(item4->mapFromItem(nullptr, 10, -5), item4->mapFromScene(10, -5));
    QCOMPARE(item1->mapToItem(nullptr, 10, -5), item1->mapToScene(10, -5));
    QCOMPARE(item2->mapToItem(nullptr, 10, -5), item2->mapToScene(10, -5));
    QCOMPARE(item3->mapToItem(nullptr, 10, -5), item3->mapToScene(10, -5));
    QCOMPARE(item4->mapToItem(nullptr, 10, -5), item4->mapToScene(10, -5));

    delete item1;
    delete item2;
    delete item3;
    delete item4;
}

void tst_QGraphicsItem::mapRectFromToParent_data()
{
    QTest::addColumn<bool>("parent");
    QTest::addColumn<QPointF>("parentPos");
    QTest::addColumn<QTransform>("parentTransform");
    QTest::addColumn<QPointF>("pos");
    QTest::addColumn<QTransform>("transform");
    QTest::addColumn<QRectF>("inputRect");
    QTest::addColumn<QRectF>("outputRect");

    QTest::newRow("nil") << false << QPointF() << QTransform() << QPointF() << QTransform() << QRectF() << QRectF();
    QTest::newRow("simple") << false << QPointF() << QTransform() << QPointF() << QTransform()
                            << QRectF(0, 0, 10, 10) << QRectF(0, 0, 10, 10);
    QTest::newRow("simple w/parent") << true
                                     << QPointF() << QTransform()
                                     << QPointF() << QTransform()
                                     << QRectF(0, 0, 10, 10) << QRectF(0, 0, 10, 10);
    QTest::newRow("simple w/parent parentPos") << true
                                               << QPointF(50, 50) << QTransform()
                                               << QPointF() << QTransform()
                                               << QRectF(0, 0, 10, 10) << QRectF(0, 0, 10, 10);
    QTest::newRow("simple w/parent parentPos parentRotation") << true
                                                              << QPointF(50, 50) << QTransform().rotate(45)
                                                              << QPointF() << QTransform()
                                                              << QRectF(0, 0, 10, 10) << QRectF(0, 0, 10, 10);
    QTest::newRow("pos w/parent") << true
                                  << QPointF() << QTransform()
                                  << QPointF(50, 50) << QTransform()
                                  << QRectF(0, 0, 10, 10) << QRectF(50, 50, 10, 10);
    QTest::newRow("rotation w/parent") << true
                                       << QPointF() << QTransform()
                                       << QPointF() << QTransform().rotate(90)
                                       << QRectF(0, 0, 10, 10) << QRectF(-10, 0, 10, 10);
    QTest::newRow("pos rotation w/parent") << true
                                           << QPointF() << QTransform()
                                           << QPointF(50, 50) << QTransform().rotate(90)
                                           << QRectF(0, 0, 10, 10) << QRectF(40, 50, 10, 10);
    QTest::newRow("pos rotation w/parent parentPos parentRotation") << true
                                                                    << QPointF(-170, -190) << QTransform().rotate(90)
                                                                    << QPointF(50, 50) << QTransform().rotate(90)
                                                                    << QRectF(0, 0, 10, 10) << QRectF(40, 50, 10, 10);
}

void tst_QGraphicsItem::mapRectFromToParent()
{
    QFETCH(bool, parent);
    QFETCH(QPointF, parentPos);
    QFETCH(QTransform, parentTransform);
    QFETCH(QPointF, pos);
    QFETCH(QTransform, transform);
    QFETCH(QRectF, inputRect);
    QFETCH(QRectF, outputRect);

    QGraphicsRectItem *rect = new QGraphicsRectItem;
    rect->setPos(pos);
    rect->setTransform(transform);

    if (parent) {
        QGraphicsRectItem *rectParent = new QGraphicsRectItem;
        rect->setParentItem(rectParent);
        rectParent->setPos(parentPos);
        rectParent->setTransform(parentTransform);
    }

    // Make sure we use non-destructive transform operations (e.g., 90 degree
    // rotations).
    QCOMPARE(rect->mapRectToParent(inputRect), outputRect);
    QCOMPARE(rect->mapRectFromParent(outputRect), inputRect);
    QCOMPARE(rect->itemTransform(rect->parentItem()).mapRect(inputRect), outputRect);
    QCOMPARE(rect->mapToParent(inputRect).boundingRect(), outputRect);
    QCOMPARE(rect->mapToParent(QPolygonF(inputRect)).boundingRect(), outputRect);
    QCOMPARE(rect->mapFromParent(outputRect).boundingRect(), inputRect);
    QCOMPARE(rect->mapFromParent(QPolygonF(outputRect)).boundingRect(), inputRect);
    QPainterPath inputPath;
    inputPath.addRect(inputRect);
    QPainterPath outputPath;
    outputPath.addRect(outputRect);
    QCOMPARE(rect->mapToParent(inputPath).boundingRect(), outputPath.boundingRect());
    QCOMPARE(rect->mapFromParent(outputPath).boundingRect(), inputPath.boundingRect());
}

void tst_QGraphicsItem::isAncestorOf()
{
    QGraphicsItem *grandPa = new QGraphicsRectItem;
    QGraphicsItem *parent = new QGraphicsRectItem;
    QGraphicsItem *child = new QGraphicsRectItem;

    QVERIFY(!parent->isAncestorOf(nullptr));
    QVERIFY(!child->isAncestorOf(nullptr));
    QVERIFY(!parent->isAncestorOf(child));
    QVERIFY(!child->isAncestorOf(parent));
    QVERIFY(!parent->isAncestorOf(parent));

    child->setParentItem(parent);
    parent->setParentItem(grandPa);

    QVERIFY(parent->isAncestorOf(child));
    QVERIFY(grandPa->isAncestorOf(parent));
    QVERIFY(grandPa->isAncestorOf(child));
    QVERIFY(!child->isAncestorOf(parent));
    QVERIFY(!parent->isAncestorOf(grandPa));
    QVERIFY(!child->isAncestorOf(grandPa));
    QVERIFY(!child->isAncestorOf(child));
    QVERIFY(!parent->isAncestorOf(parent));
    QVERIFY(!grandPa->isAncestorOf(grandPa));

    parent->setParentItem(nullptr);

    delete child;
    delete parent;
    delete grandPa;
}

void tst_QGraphicsItem::commonAncestorItem()
{
    QGraphicsRectItem ancestorItem;
    QGraphicsItem *ancestor = &ancestorItem;
    QGraphicsItem *grandMa = new QGraphicsRectItem;
    QGraphicsItem *grandPa = new QGraphicsRectItem;
    QGraphicsItem *brotherInLaw = new QGraphicsRectItem;
    QGraphicsItem *cousin = new QGraphicsRectItem;
    QGraphicsItem *husband = new QGraphicsRectItem;
    QGraphicsItem *child = new QGraphicsRectItem;
    QGraphicsItem *wife = new QGraphicsRectItem;

    child->setParentItem(husband);
    husband->setParentItem(grandPa);
    brotherInLaw->setParentItem(grandPa);
    cousin->setParentItem(brotherInLaw);
    wife->setParentItem(grandMa);
    grandMa->setParentItem(ancestor);
    grandPa->setParentItem(ancestor);

    QCOMPARE(grandMa->commonAncestorItem(grandMa), grandMa);
    QCOMPARE(grandMa->commonAncestorItem(nullptr), nullptr);
    QCOMPARE(grandMa->commonAncestorItem(grandPa), ancestor);
    QCOMPARE(grandPa->commonAncestorItem(grandMa), ancestor);
    QCOMPARE(grandPa->commonAncestorItem(husband), grandPa);
    QCOMPARE(grandPa->commonAncestorItem(wife), ancestor);
    QCOMPARE(grandMa->commonAncestorItem(husband), ancestor);
    QCOMPARE(grandMa->commonAncestorItem(wife), grandMa);
    QCOMPARE(wife->commonAncestorItem(grandMa), grandMa);
    QCOMPARE(child->commonAncestorItem(cousin), grandPa);
    QCOMPARE(cousin->commonAncestorItem(child), grandPa);
    QCOMPARE(wife->commonAncestorItem(child), ancestor);
    QCOMPARE(child->commonAncestorItem(wife), ancestor);
}

void tst_QGraphicsItem::data()
{
    QGraphicsTextItem text;

    QCOMPARE(text.data(0), QVariant());
    text.setData(0, "TextItem");
    QCOMPARE(text.data(0), QVariant(QString("TextItem")));
    text.setData(0, QVariant());
    QCOMPARE(text.data(0), QVariant());
}

void tst_QGraphicsItem::type()
{
    QCOMPARE(int(QGraphicsItem::Type), 1);
    QCOMPARE(int(QGraphicsPathItem::Type), 2);
    QCOMPARE(int(QGraphicsRectItem::Type), 3);
    QCOMPARE(int(QGraphicsEllipseItem::Type), 4);
    QCOMPARE(int(QGraphicsPolygonItem::Type), 5);
    QCOMPARE(int(QGraphicsLineItem::Type), 6);
    QCOMPARE(int(QGraphicsPixmapItem::Type), 7);
    QCOMPARE(int(QGraphicsTextItem::Type), 8);

    QCOMPARE(QGraphicsPathItem().type(), 2);
    QCOMPARE(QGraphicsRectItem().type(), 3);
    QCOMPARE(QGraphicsEllipseItem().type(), 4);
    QCOMPARE(QGraphicsPolygonItem().type(), 5);
    QCOMPARE(QGraphicsLineItem().type(), 6);
    QCOMPARE(QGraphicsPixmapItem().type(), 7);
    QCOMPARE(QGraphicsTextItem().type(), 8);
}

void tst_QGraphicsItem::graphicsitem_cast()
{
    QGraphicsPathItem pathItem;
    const QGraphicsPathItem *pPathItem = &pathItem;
    QGraphicsRectItem rectItem;
    const QGraphicsRectItem *pRectItem = &rectItem;
    QGraphicsEllipseItem ellipseItem;
    const QGraphicsEllipseItem *pEllipseItem = &ellipseItem;
    QGraphicsPolygonItem polygonItem;
    const QGraphicsPolygonItem *pPolygonItem = &polygonItem;
    QGraphicsLineItem lineItem;
    const QGraphicsLineItem *pLineItem = &lineItem;
    QGraphicsPixmapItem pixmapItem;
    const QGraphicsPixmapItem *pPixmapItem = &pixmapItem;
    QGraphicsTextItem textItem;
    const QGraphicsTextItem *pTextItem = &textItem;

    QVERIFY(qgraphicsitem_cast<QGraphicsPathItem *>(&pathItem));
    //QVERIFY(qgraphicsitem_cast<QAbstractGraphicsPathItem *>(&pathItem));
    QVERIFY(qgraphicsitem_cast<QGraphicsItem *>(&pathItem));
    QVERIFY(qgraphicsitem_cast<const QGraphicsItem *>(pPathItem));
    QVERIFY(qgraphicsitem_cast<const QGraphicsPathItem *>(pPathItem));

    QVERIFY(qgraphicsitem_cast<QGraphicsRectItem *>(&rectItem));
    QVERIFY(qgraphicsitem_cast<QGraphicsItem *>(&rectItem));
    QVERIFY(qgraphicsitem_cast<const QGraphicsItem *>(pRectItem));
    QVERIFY(qgraphicsitem_cast<const QGraphicsRectItem *>(pRectItem));

    QVERIFY(qgraphicsitem_cast<QGraphicsEllipseItem *>(&ellipseItem));
    QVERIFY(qgraphicsitem_cast<QGraphicsItem *>(&ellipseItem));
    QVERIFY(qgraphicsitem_cast<const QGraphicsItem *>(pEllipseItem));
    QVERIFY(qgraphicsitem_cast<const QGraphicsEllipseItem *>(pEllipseItem));

    QVERIFY(qgraphicsitem_cast<QGraphicsPolygonItem *>(&polygonItem));
    //QVERIFY(qgraphicsitem_cast<QAbstractGraphicsPathItem *>(&polygonItem));
    QVERIFY(qgraphicsitem_cast<QGraphicsItem *>(&polygonItem));
    QVERIFY(qgraphicsitem_cast<const QGraphicsItem *>(pPolygonItem));
    QVERIFY(qgraphicsitem_cast<const QGraphicsPolygonItem *>(pPolygonItem));

    QVERIFY(qgraphicsitem_cast<QGraphicsLineItem *>(&lineItem));
    QVERIFY(qgraphicsitem_cast<QGraphicsItem *>(&lineItem));
    QVERIFY(qgraphicsitem_cast<const QGraphicsItem *>(pLineItem));
    QVERIFY(qgraphicsitem_cast<const QGraphicsLineItem *>(pLineItem));

    QVERIFY(qgraphicsitem_cast<QGraphicsPixmapItem *>(&pixmapItem));
    QVERIFY(qgraphicsitem_cast<QGraphicsItem *>(&pixmapItem));
    QVERIFY(qgraphicsitem_cast<const QGraphicsItem *>(pPixmapItem));
    QVERIFY(qgraphicsitem_cast<const QGraphicsPixmapItem *>(pPixmapItem));

    QVERIFY(qgraphicsitem_cast<QGraphicsTextItem *>(&textItem));
    QVERIFY(qgraphicsitem_cast<QGraphicsItem *>(&textItem));
    QVERIFY(qgraphicsitem_cast<const QGraphicsItem *>(pTextItem));
    QVERIFY(qgraphicsitem_cast<const QGraphicsTextItem *>(pTextItem));

    // and some casts that _should_ fail:
    QVERIFY(!qgraphicsitem_cast<QGraphicsEllipseItem *>(&pathItem));
    QVERIFY(!qgraphicsitem_cast<const QGraphicsTextItem *>(pPolygonItem));

    // and this shouldn't crash
    QGraphicsItem *ptr = nullptr;
    QVERIFY(!qgraphicsitem_cast<QGraphicsTextItem *>(ptr));
    QVERIFY(!qgraphicsitem_cast<QGraphicsItem *>(ptr));
}

void tst_QGraphicsItem::hoverEventsGenerateRepaints()
{
    Q_CHECK_PAINTEVENTS

    QGraphicsScene scene;
    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));
    if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation))
        QVERIFY(QTest::qWaitForWindowActive(&view));
    QCoreApplication::processEvents(); // Process all queued paint events

    EventTester *tester = new EventTester;
    scene.addItem(tester);
    tester->setAcceptHoverEvents(true);

    QTRY_COMPARE(tester->repaints, 1);

    // Send a hover enter event
    QGraphicsSceneHoverEvent hoverEnterEvent(QEvent::GraphicsSceneHoverEnter);
    hoverEnterEvent.setScenePos(QPointF(0, 0));
    hoverEnterEvent.setPos(QPointF(0, 0));
    QCoreApplication::sendEvent(&scene, &hoverEnterEvent);

    // Check that we get a repaint
    int npaints = tester->repaints;
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();
    QCOMPARE(tester->events.size(), 2); //  enter + move
    QCOMPARE(tester->repaints, npaints + 1);
    QCOMPARE(tester->events.constLast(), QEvent::GraphicsSceneHoverMove);

    // Send a hover move event
    QGraphicsSceneHoverEvent hoverMoveEvent(QEvent::GraphicsSceneHoverMove);
    hoverMoveEvent.setScenePos(QPointF(0, 0));
    hoverMoveEvent.setPos(QPointF(0, 0));
    QCoreApplication::sendEvent(&scene, &hoverMoveEvent);

    // Check that we don't get a repaint
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();

    QCOMPARE(tester->events.size(), 3);
    QCOMPARE(tester->repaints, npaints + 1);
    QCOMPARE(tester->events.constLast(), QEvent::GraphicsSceneHoverMove);

    // Send a hover leave event
    QGraphicsSceneHoverEvent hoverLeaveEvent(QEvent::GraphicsSceneHoverLeave);
    hoverLeaveEvent.setScenePos(QPointF(-100, -100));
    hoverLeaveEvent.setPos(QPointF(0, 0));
    QCoreApplication::sendEvent(&scene, &hoverLeaveEvent);

    // Check that we get a repaint
    QCoreApplication::processEvents();
    QCoreApplication::processEvents();

    QCOMPARE(tester->events.size(), 4);
    QCOMPARE(tester->repaints, npaints + 2);
    QCOMPARE(tester->events.constLast(), QEvent::GraphicsSceneHoverLeave);
}

void tst_QGraphicsItem::boundingRects_data()
{
    QTest::addColumn<AbstractGraphicsShapeItemPtr>("item");
    QTest::addColumn<QRectF>("boundingRect");

    QRectF rect(0, 0, 100, 100);
    QPainterPath path;
    path.addRect(rect);

    QRectF adjustedRect(-0.5, -0.5, 101, 101);

    QTest::newRow("path") << AbstractGraphicsShapeItemPtr(new QGraphicsPathItem(path)) << adjustedRect;
    QTest::newRow("rect") << AbstractGraphicsShapeItemPtr(new QGraphicsRectItem(rect)) << adjustedRect;
    QTest::newRow("ellipse") << AbstractGraphicsShapeItemPtr(new QGraphicsEllipseItem(rect)) << adjustedRect;
    QTest::newRow("polygon") << AbstractGraphicsShapeItemPtr(new QGraphicsPolygonItem(rect)) << adjustedRect;
}

void tst_QGraphicsItem::boundingRects()
{
    QFETCH(AbstractGraphicsShapeItemPtr, item);
    QFETCH(QRectF, boundingRect);

    item->setPen(QPen(Qt::black, 1));
    QCOMPARE(item->boundingRect(), boundingRect);
}

void tst_QGraphicsItem::boundingRects2()
{
    QGraphicsPixmapItem pixmap(QPixmap::fromImage(QImage(100, 100, QImage::Format_ARGB32_Premultiplied)));
    QCOMPARE(pixmap.boundingRect(), QRectF(0, 0, 100, 100));

    QGraphicsLineItem line(0, 0, 100, 0);
    line.setPen(QPen(Qt::black, 1));
    QCOMPARE(line.boundingRect(), QRectF(-0.5, -0.5, 101, 1));
}

void tst_QGraphicsItem::sceneBoundingRect()
{
    QGraphicsScene scene;
    QGraphicsRectItem *item = scene.addRect(QRectF(0, 0, 100, 100), QPen(Qt::black, 0));
    item->setPos(100, 100);

    QCOMPARE(item->boundingRect(), QRectF(0, 0, 100, 100));
    QCOMPARE(item->sceneBoundingRect(), QRectF(100, 100, 100, 100));

    item->setTransform(QTransform().rotate(90), true);

    QCOMPARE(item->boundingRect(), QRectF(0, 0, 100, 100));
    QCOMPARE(item->sceneBoundingRect(), QRectF(0, 100, 100, 100));
}

void tst_QGraphicsItem::childrenBoundingRect()
{
    QGraphicsScene scene;
    QGraphicsRectItem *parent = scene.addRect(QRectF(0, 0, 100, 100), QPen(Qt::black, 0));
    QGraphicsRectItem *child = scene.addRect(QRectF(0, 0, 100, 100), QPen(Qt::black, 0));
    child->setParentItem(parent);
    parent->setPos(100, 100);
    child->setPos(100, 100);

    QCOMPARE(parent->boundingRect(), QRectF(0, 0, 100, 100));
    QCOMPARE(child->boundingRect(), QRectF(0, 0, 100, 100));
    QCOMPARE(child->childrenBoundingRect(), QRectF());
    QCOMPARE(parent->childrenBoundingRect(), QRectF(100, 100, 100, 100));

    QGraphicsRectItem *child2 = scene.addRect(QRectF(0, 0, 100, 100), QPen(Qt::black, 0));
    child2->setParentItem(parent);
    child2->setPos(-100, -100);
    QCOMPARE(parent->childrenBoundingRect(), QRectF(-100, -100, 300, 300));

    QGraphicsRectItem *childChild = scene.addRect(QRectF(0, 0, 100, 100), QPen(Qt::black, 0));
    childChild->setParentItem(child);
    childChild->setPos(500, 500);
    child->setTransform(QTransform().rotate(90), true);

    scene.addPolygon(parent->mapToScene(parent->boundingRect() | parent->childrenBoundingRect()))->setPen(QPen(Qt::red));;

    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.show();

    QVERIFY(QTest::qWaitForWindowExposed(&view));

    QTRY_COMPARE(parent->childrenBoundingRect(), QRectF(-500, -100, 600, 800));
}

void tst_QGraphicsItem::childrenBoundingRectTransformed()
{
    QGraphicsScene scene;

    QGraphicsRectItem *rect = scene.addRect(QRectF(0, 0, 100, 100));
    QGraphicsRectItem *rect2 = scene.addRect(QRectF(0, 0, 100, 100));
    QGraphicsRectItem *rect3 = scene.addRect(QRectF(0, 0, 100, 100));
    QGraphicsRectItem *rect4 = scene.addRect(QRectF(0, 0, 100, 100));
    QGraphicsRectItem *rect5 = scene.addRect(QRectF(0, 0, 100, 100));
    rect2->setParentItem(rect);
    rect3->setParentItem(rect2);
    rect4->setParentItem(rect3);
    rect5->setParentItem(rect4);

    rect->setPen(QPen(Qt::black, 0));
    rect2->setPen(QPen(Qt::black, 0));
    rect3->setPen(QPen(Qt::black, 0));
    rect4->setPen(QPen(Qt::black, 0));
    rect5->setPen(QPen(Qt::black, 0));

    rect2->setTransform(QTransform().translate(50, 50).rotate(45));
    rect2->setPos(25, 25);
    rect3->setTransform(QTransform().translate(50, 50).rotate(45));
    rect3->setPos(25, 25);
    rect4->setTransform(QTransform().translate(50, 50).rotate(45));
    rect4->setPos(25, 25);
    rect5->setTransform(QTransform().translate(50, 50).rotate(45));
    rect5->setPos(25, 25);

    QRectF subTreeRect = rect->childrenBoundingRect();
    QCOMPARE(subTreeRect.left(), qreal(-206.0660171779821));
    QCOMPARE(subTreeRect.top(), qreal(75.0));
    QCOMPARE(subTreeRect.width(), qreal(351.7766952966369));
    QCOMPARE(subTreeRect.height(), qreal(251.7766952966369));

    rect->setTransform(QTransform().rotate(45), true);
    rect2->setTransform(QTransform().rotate(-45), true);
    rect3->setTransform(QTransform().rotate(45), true);
    rect4->setTransform(QTransform().rotate(-45), true);
    rect5->setTransform(QTransform().rotate(45), true);

    subTreeRect = rect->childrenBoundingRect();
    QCOMPARE(rect->childrenBoundingRect(), QRectF(-100, 75, 275, 250));
}

void tst_QGraphicsItem::childrenBoundingRect2()
{
    QGraphicsItemGroup box;
    QGraphicsLineItem l1(0, 0, 100, 0, &box);
    QGraphicsLineItem l2(100, 0, 100, 100, &box);
    QGraphicsLineItem l3(0, 0, 0, 100, &box);
    // Make sure lines (zero with/height) are included in the childrenBoundingRect.
    l1.setPen(QPen(Qt::black, 0));
    l2.setPen(QPen(Qt::black, 0));
    l3.setPen(QPen(Qt::black, 0));
    QCOMPARE(box.childrenBoundingRect(), QRectF(0, 0, 100, 100));
}

void tst_QGraphicsItem::childrenBoundingRect3()
{
    QGraphicsScene scene;

    QGraphicsRectItem *rect = scene.addRect(QRectF(0, 0, 100, 100));
    QGraphicsRectItem *rect2 = scene.addRect(QRectF(0, 0, 100, 100));
    QGraphicsRectItem *rect3 = scene.addRect(QRectF(0, 0, 100, 100));
    QGraphicsRectItem *rect4 = scene.addRect(QRectF(0, 0, 100, 100));
    QGraphicsRectItem *rect5 = scene.addRect(QRectF(0, 0, 100, 100));
    rect2->setParentItem(rect);
    rect3->setParentItem(rect2);
    rect4->setParentItem(rect3);
    rect5->setParentItem(rect4);

    rect->setPen(QPen(Qt::black, 0));
    rect2->setPen(QPen(Qt::black, 0));
    rect3->setPen(QPen(Qt::black, 0));
    rect4->setPen(QPen(Qt::black, 0));
    rect5->setPen(QPen(Qt::black, 0));

    rect2->setTransform(QTransform().translate(50, 50).rotate(45));
    rect2->setPos(25, 25);
    rect3->setTransform(QTransform().translate(50, 50).rotate(45));
    rect3->setPos(25, 25);
    rect4->setTransform(QTransform().translate(50, 50).rotate(45));
    rect4->setPos(25, 25);
    rect5->setTransform(QTransform().translate(50, 50).rotate(45));
    rect5->setPos(25, 25);

    // Try to mess up the cached bounding rect.
    (void)rect2->childrenBoundingRect();

    QRectF subTreeRect = rect->childrenBoundingRect();
    QCOMPARE(subTreeRect.left(), qreal(-206.0660171779821));
    QCOMPARE(subTreeRect.top(), qreal(75.0));
    QCOMPARE(subTreeRect.width(), qreal(351.7766952966369));
    QCOMPARE(subTreeRect.height(), qreal(251.7766952966369));
}

void tst_QGraphicsItem::childrenBoundingRect4()
{
    QGraphicsScene scene;

    QGraphicsRectItem *rect = scene.addRect(QRectF(0, 0, 10, 10));
    QGraphicsRectItem *rect2 = scene.addRect(QRectF(0, 0, 20, 20));
    QGraphicsRectItem *rect3 = scene.addRect(QRectF(0, 0, 30, 30));
    rect2->setParentItem(rect);
    rect3->setParentItem(rect);

    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.show();

    QVERIFY(QTest::qWaitForWindowExposed(&view));

    // Try to mess up the cached bounding rect.
    rect->childrenBoundingRect();
    rect2->childrenBoundingRect();

    rect3->setOpacity(0.0);
    rect3->setParentItem(rect2);

    QCOMPARE(rect->childrenBoundingRect(), rect3->boundingRect());
    QCOMPARE(rect2->childrenBoundingRect(), rect3->boundingRect());
}

void tst_QGraphicsItem::childrenBoundingRect5()
{
    QGraphicsScene scene;

    QGraphicsRectItem *parent = scene.addRect(QRectF(0, 0, 100, 100));
    QGraphicsRectItem *child = scene.addRect(QRectF(0, 0, 100, 100));
    child->setParentItem(parent);

    parent->setPen(QPen(Qt::black, 0));
    child->setPen(QPen(Qt::black, 0));

    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.show();

    QVERIFY(QTest::qWaitForWindowExposed(&view));

    // Try to mess up the cached bounding rect.
    QRectF expectedChildrenBoundingRect = parent->boundingRect();
    QCOMPARE(parent->childrenBoundingRect(), expectedChildrenBoundingRect);

    // Apply some effects.
    QGraphicsDropShadowEffect *dropShadow = new QGraphicsDropShadowEffect;
    dropShadow->setOffset(25, 25);
    child->setGraphicsEffect(dropShadow);
    parent->setGraphicsEffect(new QGraphicsOpacityEffect);

    QVERIFY(parent->childrenBoundingRect() != expectedChildrenBoundingRect);
    expectedChildrenBoundingRect |= dropShadow->boundingRect();
    QCOMPARE(parent->childrenBoundingRect(), expectedChildrenBoundingRect);
}

void tst_QGraphicsItem::group()
{
    QGraphicsScene scene;
    QGraphicsRectItem *parent = scene.addRect(QRectF(0, 0, 50, 50), QPen(Qt::black, 0), QBrush(Qt::green));
    QGraphicsRectItem *child = scene.addRect(QRectF(0, 0, 50, 50), QPen(Qt::black, 0), QBrush(Qt::blue));
    QGraphicsRectItem *parent2 = scene.addRect(QRectF(0, 0, 50, 50), QPen(Qt::black, 0), QBrush(Qt::red));
    parent2->setPos(-50, 50);
    child->setTransform(QTransform().rotate(45), true);
    child->setParentItem(parent);
    parent->setPos(25, 25);
    child->setPos(25, 25);

    QCOMPARE(parent->group(), nullptr);
    QCOMPARE(parent2->group(), nullptr);
    QCOMPARE(child->group(), nullptr);

    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));

    QGraphicsItemGroup *group = new QGraphicsItemGroup;
    group->setSelected(true);
    scene.addItem(group);

    QRectF parentSceneBoundingRect = parent->sceneBoundingRect();
    group->addToGroup(parent);
    QCOMPARE(parent->group(), group);
    QCOMPARE(parent->sceneBoundingRect(), parentSceneBoundingRect);

    QCOMPARE(parent->parentItem(), group);
    QCOMPARE(group->childItems().size(), 1);
    QCOMPARE(scene.items().size(), 4);
    QCOMPARE(scene.items(group->sceneBoundingRect()).size(), 3);


    QRectF parent2SceneBoundingRect = parent2->sceneBoundingRect();
    group->addToGroup(parent2);
    QCOMPARE(parent2->group(), group);
    QCOMPARE(parent2->sceneBoundingRect(), parent2SceneBoundingRect);

    QCOMPARE(parent2->parentItem(), group);
    QCOMPARE(group->childItems().size(), 2);
    QCOMPARE(scene.items().size(), 4);
    QCOMPARE(scene.items(group->sceneBoundingRect()).size(), 4);

    GraphicsItems newItems;
    newItems.reserve(100);
    for (int i = 0; i < 100; ++i) {
        QGraphicsItem *item = scene.addRect(QRectF(-25, -25, 50, 50), QPen(Qt::black, 0),
                                            QBrush(QColor(QRandomGenerator::global()->bounded(255), QRandomGenerator::global()->bounded(255),
                                                          QRandomGenerator::global()->bounded(255), QRandomGenerator::global()->bounded(255))));
        newItems << item;
        item->setPos(-1000 + QRandomGenerator::global()->bounded(2000),
                     -1000 + QRandomGenerator::global()->bounded(2000));
        item->setTransform(QTransform().rotate(QRandomGenerator::global()->bounded(90)), true);
    }

    view.fitInView(scene.itemsBoundingRect());

    for (QGraphicsItem *item : std::as_const(newItems)) {
        group->addToGroup(item);
        QCOMPARE(item->group(), group);
    }
}

void tst_QGraphicsItem::setGroup()
{
    QGraphicsItemGroup group1;
    QGraphicsItemGroup group2;

    const QScopedPointer<QGraphicsRectItem> rect(new QGraphicsRectItem);
    QCOMPARE(rect->group(), nullptr);
    QCOMPARE(rect->parentItem(), nullptr);
    rect->setGroup(&group1);
    QCOMPARE(rect->group(), &group1);
    QCOMPARE(rect->parentItem(), &group1);
    rect->setGroup(&group2);
    QCOMPARE(rect->group(), &group2);
    QCOMPARE(rect->parentItem(), &group2);
    rect->setGroup(nullptr);
    QCOMPARE(rect->group(), nullptr);
    QCOMPARE(rect->parentItem(), nullptr);
}

void tst_QGraphicsItem::setGroup2()
{
    QGraphicsScene scene;
    QGraphicsItemGroup group;
    scene.addItem(&group);

    QGraphicsRectItem *rect = scene.addRect(50,50,50,50,Qt::NoPen,Qt::black);
    rect->setTransformOriginPoint(50,50);
    rect->setRotation(45);
    rect->setScale(1.5);
    rect->setTransform(QTransform::fromTranslate(20,20), true);
    group.setTransform(QTransform::fromTranslate(-30, -40), true);
    group.setRotation(180);
    group.setScale(0.5);

    QTransform oldSceneTransform = rect->sceneTransform();
    rect->setGroup(&group);
    QCOMPARE(rect->sceneTransform(), oldSceneTransform);

    group.setRotation(20);
    group.setScale(2);
    rect->setRotation(90);
    rect->setScale(0.8);

    oldSceneTransform = rect->sceneTransform();
    rect->setGroup(nullptr);
    qFuzzyCompare(rect->sceneTransform(), oldSceneTransform);
}

void tst_QGraphicsItem::nestedGroups()
{
    QGraphicsItemGroup *group1 = new QGraphicsItemGroup;
    QGraphicsItemGroup *group2 = new QGraphicsItemGroup;

    QGraphicsRectItem *rect = new QGraphicsRectItem;
    QGraphicsRectItem *rect2 = new QGraphicsRectItem;
    rect2->setParentItem(rect);

    group1->addToGroup(rect);
    QCOMPARE(rect->group(), group1);
    QCOMPARE(rect2->group(), group1);

    group2->addToGroup(group1);
    QCOMPARE(rect->group(), group1);
    QCOMPARE(rect2->group(), group1);
    QCOMPARE(group1->group(), group2);
    QCOMPARE(group2->group(), nullptr);

    QGraphicsScene scene;
    scene.addItem(group1);

    QCOMPARE(rect->group(), group1);
    QCOMPARE(rect2->group(), group1);
    QCOMPARE(group1->group(), nullptr);
    QVERIFY(group2->childItems().isEmpty());

    delete group2;
}

void tst_QGraphicsItem::warpChildrenIntoGroup()
{
    QGraphicsScene scene;
    QGraphicsRectItem *parentRectItem = scene.addRect(QRectF(0, 0, 100, 100));
    QGraphicsRectItem *childRectItem = scene.addRect(QRectF(0, 0, 100, 100));
    parentRectItem->setTransform(QTransform().rotate(90), true);
    childRectItem->setPos(-50, -25);
    childRectItem->setParentItem(parentRectItem);

    QCOMPARE(childRectItem->mapToScene(50, 0), QPointF(25, 0));
    QCOMPARE(childRectItem->scenePos(), QPointF(25, -50));

    QGraphicsRectItem *parentOfGroup = scene.addRect(QRectF(0, 0, 100, 100));
    parentOfGroup->setPos(-200, -200);
    parentOfGroup->setTransform(QTransform::fromScale(2, 2), true);

    QGraphicsItemGroup *group = new QGraphicsItemGroup;
    group->setPos(50, 50);
    group->setParentItem(parentOfGroup);

    QCOMPARE(group->scenePos(), QPointF(-100, -100));

    group->addToGroup(childRectItem);

    QCOMPARE(childRectItem->mapToScene(50, 0), QPointF(25, 0));
    QCOMPARE(childRectItem->scenePos(), QPointF(25, -50));
}

void tst_QGraphicsItem::removeFromGroup()
{
    QGraphicsScene scene;
    QGraphicsRectItem *rect1 = scene.addRect(QRectF(-100, -100, 200, 200));
    QGraphicsRectItem *rect2 = scene.addRect(QRectF(100, 100, 200, 200));
    rect1->setFlag(QGraphicsItem::ItemIsSelectable);
    rect2->setFlag(QGraphicsItem::ItemIsSelectable);
    rect1->setSelected(true);
    rect2->setSelected(true);

    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.show();

    QCoreApplication::processEvents(); // index items
    QCoreApplication::processEvents(); // emit changed

    QGraphicsItemGroup *group = scene.createItemGroup(scene.selectedItems());
    QVERIFY(group);
    QCOMPARE(group->childItems().size(), 2);
    QCoreApplication::processEvents(); // index items
    QCoreApplication::processEvents(); // emit changed

    scene.destroyItemGroup(group); // calls removeFromGroup.

    QCoreApplication::processEvents(); // index items
    QCoreApplication::processEvents(); // emit changed

    QCOMPARE(scene.items().size(), 2);
    QVERIFY(!rect1->group());
    QVERIFY(!rect2->group());
}

class ChildEventTester : public QGraphicsRectItem
{
public:
    using QGraphicsRectItem::QGraphicsRectItem;

    int counter = 0;

protected:
    void focusInEvent(QFocusEvent *event) override
    { ++counter; QGraphicsRectItem::focusInEvent(event); }
    void mousePressEvent(QGraphicsSceneMouseEvent *) override
    { ++counter; }
    void mouseMoveEvent(QGraphicsSceneMouseEvent *) override
    { ++counter; }
    void mouseReleaseEvent(QGraphicsSceneMouseEvent *) override
    { ++counter; }
};

void tst_QGraphicsItem::handlesChildEvents()
{
    ChildEventTester *blue = new ChildEventTester(QRectF(0, 0, 100, 100));
    ChildEventTester *red = new ChildEventTester(QRectF(0, 0, 50, 50));
    ChildEventTester *green = new ChildEventTester(QRectF(0, 0, 25, 25));
    ChildEventTester *gray = new ChildEventTester(QRectF(0, 0, 25, 25));
    ChildEventTester *yellow = new ChildEventTester(QRectF(0, 0, 50, 50));

    blue->setBrush(QBrush(Qt::blue));
    red->setBrush(QBrush(Qt::red));
    yellow->setBrush(QBrush(Qt::yellow));
    green->setBrush(QBrush(Qt::green));
    gray->setBrush(QBrush(Qt::gray));
    red->setPos(50, 0);
    yellow->setPos(50, 50);
    green->setPos(25, 0);
    gray->setPos(25, 25);
    red->setParentItem(blue);
    yellow->setParentItem(blue);
    green->setParentItem(red);
    gray->setParentItem(red);

    QGraphicsScene scene;
    scene.addItem(blue);

    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));

    // Pull out the items, closest item first
    QList<QGraphicsItem *> items = scene.items(scene.itemsBoundingRect());
    QCOMPARE(items.at(0), yellow);
    QCOMPARE(items.at(1), gray);
    QCOMPARE(items.at(2), green);
    QCOMPARE(items.at(3), red);
    QCOMPARE(items.at(4), blue);

    QCOMPARE(blue->counter, 0);

    // Send events to the toplevel item
    QGraphicsSceneMouseEvent pressEvent(QEvent::GraphicsSceneMousePress);
    QGraphicsSceneMouseEvent releaseEvent(QEvent::GraphicsSceneMouseRelease);

    pressEvent.setButton(Qt::LeftButton);
    pressEvent.setScenePos(blue->mapToScene(5, 5));
    pressEvent.setScreenPos(view.mapFromScene(pressEvent.scenePos()));
    releaseEvent.setButton(Qt::LeftButton);
    releaseEvent.setScenePos(blue->mapToScene(5, 5));
    releaseEvent.setScreenPos(view.mapFromScene(pressEvent.scenePos()));
    QCoreApplication::sendEvent(&scene, &pressEvent);
    QCoreApplication::sendEvent(&scene, &releaseEvent);

    QCOMPARE(blue->counter, 2);

    // Send events to a level1 item
    pressEvent.setScenePos(red->mapToScene(5, 5));
    pressEvent.setScreenPos(view.mapFromScene(pressEvent.scenePos()));
    releaseEvent.setScenePos(red->mapToScene(5, 5));
    releaseEvent.setScreenPos(view.mapFromScene(releaseEvent.scenePos()));
    QCoreApplication::sendEvent(&scene, &pressEvent);
    QCoreApplication::sendEvent(&scene, &releaseEvent);

    QCOMPARE(blue->counter, 2);
    QCOMPARE(red->counter, 2);

    // Send events to a level2 item
    pressEvent.setScenePos(green->mapToScene(5, 5));
    pressEvent.setScreenPos(view.mapFromScene(pressEvent.scenePos()));
    releaseEvent.setScenePos(green->mapToScene(5, 5));
    releaseEvent.setScreenPos(view.mapFromScene(releaseEvent.scenePos()));
    QCoreApplication::sendEvent(&scene, &pressEvent);
    QCoreApplication::sendEvent(&scene, &releaseEvent);

    QCOMPARE(blue->counter, 2);
    QCOMPARE(red->counter, 2);
    QCOMPARE(green->counter, 2);

    blue->setHandlesChildEvents(true);

    // Send events to a level1 item
    pressEvent.setScenePos(red->mapToScene(5, 5));
    pressEvent.setScreenPos(view.mapFromScene(pressEvent.scenePos()));
    releaseEvent.setScenePos(red->mapToScene(5, 5));
    releaseEvent.setScreenPos(view.mapFromScene(releaseEvent.scenePos()));
    QCoreApplication::sendEvent(&scene, &pressEvent);
    QCoreApplication::sendEvent(&scene, &releaseEvent);

    QCOMPARE(blue->counter, 4);
    QCOMPARE(red->counter, 2);

    // Send events to a level2 item
    pressEvent.setScenePos(green->mapToScene(5, 5));
    pressEvent.setScreenPos(view.mapFromScene(pressEvent.scenePos()));
    releaseEvent.setScenePos(green->mapToScene(5, 5));
    releaseEvent.setScreenPos(view.mapFromScene(releaseEvent.scenePos()));
    QCoreApplication::sendEvent(&scene, &pressEvent);
    QCoreApplication::sendEvent(&scene, &releaseEvent);

    QCOMPARE(blue->counter, 6);
    QCOMPARE(red->counter, 2);
    QCOMPARE(green->counter, 2);

    blue->setHandlesChildEvents(false);

    // Send events to a level1 item
    pressEvent.setScenePos(red->mapToScene(5, 5));
    pressEvent.setScreenPos(view.mapFromScene(pressEvent.scenePos()));
    releaseEvent.setScenePos(red->mapToScene(5, 5));
    releaseEvent.setScreenPos(view.mapFromScene(releaseEvent.scenePos()));
    QCoreApplication::sendEvent(&scene, &pressEvent);
    QCoreApplication::sendEvent(&scene, &releaseEvent);

    QCOMPARE(blue->counter, 6);
    QCOMPARE(red->counter, 4);

    // Send events to a level2 item
    pressEvent.setScenePos(green->mapToScene(5, 5));
    pressEvent.setScreenPos(view.mapFromScene(pressEvent.scenePos()));
    releaseEvent.setScenePos(green->mapToScene(5, 5));
    releaseEvent.setScreenPos(view.mapFromScene(releaseEvent.scenePos()));
    QCoreApplication::sendEvent(&scene, &pressEvent);
    QCoreApplication::sendEvent(&scene, &releaseEvent);

    QCOMPARE(blue->counter, 6);
    QCOMPARE(red->counter, 4);
    QCOMPARE(green->counter, 4);
}

void tst_QGraphicsItem::handlesChildEvents2()
{
    ChildEventTester *root = new ChildEventTester(QRectF(0, 0, 10, 10));
    root->setHandlesChildEvents(true);
    QVERIFY(root->handlesChildEvents());

    ChildEventTester *child = new ChildEventTester(QRectF(0, 0, 10, 10), root);
    QVERIFY(!child->handlesChildEvents());

    ChildEventTester *child2 = new ChildEventTester(QRectF(0, 0, 10, 10));
    ChildEventTester *child3 = new ChildEventTester(QRectF(0, 0, 10, 10), child2);
    ChildEventTester *child4 = new ChildEventTester(QRectF(0, 0, 10, 10), child3);
    child2->setParentItem(root);
    QVERIFY(!child2->handlesChildEvents());
    QVERIFY(!child3->handlesChildEvents());
    QVERIFY(!child4->handlesChildEvents());

    QGraphicsScene scene;
    scene.addItem(root);

    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));
    QApplication::processEvents();

    QMouseEvent event(QEvent::MouseButtonPress, view.mapFromScene(5, 5),
                      view.viewport()->mapToGlobal(view.mapFromScene(5, 5)), Qt::LeftButton, {}, {});
    QCoreApplication::sendEvent(view.viewport(), &event);

    QTRY_COMPARE(root->counter, 1);
}

void tst_QGraphicsItem::handlesChildEvents3()
{
    QGraphicsScene scene;
    QEvent activate(QEvent::WindowActivate);
    QCoreApplication::sendEvent(&scene, &activate);

    ChildEventTester *group2 = new ChildEventTester(QRectF(), nullptr);
    ChildEventTester *group1 = new ChildEventTester(QRectF(), group2);
    ChildEventTester *leaf = new ChildEventTester(QRectF(), group1);
    scene.addItem(group2);

    leaf->setFlag(QGraphicsItem::ItemIsFocusable);
    group1->setFlag(QGraphicsItem::ItemIsFocusable);
    group1->setHandlesChildEvents(true);
    group2->setFlag(QGraphicsItem::ItemIsFocusable);
    group2->setHandlesChildEvents(true);

    leaf->setFocus();
    QVERIFY(leaf->hasFocus()); // group2 stole the event, but leaf still got focus
    QVERIFY(!group1->hasFocus());
    QVERIFY(!group2->hasFocus());
    QCOMPARE(leaf->counter, 0);
    QCOMPARE(group1->counter, 0);
    QCOMPARE(group2->counter, 1);

    group1->setFocus();
    QVERIFY(group1->hasFocus()); // group2 stole the event, but group1 still got focus
    QVERIFY(!leaf->hasFocus());
    QVERIFY(!group2->hasFocus());
    QCOMPARE(leaf->counter, 0);
    QCOMPARE(group1->counter, 0);
    QCOMPARE(group2->counter, 2);

    group2->setFocus();
    QVERIFY(group2->hasFocus()); // group2 stole the event, and now group2 also has focus
    QVERIFY(!leaf->hasFocus());
    QVERIFY(!group1->hasFocus());
    QCOMPARE(leaf->counter, 0);
    QCOMPARE(group1->counter, 0);
    QCOMPARE(group2->counter, 3);
}


class ChildEventFilterTester : public ChildEventTester
{
public:
    ChildEventFilterTester(const QRectF &rect, QGraphicsItem *parent = nullptr)
        : ChildEventTester(rect, parent)
    { }

    QEvent::Type filter = QEvent::None;

protected:
    bool sceneEventFilter(QGraphicsItem *item, QEvent *event) override
    {
        Q_UNUSED(item);
        if (event->type() == filter) {
            ++counter;
            return true;
        }
        return false;
    }
};

void tst_QGraphicsItem::filtersChildEvents()
{
    QGraphicsScene scene;
    ChildEventFilterTester *root = new ChildEventFilterTester(QRectF(0, 0, 10, 10));
    ChildEventFilterTester *filter = new ChildEventFilterTester(QRectF(10, 10, 10, 10), root);
    ChildEventTester *child = new ChildEventTester(QRectF(20, 20, 10, 10), filter);

    // setup filter
    filter->setFiltersChildEvents(true);
    filter->filter = QEvent::GraphicsSceneMousePress;

    scene.addItem(root);

    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));

    QGraphicsSceneMouseEvent pressEvent(QEvent::GraphicsSceneMousePress);
    QGraphicsSceneMouseEvent releaseEvent(QEvent::GraphicsSceneMouseRelease);

    // send event to child
    pressEvent.setButton(Qt::LeftButton);
    pressEvent.setScenePos(QPointF(25, 25));//child->mapToScene(5, 5));
    pressEvent.setScreenPos(view.mapFromScene(pressEvent.scenePos()));
    releaseEvent.setButton(Qt::LeftButton);
    releaseEvent.setScenePos(QPointF(25, 25));//child->mapToScene(5, 5));
    releaseEvent.setScreenPos(view.mapFromScene(pressEvent.scenePos()));
    QCoreApplication::sendEvent(&scene, &pressEvent);
    QCoreApplication::sendEvent(&scene, &releaseEvent);

    QTRY_COMPARE(child->counter, 1);  // mouse release is not filtered
    QCOMPARE(filter->counter, 1); // mouse press is filtered
    QCOMPARE(root->counter, 0);

    // add another filter
    root->setFiltersChildEvents(true);
    root->filter = QEvent::GraphicsSceneMouseRelease;

    // send event to child
    QCoreApplication::sendEvent(&scene, &pressEvent);
    QCoreApplication::sendEvent(&scene, &releaseEvent);

    QCOMPARE(child->counter, 1);
    QCOMPARE(filter->counter, 2); // mouse press is filtered
    QCOMPARE(root->counter, 1); // mouse release is filtered

    // reparent to another sub-graph
    ChildEventTester *parent = new ChildEventTester(QRectF(10, 10, 10, 10), root);
    child->setParentItem(parent);

    // send event to child
    QCoreApplication::sendEvent(&scene, &pressEvent);
    QCoreApplication::sendEvent(&scene, &releaseEvent);

    QCOMPARE(child->counter, 2); // mouse press is _not_ filtered
    QCOMPARE(parent->counter, 0);
    QCOMPARE(filter->counter, 2);
    QCOMPARE(root->counter, 2); // mouse release is filtered
}

void tst_QGraphicsItem::filtersChildEvents2()
{
    ChildEventFilterTester *root = new ChildEventFilterTester(QRectF(0, 0, 10, 10));
    root->setFiltersChildEvents(true);
    root->filter = QEvent::GraphicsSceneMousePress;
    QVERIFY(root->filtersChildEvents());

    ChildEventTester *child = new ChildEventTester(QRectF(0, 0, 10, 10), root);
    QVERIFY(!child->filtersChildEvents());

    ChildEventTester *child2 = new ChildEventTester(QRectF(0, 0, 10, 10));
    ChildEventTester *child3 = new ChildEventTester(QRectF(0, 0, 10, 10), child2);
    ChildEventTester *child4 = new ChildEventTester(QRectF(0, 0, 10, 10), child3);

    child2->setParentItem(root);
    QVERIFY(!child2->filtersChildEvents());
    QVERIFY(!child3->filtersChildEvents());
    QVERIFY(!child4->filtersChildEvents());

    QGraphicsScene scene;
    scene.addItem(root);

    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.show();

    QVERIFY(QTest::qWaitForWindowExposed(&view));
    QApplication::processEvents();

    QMouseEvent event(QEvent::MouseButtonPress, view.mapFromScene(5, 5),
                      view.viewport()->mapToGlobal(view.mapFromScene(5, 5)), Qt::LeftButton, {}, {});
    QCoreApplication::sendEvent(view.viewport(), &event);

    QTRY_COMPARE(root->counter, 1);
    QCOMPARE(child->counter, 0);
    QCOMPARE(child2->counter, 0);
    QCOMPARE(child3->counter, 0);
    QCOMPARE(child4->counter, 0);
}

class CustomItem : public QGraphicsItem
{
public:
    QRectF boundingRect() const override
    { return QRectF(-110, -110, 220, 220); }

    void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override
    {
        for (int x = -100; x <= 100; x += 25)
            painter->drawLine(x, -100, x, 100);
        for (int y = -100; y <= 100; y += 25)
            painter->drawLine(-100, y, 100, y);

        QFont font = painter->font();
        font.setPointSize(4);
        painter->setFont(font);
        for (int x = -100; x < 100; x += 25) {
            const QString prefix = QString::number(x) + QLatin1Char('x');
            for (int y = -100; y < 100; y += 25)
                painter->drawText(QRectF(x, y, 25, 25), Qt::AlignCenter, prefix + QString::number(y));
        }
    }
};

void tst_QGraphicsItem::ensureVisible()
{
    QGraphicsScene scene;
    scene.setSceneRect(-200, -200, 400, 400);
    QGraphicsItem *item = new CustomItem;
    scene.addItem(item);

    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.setFixedSize(300, 300);
    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));

    for (int i = 0; i < 25; ++i) {
        view.scale(qreal(1.06), qreal(1.06));
        QApplication::processEvents();
    }

    item->ensureVisible(-100, -100, 25, 25);

    for (int x = -100; x < 100; x += 25) {
        for (int y = -100; y < 100; y += 25) {
            int xmargin = QRandomGenerator::global()->bounded(75);
            int ymargin = QRandomGenerator::global()->bounded(75);
            item->ensureVisible(x, y, 25, 25, xmargin, ymargin);
            QApplication::processEvents();

            QPolygonF viewScenePoly;
            viewScenePoly << view.mapToScene(view.rect().topLeft())
                          << view.mapToScene(view.rect().topRight())
                          << view.mapToScene(view.rect().bottomRight())
                          << view.mapToScene(view.rect().bottomLeft());

            QVERIFY(scene.items(viewScenePoly).contains(item));

            QPainterPath path;
            path.addPolygon(viewScenePoly);
            QVERIFY(path.contains(item->mapToScene(x + 12, y + 12)));

            QPolygonF viewScenePolyMinusMargins;
            viewScenePolyMinusMargins << view.mapToScene(view.rect().topLeft() + QPoint(xmargin, ymargin))
                          << view.mapToScene(view.rect().topRight() + QPoint(-xmargin, ymargin))
                          << view.mapToScene(view.rect().bottomRight() + QPoint(-xmargin, -ymargin))
                          << view.mapToScene(view.rect().bottomLeft() + QPoint(xmargin, -ymargin));

            QPainterPath path2;
            path2.addPolygon(viewScenePolyMinusMargins);
            QVERIFY(path2.contains(item->mapToScene(x + 12, y + 12)));
        }
    }

    item->ensureVisible(100, 100, 25, 25);
    QTest::qWait(25);
}

#ifndef QT_NO_CURSOR
void tst_QGraphicsItem::cursor()
{
    QGraphicsScene scene;
    QGraphicsView view(&scene);
    view.showFullScreen();
    QVERIFY(QTest::qWaitForWindowExposed(&view));
    const Qt::CursorShape viewportShape = view.viewport()->cursor().shape();

    QGraphicsRectItem *item1 = scene.addRect(QRectF(-100, 0, 50, 50));
    QGraphicsRectItem *item2 = scene.addRect(QRectF(50, 0, 50, 50));

    QVERIFY(!item1->hasCursor());
    QVERIFY(!item2->hasCursor());

    item1->setCursor(Qt::IBeamCursor);
    item2->setCursor(Qt::PointingHandCursor);

    QVERIFY(item1->hasCursor());
    QVERIFY(item2->hasCursor());

    item1->setCursor(QCursor());
    item2->setCursor(QCursor());

    QVERIFY(item1->hasCursor());
    QVERIFY(item2->hasCursor());

    item1->unsetCursor();
    item2->unsetCursor();

    QVERIFY(!item1->hasCursor());
    QVERIFY(!item2->hasCursor());

    item1->setCursor(Qt::IBeamCursor);
    item2->setCursor(Qt::PointingHandCursor);

    QPoint viewCenter = view.rect().center();
    QPoint item1Center = view.mapFromScene(item1->sceneBoundingRect().center());
    QPoint item2Center = view.mapFromScene(item2->sceneBoundingRect().center());

    {
        QMouseEvent event(QEvent::MouseMove, viewCenter, view.viewport()->mapToGlobal(viewCenter), Qt::NoButton, {}, {});
        QCoreApplication::sendEvent(view.viewport(), &event);
    }

    QCOMPARE(view.viewport()->cursor().shape(), viewportShape);

    {
        QMouseEvent event(QEvent::MouseMove, item1Center, view.viewport()->mapToGlobal(item1Center), Qt::NoButton, {}, {});
        QCoreApplication::sendEvent(view.viewport(), &event);
    }

    QCOMPARE(view.viewport()->cursor().shape(), item1->cursor().shape());

    {
        QMouseEvent event(QEvent::MouseMove, item2Center, view.viewport()->mapToGlobal(item2Center), Qt::NoButton, {}, {});
        QCoreApplication::sendEvent(view.viewport(), &event);
    }

    QCOMPARE(view.viewport()->cursor().shape(), item2->cursor().shape());

    {
        QMouseEvent event(QEvent::MouseMove, viewCenter, view.viewport()->mapToGlobal(viewCenter), Qt::NoButton, {}, {});
        QCoreApplication::sendEvent(view.viewport(), &event);
    }

    QCOMPARE(view.viewport()->cursor().shape(), viewportShape);

    item1->setEnabled(false);
    {
        QMouseEvent event(QEvent::MouseMove, item1Center, view.viewport()->mapToGlobal(item1Center), Qt::NoButton, {}, {});
        QCoreApplication::sendEvent(view.viewport(), &event);
    }

    QCOMPARE(view.viewport()->cursor().shape(), viewportShape);
}
#endif
/*
void tst_QGraphicsItem::textControlGetterSetter()
{
    QGraphicsTextItem *item = new QGraphicsTextItem;
    QCOMPARE(item->textControl()->parent(), item);
    QPointer<QWidgetTextControl> control = item->textControl();
    delete item;
    QVERIFY(!control);

    item = new QGraphicsTextItem;

    QPointer<QWidgetTextControl> oldControl = control;
    control = new QWidgetTextControl;

    item->setTextControl(control);
    QCOMPARE(item->textControl(), control);
    QVERIFY(!control->parent());
    QVERIFY(!oldControl);

    // set some text to give it a size, to test that
    // setTextControl (re)connects signals
    const QRectF oldBoundingRect = item->boundingRect();
    QVERIFY(oldBoundingRect.isValid());
    item->setPlainText("Some text");
    item->adjustSize();
    QVERIFY(item->boundingRect().isValid());
    QVERIFY(item->boundingRect() != oldBoundingRect);

    // test that on setting a control the item size
    // is adjusted
    oldControl = control;
    control = new QWidgetTextControl;
    control->setPlainText("foo!");
    item->setTextControl(control);
    QCOMPARE(item->boundingRect().size(), control->document()->documentLayout()->documentSize());

    QVERIFY(oldControl);
    delete oldControl;

    delete item;
    QVERIFY(control);
    delete control;
}
*/

void tst_QGraphicsItem::defaultItemTest_QGraphicsLineItem()
{
    QGraphicsLineItem item;
    QCOMPARE(item.line(), QLineF());
    QCOMPARE(item.pen(), QPen());
    QCOMPARE(item.shape(), QPainterPath());

    item.setPen(QPen(Qt::black, 1));
    QCOMPARE(item.pen(), QPen(Qt::black, 1));
    item.setLine(QLineF(0, 0, 10, 0));
    QCOMPARE(item.line(), QLineF(0, 0, 10, 0));
    QCOMPARE(item.boundingRect(), QRectF(-0.5, -0.5, 11, 1));
    QCOMPARE(item.shape().elementCount(), 11);

    QPainterPath path;
    path.moveTo(0, -0.5);
    path.lineTo(10, -0.5);
    path.lineTo(10.5, -0.5);
    path.lineTo(10.5, 0.5);
    path.lineTo(10, 0.5);
    path.lineTo(0, 0.5);
    path.lineTo(-0.5, 0.5);
    path.lineTo(-0.5, -0.5);
    path.lineTo(0, -0.5);
    path.lineTo(0, 0);
    path.lineTo(10, 0);
    path.closeSubpath();

    for (int i = 0; i < 11; ++i)
        QCOMPARE(QPointF(item.shape().elementAt(i)), QPointF(path.elementAt(i)));
}

void tst_QGraphicsItem::defaultItemTest_QGraphicsPixmapItem()
{
    QGraphicsPixmapItem item;
    QVERIFY(item.pixmap().isNull());
    QCOMPARE(item.offset(), QPointF());
    QCOMPARE(item.transformationMode(), Qt::FastTransformation);

    QPixmap pixmap(300, 200);
    pixmap.fill(Qt::red);
    item.setPixmap(pixmap);
    QCOMPARE(item.pixmap(), pixmap);

    item.setTransformationMode(Qt::FastTransformation);
    QCOMPARE(item.transformationMode(), Qt::FastTransformation);
    item.setTransformationMode(Qt::SmoothTransformation);
    QCOMPARE(item.transformationMode(), Qt::SmoothTransformation);

    item.setOffset(-15, -15);
    QCOMPARE(item.offset(), QPointF(-15, -15));
    item.setOffset(QPointF(-10, -10));
    QCOMPARE(item.offset(), QPointF(-10, -10));

    QCOMPARE(item.boundingRect(), QRectF(-10, -10, 300, 200));
}

void tst_QGraphicsItem::defaultItemTest_QGraphicsTextItem()
{
    QGraphicsTextItem *text = new QGraphicsTextItem;
    QVERIFY(!text->openExternalLinks());
    QVERIFY(text->textCursor().isNull());
    QCOMPARE(text->defaultTextColor(), QPalette().color(QPalette::Text));
    QVERIFY(text->document() != nullptr);
    QCOMPARE(text->font(), QApplication::font());
    QCOMPARE(text->textInteractionFlags(), Qt::TextInteractionFlags(Qt::NoTextInteraction));
    QCOMPARE(text->textWidth(), -1.0);
    QCOMPARE(text->toPlainText(), QString(""));

    QGraphicsScene scene;
    scene.addItem(text);
    text->setPlainText("Hello world");
    text->setFlag(QGraphicsItem::ItemIsMovable);

    {
        QGraphicsSceneMouseEvent event(QEvent::GraphicsSceneMousePress);
        event.setScenePos(QPointF(1, 1));
        event.setButton(Qt::LeftButton);
        event.setButtons(Qt::LeftButton);
        QCoreApplication::sendEvent(&scene, &event);
        QGraphicsSceneMouseEvent event2(QEvent::GraphicsSceneMouseMove);
        event2.setScenePos(QPointF(11, 11));
        event2.setButton(Qt::LeftButton);
        event2.setButtons(Qt::LeftButton);
        QCoreApplication::sendEvent(&scene, &event2);
    }

    QCOMPARE(text->pos(), QPointF(10, 10));

    text->setTextInteractionFlags(Qt::NoTextInteraction);
    QVERIFY(!(text->flags() & QGraphicsItem::ItemAcceptsInputMethod));
    text->setTextInteractionFlags(Qt::TextEditorInteraction);
    QCOMPARE(text->textInteractionFlags(), Qt::TextInteractionFlags(Qt::TextEditorInteraction));
    QVERIFY(text->flags() & QGraphicsItem::ItemAcceptsInputMethod);

    {
        QGraphicsSceneMouseEvent event2(QEvent::GraphicsSceneMouseMove);
        event2.setScenePos(QPointF(21, 21));
        event2.setButton(Qt::LeftButton);
        event2.setButtons(Qt::LeftButton);
        QCoreApplication::sendEvent(&scene, &event2);
    }

    QCOMPARE(text->pos(), QPointF(20, 20)); // clicked on edge, item moved
}

void tst_QGraphicsItem::defaultItemTest_QGraphicsEllipseItem()
{
    QGraphicsEllipseItem item;
    item.setPen(QPen(Qt::black, 0));
    QVERIFY(item.rect().isNull());
    QVERIFY(item.boundingRect().isNull());
    QVERIFY(item.shape().isEmpty());
    QCOMPARE(item.spanAngle(), 360 * 16);
    QCOMPARE(item.startAngle(), 0);

    item.setRect(0, 0, 100, 100);
    QCOMPARE(item.boundingRect(), QRectF(0, 0, 100, 100));

    item.setSpanAngle(90 * 16);
    // for some reason, the bounding rect has very few significant digits
    // (i.e. it's likely that floats are being used inside it), so we
    // must force the conversion from qreals to float or these tests will fail
    QCOMPARE(float(item.boundingRect().left()), 50.0f);
    QVERIFY(qFuzzyIsNull(float(item.boundingRect().top())));
    QCOMPARE(float(item.boundingRect().width()), 50.0f);
    QCOMPARE(float(item.boundingRect().height()), 50.0f);

    item.setPen(QPen(Qt::black, 1));
    QCOMPARE(item.boundingRect(), QRectF(49.5, -0.5, 51, 51));

    item.setSpanAngle(180 * 16);
    QCOMPARE(item.boundingRect(), QRectF(-0.5, -0.5, 101, 51));

    item.setSpanAngle(360 * 16);
    QCOMPARE(item.boundingRect(), QRectF(-0.5, -0.5, 101, 101));
}

class ItemChangeTester : public QGraphicsRectItem
{
public:
    ItemChangeTester(QGraphicsItem *parent = nullptr) : QGraphicsRectItem(parent)
    {
        setFlag(ItemSendsGeometryChanges);
        clear();
    }

    void clear()
    {
        itemChangeReturnValue = QVariant();
        itemSceneChangeTargetScene = nullptr;
        changes.clear();
        values.clear();
        oldValues.clear();
    }

    QVariant itemChangeReturnValue;
    QGraphicsScene *itemSceneChangeTargetScene;

    QList<GraphicsItemChange> changes;
    QVariantList values;
    QVariantList oldValues;
protected:
    QVariant itemChange(GraphicsItemChange change, const QVariant &value) override
    {
        changes << change;
        values << value;
        switch (change) {
        case QGraphicsItem::ItemPositionChange:
            oldValues << pos();
            break;
        case QGraphicsItem::ItemPositionHasChanged:
            break;
        case QGraphicsItem::ItemTransformChange: {
            QVariant variant;
            variant.setValue(transform());
            oldValues << variant;
        }
            break;
        case QGraphicsItem::ItemTransformHasChanged:
            break;
        case QGraphicsItem::ItemVisibleChange:
            oldValues << isVisible();
            break;
        case QGraphicsItem::ItemVisibleHasChanged:
            break;
        case QGraphicsItem::ItemEnabledChange:
            oldValues << isEnabled();
            break;
        case QGraphicsItem::ItemEnabledHasChanged:
            break;
        case QGraphicsItem::ItemSelectedChange:
            oldValues << isSelected();
            break;
        case QGraphicsItem::ItemSelectedHasChanged:
            break;
        case QGraphicsItem::ItemParentChange:
            oldValues << QVariant::fromValue<void *>(parentItem());
            break;
        case QGraphicsItem::ItemParentHasChanged:
            break;
        case QGraphicsItem::ItemChildAddedChange:
            oldValues << childItems().size();
            break;
        case QGraphicsItem::ItemChildRemovedChange:
            oldValues << childItems().size();
            break;
        case QGraphicsItem::ItemSceneChange:
            oldValues << QVariant::fromValue<QGraphicsScene *>(scene());
            if (itemSceneChangeTargetScene
                && qvariant_cast<QGraphicsScene *>(value)
                && itemSceneChangeTargetScene != qvariant_cast<QGraphicsScene *>(value)) {
                return QVariant::fromValue<QGraphicsScene *>(itemSceneChangeTargetScene);
            }
            return value;
        case QGraphicsItem::ItemSceneHasChanged:
            break;
        case QGraphicsItem::ItemCursorChange:
#ifndef QT_NO_CURSOR
            oldValues << cursor();
#endif
            break;
        case QGraphicsItem::ItemCursorHasChanged:
            break;
        case QGraphicsItem::ItemToolTipChange:
            oldValues << toolTip();
            break;
        case QGraphicsItem::ItemToolTipHasChanged:
            break;
        case QGraphicsItem::ItemFlagsChange:
            oldValues << quint32(flags());
            break;
        case QGraphicsItem::ItemFlagsHaveChanged:
            break;
        case QGraphicsItem::ItemZValueChange:
            oldValues << zValue();
            break;
        case QGraphicsItem::ItemZValueHasChanged:
            break;
        case QGraphicsItem::ItemOpacityChange:
            oldValues << opacity();
            break;
        case QGraphicsItem::ItemOpacityHasChanged:
            break;
        case QGraphicsItem::ItemScenePositionHasChanged:
            break;
        case QGraphicsItem::ItemRotationChange:
            oldValues << rotation();
            break;
        case QGraphicsItem::ItemRotationHasChanged:
            break;
        case QGraphicsItem::ItemScaleChange:
            oldValues << scale();
            break;
        case QGraphicsItem::ItemScaleHasChanged:
            break;
        case QGraphicsItem::ItemTransformOriginPointChange:
            oldValues << transformOriginPoint();
            break;
        case QGraphicsItem::ItemTransformOriginPointHasChanged:
            break;
        }
        return itemChangeReturnValue.isValid() ? itemChangeReturnValue : value;
    }
};

void tst_QGraphicsItem::itemChange()
{
    ItemChangeTester tester;
    tester.itemSceneChangeTargetScene = nullptr;

    ItemChangeTester testerHelper;
    QVERIFY(tester.changes.isEmpty());
    QVERIFY(tester.values.isEmpty());

    int changeCount = 0;
    {
        // ItemEnabledChange
        tester.itemChangeReturnValue = true;
        tester.setEnabled(false);
        ++changeCount;
        ++changeCount; // HasChanged
        QCOMPARE(tester.changes.size(), changeCount);
        QCOMPARE(tester.changes.at(tester.changes.size() - 2), QGraphicsItem::ItemEnabledChange);
        QCOMPARE(tester.changes.at(tester.changes.size() - 1), QGraphicsItem::ItemEnabledHasChanged);
        QCOMPARE(tester.values.at(tester.values.size() - 2), QVariant(false));
        QCOMPARE(tester.values.at(tester.values.size() - 1), QVariant(true));
        QCOMPARE(tester.oldValues.constLast(), QVariant(true));
        QCOMPARE(tester.isEnabled(), true);
    }
    {
        // ItemTransformChange / ItemTransformHasChanged
        tester.itemChangeReturnValue.setValue(QTransform().rotate(90));
        tester.setTransform(QTransform::fromTranslate(50, 0), true);
        ++changeCount; // notification sent too
        ++changeCount;
        QCOMPARE(tester.changes.size(), changeCount);
        QCOMPARE(tester.changes.at(tester.changes.size() - 2), QGraphicsItem::ItemTransformChange);
        QCOMPARE(tester.changes.at(tester.changes.size() - 1), QGraphicsItem::ItemTransformHasChanged);
        QCOMPARE(qvariant_cast<QTransform>(tester.values.at(tester.values.size() - 2)),
                 QTransform().translate(50, 0));
        QCOMPARE(qvariant_cast<QTransform>(tester.values.at(tester.values.size() - 1)),
                 QTransform().rotate(90));
        QVariant variant;
        variant.setValue(QTransform());
        QCOMPARE(tester.oldValues.constLast(), variant);
        QCOMPARE(tester.transform(), QTransform().rotate(90));
    }
    {
        // ItemPositionChange / ItemPositionHasChanged
        tester.itemChangeReturnValue = QPointF(42, 0);
        tester.setPos(0, 42);
        ++changeCount; // notification sent too
        ++changeCount;
        QCOMPARE(tester.changes.size(), changeCount);
        QCOMPARE(tester.changes.at(tester.changes.size() - 2), QGraphicsItem::ItemPositionChange);
        QCOMPARE(tester.changes.at(tester.changes.size() - 1), QGraphicsItem::ItemPositionHasChanged);
        QCOMPARE(tester.values.at(tester.changes.size() - 2), QVariant(QPointF(0, 42)));
        QCOMPARE(tester.values.at(tester.changes.size() - 1), QVariant(QPointF(42, 0)));
        QCOMPARE(tester.oldValues.constLast(), QVariant(QPointF()));
        QCOMPARE(tester.pos(), QPointF(42, 0));
    }
    {
        // ItemZValueChange / ItemZValueHasChanged
        tester.itemChangeReturnValue = qreal(2.0);
        tester.setZValue(1.0);
        ++changeCount; // notification sent too
        ++changeCount;
        QCOMPARE(tester.changes.size(), changeCount);
        QCOMPARE(tester.changes.at(tester.changes.size() - 2), QGraphicsItem::ItemZValueChange);
        QCOMPARE(tester.changes.at(tester.changes.size() - 1), QGraphicsItem::ItemZValueHasChanged);
        QCOMPARE(tester.values.at(tester.changes.size() - 2), QVariant(qreal(1.0)));
        QCOMPARE(tester.values.at(tester.changes.size() - 1), QVariant(qreal(2.0)));
        QCOMPARE(tester.oldValues.constLast(), QVariant(qreal(0.0)));
        QCOMPARE(tester.zValue(), qreal(2.0));
    }
    {
        // ItemRotationChange / ItemRotationHasChanged
        tester.itemChangeReturnValue = qreal(15.0);
        tester.setRotation(10.0);
        ++changeCount; // notification sent too
        ++changeCount;
        QCOMPARE(tester.changes.size(), changeCount);
        QCOMPARE(tester.changes.at(tester.changes.size() - 2), QGraphicsItem::ItemRotationChange);
        QCOMPARE(tester.changes.at(tester.changes.size() - 1), QGraphicsItem::ItemRotationHasChanged);
        QCOMPARE(tester.values.at(tester.changes.size() - 2), QVariant(qreal(10.0)));
        QCOMPARE(tester.values.at(tester.changes.size() - 1), QVariant(qreal(15.0)));
        QCOMPARE(tester.oldValues.constLast(), QVariant(qreal(0.0)));
        QCOMPARE(tester.rotation(), qreal(15.0));
    }
    {
        // ItemScaleChange / ItemScaleHasChanged
        tester.itemChangeReturnValue = qreal(2.0);
        tester.setScale(1.5);
        ++changeCount; // notification sent too
        ++changeCount;
        QCOMPARE(tester.changes.size(), changeCount);
        QCOMPARE(tester.changes.at(tester.changes.size() - 2), QGraphicsItem::ItemScaleChange);
        QCOMPARE(tester.changes.at(tester.changes.size() - 1), QGraphicsItem::ItemScaleHasChanged);
        QCOMPARE(tester.values.at(tester.changes.size() - 2), QVariant(qreal(1.5)));
        QCOMPARE(tester.values.at(tester.changes.size() - 1), QVariant(qreal(2.0)));
        QCOMPARE(tester.oldValues.constLast(), QVariant(qreal(1.0)));
        QCOMPARE(tester.scale(), qreal(2.0));
    }
    {
        // ItemTransformOriginPointChange / ItemTransformOriginPointHasChanged
        tester.itemChangeReturnValue = QPointF(2.0, 2.0);
        tester.setTransformOriginPoint(1.0, 1.0);
        ++changeCount; // notification sent too
        ++changeCount;
        QCOMPARE(tester.changes.size(), changeCount);
        QCOMPARE(tester.changes.at(tester.changes.size() - 2), QGraphicsItem::ItemTransformOriginPointChange);
        QCOMPARE(tester.changes.at(tester.changes.size() - 1), QGraphicsItem::ItemTransformOriginPointHasChanged);
        QCOMPARE(tester.values.at(tester.changes.size() - 2), QVariant(QPointF(1.0, 1.0)));
        QCOMPARE(tester.values.at(tester.changes.size() - 1), QVariant(QPointF(2.0, 2.0)));
        QCOMPARE(tester.oldValues.constLast(), QVariant(QPointF(0.0, 0.0)));
        QCOMPARE(tester.transformOriginPoint(), QPointF(2.0, 2.0));
    }
    {
        // ItemFlagsChange
        tester.itemChangeReturnValue = QGraphicsItem::ItemIsSelectable;
        tester.setFlag(QGraphicsItem::ItemIsSelectable, false);
        QCOMPARE(tester.changes.size(), changeCount);  // No change
        tester.setFlag(QGraphicsItem::ItemIsSelectable, true);
        ++changeCount;
        ++changeCount; // ItemFlagsHasChanged
        QCOMPARE(tester.changes.size(), changeCount);
        QCOMPARE(tester.changes.at(tester.changes.size() - 2), QGraphicsItem::ItemFlagsChange);
        QCOMPARE(tester.changes.at(tester.changes.size() - 1), QGraphicsItem::ItemFlagsHaveChanged);
        QVariant expectedFlags = QVariant::fromValue<quint32>(QGraphicsItem::GraphicsItemFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemSendsGeometryChanges));
        QCOMPARE(tester.values.at(tester.values.size() - 2), expectedFlags);
        QCOMPARE(tester.values.at(tester.values.size() - 1),
                 QVariant::fromValue<quint32>(quint32(QGraphicsItem::ItemIsSelectable)));
    }
    {
        // ItemSelectedChange
        tester.setSelected(false);
        QCOMPARE(tester.changes.size(), changeCount); // No change :-)
        tester.itemChangeReturnValue = true;
        tester.setSelected(true);
        ++changeCount;
        ++changeCount; // ItemSelectedHasChanged
        QCOMPARE(tester.changes.size(), changeCount);
        QCOMPARE(tester.changes.at(tester.changes.size() - 2), QGraphicsItem::ItemSelectedChange);
        QCOMPARE(tester.changes.at(tester.changes.size() - 1), QGraphicsItem::ItemSelectedHasChanged);
        QCOMPARE(tester.values.at(tester.values.size() - 2), QVariant(true));
        QCOMPARE(tester.values.at(tester.values.size() - 1), QVariant(true));
        QCOMPARE(tester.oldValues.constLast(), QVariant(false));
        QCOMPARE(tester.isSelected(), true);

        tester.itemChangeReturnValue = false;
        tester.setSelected(true);

        // the value hasn't changed to the itemChange return value
        // bacause itemChange is never called (true -> true is a noop).
        QCOMPARE(tester.isSelected(), true);
    }
    {
        // ItemVisibleChange
        tester.itemChangeReturnValue = false;
        QVERIFY(tester.isVisible());
        tester.setVisible(false);
        ++changeCount; // ItemVisibleChange
        ++changeCount; // ItemSelectedChange
        ++changeCount; // ItemSelectedHasChanged
        ++changeCount; // ItemVisibleHasChanged
        QCOMPARE(tester.changes.size(), changeCount);
        QCOMPARE(tester.changes.at(tester.changes.size() - 4), QGraphicsItem::ItemVisibleChange);
        QCOMPARE(tester.changes.at(tester.changes.size() - 3), QGraphicsItem::ItemSelectedChange);
        QCOMPARE(tester.changes.at(tester.changes.size() - 2), QGraphicsItem::ItemSelectedHasChanged);
        QCOMPARE(tester.changes.at(tester.changes.size() - 1), QGraphicsItem::ItemVisibleHasChanged);
        QCOMPARE(tester.values.at(tester.values.size() - 4), QVariant(false));
        QCOMPARE(tester.values.at(tester.values.size() - 3), QVariant(false));
        QCOMPARE(tester.values.at(tester.values.size() - 2), QVariant(false));
        QCOMPARE(tester.values.at(tester.values.size() - 1), QVariant(false));
        QCOMPARE(tester.isVisible(), false);
    }
    {
        // ItemParentChange
        tester.itemChangeReturnValue.setValue(static_cast<QGraphicsItem *>(nullptr));
        tester.setParentItem(&testerHelper);
        QCOMPARE(tester.changes.size(), ++changeCount);
        QCOMPARE(tester.changes.constLast(), QGraphicsItem::ItemParentChange);
        QCOMPARE(qvariant_cast<QGraphicsItem *>(tester.values.last()), &testerHelper);
        QCOMPARE(qvariant_cast<QGraphicsItem *>(tester.oldValues.last()), nullptr);
        QCOMPARE(tester.parentItem(), nullptr);
    }
    {
        // ItemOpacityChange
        tester.itemChangeReturnValue = 1.0;
        tester.setOpacity(0.7);
        QCOMPARE(tester.changes.size(), ++changeCount);
        QCOMPARE(tester.changes.constLast(), QGraphicsItem::ItemOpacityChange);
        QVERIFY(qFuzzyCompare(qreal(tester.values.last().toDouble()), qreal(0.7)));
        QCOMPARE(tester.oldValues.last().toDouble(), double(1.0));
        QCOMPARE(tester.opacity(), qreal(1.0));
        tester.itemChangeReturnValue = 0.7;
        tester.setOpacity(0.7);
        ++changeCount; // ItemOpacityChange
        ++changeCount; // ItemOpacityHasChanged
        QCOMPARE(tester.changes.size(), changeCount);
        QCOMPARE(tester.changes.at(tester.changes.size() - 2), QGraphicsItem::ItemOpacityChange);
        QCOMPARE(tester.changes.at(tester.changes.size() - 1), QGraphicsItem::ItemOpacityHasChanged);
        QCOMPARE(tester.opacity(), qreal(0.7));
    }
    {
        // ItemChildAddedChange
        tester.itemChangeReturnValue.clear();
        testerHelper.setParentItem(&tester);
        QCOMPARE(tester.changes.size(), ++changeCount);
        QCOMPARE(tester.changes.constLast(), QGraphicsItem::ItemChildAddedChange);
        QCOMPARE(qvariant_cast<QGraphicsItem *>(tester.values.last()), &testerHelper);
    }
    {
        // ItemChildRemovedChange 1
        testerHelper.setParentItem(nullptr);
        QCOMPARE(tester.changes.size(), ++changeCount);
        QCOMPARE(tester.changes.constLast(), QGraphicsItem::ItemChildRemovedChange);
        QCOMPARE(qvariant_cast<QGraphicsItem *>(tester.values.last()), &testerHelper);

        // ItemChildRemovedChange 1
        ItemChangeTester *test = new ItemChangeTester;
        test->itemSceneChangeTargetScene = nullptr;
        int count = 0;
        QGraphicsScene *scene = new QGraphicsScene;
        scene->addItem(test);
        count = test->changes.size();
        //We test here the fact that when a child is deleted the parent receive only one ItemChildRemovedChange
        QGraphicsRectItem *child = new QGraphicsRectItem(test);
        //We received ItemChildAddedChange
        QCOMPARE(test->changes.size(), ++count);
        QCOMPARE(test->changes.constLast(), QGraphicsItem::ItemChildAddedChange);
        delete child;
        child = nullptr;
        QCOMPARE(test->changes.size(), ++count);
        QCOMPARE(test->changes.constLast(), QGraphicsItem::ItemChildRemovedChange);

        ItemChangeTester *childTester = new ItemChangeTester(test);
        //Changes contains all sceneHasChanged and so on, we don't want to test that
        int childCount = childTester->changes.size();
        //We received ItemChildAddedChange
        QCOMPARE(test->changes.size(), ++count);
        child = new QGraphicsRectItem(childTester);
        //We received ItemChildAddedChange
        QCOMPARE(childTester->changes.size(), ++childCount);
        QCOMPARE(childTester->changes.constLast(), QGraphicsItem::ItemChildAddedChange);
        //Delete the child of the top level with all its children
        delete childTester;
        //Only one removal
        QCOMPARE(test->changes.size(), ++count);
        QCOMPARE(test->changes.constLast(), QGraphicsItem::ItemChildRemovedChange);
        delete scene;
    }
    {
        // ItemChildRemovedChange 2
        ItemChangeTester parent;
        ItemChangeTester *child = new ItemChangeTester;
        child->setParentItem(&parent);
        QCOMPARE(parent.changes.constLast(), QGraphicsItem::ItemChildAddedChange);
        QCOMPARE(qvariant_cast<QGraphicsItem *>(parent.values.last()), child);
        delete child;
        QCOMPARE(parent.changes.constLast(), QGraphicsItem::ItemChildRemovedChange);
        QCOMPARE(qvariant_cast<QGraphicsItem *>(parent.values.last()), child);
    }
    {
        // !!! Note: If this test crashes because of double-deletion, there's
        // a bug somewhere in QGraphicsScene or QGraphicsItem.

        // ItemSceneChange
        QGraphicsScene scene;
        QGraphicsScene scene2;
        scene.addItem(&tester);
        ++changeCount; // ItemSceneChange (scene)
        ++changeCount; // ItemSceneHasChanged (scene)
        QCOMPARE(tester.changes.size(), changeCount);

        QCOMPARE(tester.scene(), &scene);
        QCOMPARE(tester.changes.at(tester.changes.size() - 2), QGraphicsItem::ItemSceneChange);
        QCOMPARE(tester.changes.at(tester.changes.size() - 1), QGraphicsItem::ItemSceneHasChanged);
        // Item's old value was 0
        // Item's current value is scene
        QCOMPARE(qvariant_cast<QGraphicsScene *>(tester.oldValues.last()), nullptr);
        QCOMPARE(qvariant_cast<QGraphicsScene *>(tester.values.last()), &scene);
        scene2.addItem(&tester);
        ++changeCount; // ItemSceneChange (0) was: (scene)
        ++changeCount; // ItemSceneHasChanged (0)
        ++changeCount; // ItemSceneChange (scene2) was: (0)
        ++changeCount; // ItemSceneHasChanged (scene2)
        QCOMPARE(tester.changes.size(), changeCount);

        QCOMPARE(tester.scene(), &scene2);
        QCOMPARE(tester.changes.at(tester.changes.size() - 4), QGraphicsItem::ItemSceneChange);
        QCOMPARE(tester.changes.at(tester.changes.size() - 3), QGraphicsItem::ItemSceneHasChanged);
        QCOMPARE(tester.changes.at(tester.changes.size() - 2), QGraphicsItem::ItemSceneChange);
        QCOMPARE(tester.changes.at(tester.changes.size() - 1), QGraphicsItem::ItemSceneHasChanged);
        // Item's last old value was scene
        // Item's last current value is 0

        QCOMPARE(qvariant_cast<QGraphicsScene *>(tester.oldValues.at(tester.oldValues.size() - 2)), &scene);
        QCOMPARE(qvariant_cast<QGraphicsScene *>(tester.oldValues.at(tester.oldValues.size() - 1)), nullptr);
        QCOMPARE(qvariant_cast<QGraphicsScene *>(tester.values.at(tester.values.size() - 4)), nullptr);
        QCOMPARE(qvariant_cast<QGraphicsScene *>(tester.values.at(tester.values.size() - 3)), nullptr);
        QCOMPARE(qvariant_cast<QGraphicsScene *>(tester.values.at(tester.values.size() - 2)), &scene2);
        QCOMPARE(qvariant_cast<QGraphicsScene *>(tester.values.at(tester.values.size() - 1)), &scene2);
        // Item's last old value was 0
        // Item's last current value is scene2
        QCOMPARE(qvariant_cast<QGraphicsScene *>(tester.oldValues.last()), nullptr);
        QCOMPARE(qvariant_cast<QGraphicsScene *>(tester.values.last()), &scene2);

        scene2.removeItem(&tester);
        ++changeCount; // ItemSceneChange (0) was: (scene2)
        ++changeCount; // ItemSceneHasChanged (0)
        QCOMPARE(tester.changes.size(), changeCount);

        QCOMPARE(tester.scene(), nullptr);
        QCOMPARE(tester.changes.at(tester.changes.size() - 2), QGraphicsItem::ItemSceneChange);
        QCOMPARE(tester.changes.at(tester.changes.size() - 1), QGraphicsItem::ItemSceneHasChanged);
        // Item's last old value was scene2
        // Item's last current value is 0
        QCOMPARE(qvariant_cast<QGraphicsScene *>(tester.oldValues.last()), &scene2);
        QCOMPARE(qvariant_cast<QGraphicsScene *>(tester.values.at(tester.values.size() - 2)), nullptr);
        QCOMPARE(qvariant_cast<QGraphicsScene *>(tester.values.at(tester.values.size() - 1)), nullptr);

        tester.itemSceneChangeTargetScene = &scene;
        scene2.addItem(&tester);
        ++changeCount; // ItemSceneChange (scene2) was: (0)
        ++changeCount; // ItemSceneChange (scene) was: (0)
        ++changeCount; // ItemSceneHasChanged (scene)
        QCOMPARE(tester.values.size(), changeCount);

        QCOMPARE(qvariant_cast<QGraphicsScene *>(tester.values.at(tester.values.size() - 3)), &scene2);
        QCOMPARE(qvariant_cast<QGraphicsScene *>(tester.values.at(tester.values.size() - 2)), &scene);
        QCOMPARE(qvariant_cast<QGraphicsScene *>(tester.values.at(tester.values.size() - 1)), &scene);

        QCOMPARE(tester.scene(), &scene);
        tester.itemSceneChangeTargetScene = nullptr;
        tester.itemChangeReturnValue = QVariant();
        scene.removeItem(&tester);
        ++changeCount; // ItemSceneChange
        ++changeCount; // ItemSceneHasChanged
        QCOMPARE(tester.scene(), nullptr);
    }
    {
        // ItemToolTipChange/ItemToolTipHasChanged
        const QString toolTip(QLatin1String("I'm soo cool"));
        const QString overridenToolTip(QLatin1String("No, you are not soo cool"));
        tester.itemChangeReturnValue = overridenToolTip;
        tester.setToolTip(toolTip);
        ++changeCount; // ItemToolTipChange
        ++changeCount; // ItemToolTipHasChanged
        QCOMPARE(tester.changes.size(), changeCount);
        QCOMPARE(tester.changes.at(changeCount - 2), QGraphicsItem::ItemToolTipChange);
        QCOMPARE(tester.values.at(changeCount - 2).toString(), toolTip);
        QCOMPARE(tester.changes.at(changeCount - 1), QGraphicsItem::ItemToolTipHasChanged);
        QCOMPARE(tester.values.at(changeCount - 1).toString(), overridenToolTip);
        QCOMPARE(tester.toolTip(), overridenToolTip);
        tester.itemChangeReturnValue = QVariant();
    }
}

class EventFilterTesterItem : public QGraphicsLineItem
{
public:
    QList<QEvent::Type> filteredEvents;
    GraphicsItems filteredEventReceivers;
    QList<QEvent::Type> receivedEvents;
    bool handlesSceneEvents = false;

protected:
    bool sceneEventFilter(QGraphicsItem *watched, QEvent *event) override
    {
        filteredEvents << event->type();
        filteredEventReceivers << watched;
        return handlesSceneEvents;
    }

    bool sceneEvent(QEvent *event) override
    {
        return QGraphicsLineItem::sceneEvent(event);
    }
};

void tst_QGraphicsItem::sceneEventFilter()
{
    if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation))
        QSKIP("Window activation is not supported");

    QGraphicsScene scene;

    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.show();
    QApplicationPrivate::setActiveWindow(&view);
    QVERIFY(QTest::qWaitForWindowExposed(&view));
    QVERIFY(QTest::qWaitForWindowActive(&view));

    QGraphicsTextItem *text1 = scene.addText(QLatin1String("Text1"));
    QGraphicsTextItem *text2 = scene.addText(QLatin1String("Text2"));
    QGraphicsTextItem *text3 = scene.addText(QLatin1String("Text3"));
    text1->setFlag(QGraphicsItem::ItemIsFocusable);
    text2->setFlag(QGraphicsItem::ItemIsFocusable);
    text3->setFlag(QGraphicsItem::ItemIsFocusable);

    EventFilterTesterItem *tester = new EventFilterTesterItem;
    scene.addItem(tester);

    QTRY_VERIFY(!text1->hasFocus());
    text1->installSceneEventFilter(tester);
    text1->setFocus();
    QTRY_VERIFY(text1->hasFocus());

    QCOMPARE(tester->filteredEvents.size(), 1);
    QCOMPARE(tester->filteredEvents.at(0), QEvent::FocusIn);
    QCOMPARE(tester->filteredEventReceivers.at(0), static_cast<QGraphicsItem *>(text1));

    text2->installSceneEventFilter(tester);
    text3->installSceneEventFilter(tester);

    text2->setFocus();
    text3->setFocus();

    QCOMPARE(tester->filteredEvents.size(), 5);
    QCOMPARE(tester->filteredEvents.at(1), QEvent::FocusOut);
    QCOMPARE(tester->filteredEventReceivers.at(1), static_cast<QGraphicsItem *>(text1));
    QCOMPARE(tester->filteredEvents.at(2), QEvent::FocusIn);
    QCOMPARE(tester->filteredEventReceivers.at(2), static_cast<QGraphicsItem *>(text2));
    QCOMPARE(tester->filteredEvents.at(3), QEvent::FocusOut);
    QCOMPARE(tester->filteredEventReceivers.at(3), static_cast<QGraphicsItem *>(text2));
    QCOMPARE(tester->filteredEvents.at(4), QEvent::FocusIn);
    QCOMPARE(tester->filteredEventReceivers.at(4), static_cast<QGraphicsItem *>(text3));

    text1->removeSceneEventFilter(tester);
    text1->setFocus();

    QCOMPARE(tester->filteredEvents.size(), 6);
    QCOMPARE(tester->filteredEvents.at(5), QEvent::FocusOut);
    QCOMPARE(tester->filteredEventReceivers.at(5), static_cast<QGraphicsItem *>(text3));

    tester->handlesSceneEvents = true;
    text2->setFocus();

    QCOMPARE(tester->filteredEvents.size(), 7);
    QCOMPARE(tester->filteredEvents.at(6), QEvent::FocusIn);
    QCOMPARE(tester->filteredEventReceivers.at(6), static_cast<QGraphicsItem *>(text2));

    QVERIFY(text2->hasFocus());

    //Let check if the items are correctly removed from the sceneEventFilters array
    //to avoid stale pointers.
    QGraphicsView gv;
    QGraphicsScene *anotherScene = new QGraphicsScene;
    QGraphicsTextItem *ti = anotherScene->addText("This is a test #1");
    ti->moveBy(50, 50);
    QGraphicsTextItem *ti2 = anotherScene->addText("This is a test #2");
    QGraphicsTextItem *ti3 = anotherScene->addText("This is a test #3");
    gv.setScene(anotherScene);
    gv.show();
    QVERIFY(QTest::qWaitForWindowExposed(&gv));
    ti->installSceneEventFilter(ti2);
    ti3->installSceneEventFilter(ti);
    delete ti2;
    //we souldn't crash
    QTest::mouseMove(gv.viewport(), gv.mapFromScene(ti->scenePos()));
    QTest::qWait(30);
    delete ti;
}

class GeometryChanger : public QGraphicsRectItem
{
public:
    explicit GeometryChanger(QRectF r) : QGraphicsRectItem(r) {}
    void changeGeometry()
    { prepareGeometryChange(); }
};

void tst_QGraphicsItem::prepareGeometryChange()
{
    {
        QGraphicsScene scene;
        const QRectF rect(0, 0, 100, 100);
        GeometryChanger item(rect);
        scene.addItem(&item);
        QVERIFY(scene.items(rect).contains(&item));
        item.changeGeometry();
        QVERIFY(scene.items(rect).contains(&item));
    }
}


class PaintTester : public QGraphicsRectItem
{
public:
    PaintTester() { setRect(QRectF(10, 10, 20, 20));}

    void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *w) override
    {
        widget = w;
        painted++;
    }

    QWidget *widget = nullptr;
    int painted = 0;
};

void tst_QGraphicsItem::paint()
{
    QGraphicsScene scene;

    PaintTester paintTester;
    scene.addItem(&paintTester);

    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));
    QApplication::processEvents();
#ifdef Q_OS_WIN32
    //we try to switch the desktop: if it fails, we skip the test
    if (::SwitchDesktop( ::GetThreadDesktop( ::GetCurrentThreadId() ) ) == 0) {
        QSKIP("The Graphics View doesn't get the paint events");
    }
#endif

    QTRY_COMPARE(paintTester.widget, view.viewport());

    view.hide();

    QGraphicsScene scene2;
    QGraphicsView view2(&scene2);
    view2.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view2.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view2));
    QCoreApplication::processEvents(); // Process all queued paint events

    PaintTester tester2;
    scene2.addItem(&tester2);

    //First show at least one paint
    QCOMPARE(tester2.painted, 0);
    QTRY_VERIFY(tester2.painted > 0);
    int painted = tester2.painted;

    //nominal case, update call paint
    tester2.update();
    QTRY_COMPARE(tester2.painted, painted + 1);
    painted = tester2.painted;

    //we remove the item from the scene, number of updates is still the same
    tester2.update();
    scene2.removeItem(&tester2);
    QTRY_COMPARE(tester2.painted, painted);

    //We re-add the item, the number of paint should increase
    scene2.addItem(&tester2);
    tester2.update();
    QTRY_COMPARE(tester2.painted, painted + 1);
}

class HarakiriItem : public QGraphicsRectItem
{
public:
    HarakiriItem(int harakiriPoint)
        : QGraphicsRectItem(QRectF(0, 0, 100, 100)), harakiri(harakiriPoint)
    { dead = false; }

    static bool dead;

    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override
    {
        QGraphicsRectItem::paint(painter, option, widget);
        if (harakiri == 0) {
            // delete unsupported since 4.5
            /*
            dead = true;
            delete this;
            */
        }
    }

    void advance(int n) override
    {
        if (harakiri == 1 && n == 0) {
            // delete unsupported
            /*
            dead = true;
            delete this;
            */
        }
        if (harakiri == 2 && n == 1) {
            dead = true;
            delete this;
        }
    }

protected:
#ifndef QT_NO_CONTEXTMENU
    void contextMenuEvent(QGraphicsSceneContextMenuEvent *) override
    {
        if (harakiri == 3) {
            dead = true;
            delete this;
        }
    }
#endif // QT_NO_CONTEXTMENU

    void dragEnterEvent(QGraphicsSceneDragDropEvent *event) override
    {
        // ??
        QGraphicsRectItem::dragEnterEvent(event);
    }

    void dragLeaveEvent(QGraphicsSceneDragDropEvent *event) override
    {
        // ??
        QGraphicsRectItem::dragLeaveEvent(event);
    }

    void dragMoveEvent(QGraphicsSceneDragDropEvent *event) override
    {
        // ??
        QGraphicsRectItem::dragMoveEvent(event);
    }

    void dropEvent(QGraphicsSceneDragDropEvent *event) override
    {
        // ??
        QGraphicsRectItem::dropEvent(event);
    }

    void focusInEvent(QFocusEvent *) override
    {
        if (harakiri == 4) {
            dead = true;
            delete this;
        }
    }

    void focusOutEvent(QFocusEvent *) override
    {
        if (harakiri == 5) {
            dead = true;
            delete this;
        }
    }

    void hoverEnterEvent(QGraphicsSceneHoverEvent *) override
    {
        if (harakiri == 6) {
            dead = true;
            delete this;
        }
    }

    void hoverLeaveEvent(QGraphicsSceneHoverEvent *) override
    {
        if (harakiri == 7) {
            dead = true;
            delete this;
        }
    }

    void hoverMoveEvent(QGraphicsSceneHoverEvent *) override
    {
        if (harakiri == 8) {
            dead = true;
            delete this;
        }
    }

    void inputMethodEvent(QInputMethodEvent *event) override
    {
        // ??
        QGraphicsRectItem::inputMethodEvent(event);
    }

    QVariant inputMethodQuery(Qt::InputMethodQuery query) const override
    {
        // ??
        return QGraphicsRectItem::inputMethodQuery(query);
    }

    QVariant itemChange(GraphicsItemChange change, const QVariant &value) override
    {
        // deletion not supported
        return QGraphicsRectItem::itemChange(change, value);
    }

    void keyPressEvent(QKeyEvent *) override
    {
        if (harakiri == 9) {
            dead = true;
            delete this;
        }
    }

    void keyReleaseEvent(QKeyEvent *) override
    {
        if (harakiri == 10) {
            dead = true;
            delete this;
        }
    }

    void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *) override
    {
        if (harakiri == 11) {
            dead = true;
            delete this;
        }
    }

    void mouseMoveEvent(QGraphicsSceneMouseEvent *) override
    {
        if (harakiri == 12) {
            dead = true;
            delete this;
        }
    }

    void mousePressEvent(QGraphicsSceneMouseEvent *) override
    {
        if (harakiri == 13) {
            dead = true;
            delete this;
        }
    }

    void mouseReleaseEvent(QGraphicsSceneMouseEvent *) override
    {
        if (harakiri == 14) {
            dead = true;
            delete this;
        }
    }

    bool sceneEvent(QEvent *event) override
    {
        // deletion not supported
        return QGraphicsRectItem::sceneEvent(event);
    }

    bool sceneEventFilter(QGraphicsItem *watched, QEvent *event) override
    {
        // deletion not supported
        return QGraphicsRectItem::sceneEventFilter(watched, event);
    }

    void wheelEvent(QGraphicsSceneWheelEvent *) override
    {
        if (harakiri == 16) {
            dead = true;
            delete this;
        }
    }

private:
    int harakiri;
};

bool HarakiriItem::dead;

void tst_QGraphicsItem::deleteItemInEventHandlers()
{
    for (int i = 0; i < 17; ++i) {
        QGraphicsScene scene;
        HarakiriItem *item = new HarakiriItem(i);
        item->setAcceptHoverEvents(true);
        item->setFlag(QGraphicsItem::ItemIsFocusable);

        scene.addItem(item);

        item->installSceneEventFilter(item); // <- ehey!

        QGraphicsView view(&scene);
        view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
        view.show();

        QCoreApplication::processEvents();
        QCoreApplication::processEvents();

        if (!HarakiriItem::dead)
            scene.advance();

#ifndef QT_NO_CONTEXTMENU
        if (!HarakiriItem::dead) {
            auto viewPos = view.mapFromScene(item->scenePos());
            QContextMenuEvent event(QContextMenuEvent::Other, viewPos,
                                    view.mapToGlobal(viewPos));
            QCoreApplication::sendEvent(view.viewport(), &event);
        }
#endif // QT_NO_CONTEXTMENU
        if (!HarakiriItem::dead)
            QTest::mouseMove(view.viewport(), view.mapFromScene(item->scenePos()));
        if (!HarakiriItem::dead)
            QTest::mouseClick(view.viewport(), Qt::LeftButton, {}, view.mapFromScene(item->scenePos()));
        if (!HarakiriItem::dead)
            QTest::mouseDClick(view.viewport(), Qt::LeftButton, {}, view.mapFromScene(item->scenePos()));
        if (!HarakiriItem::dead)
            QTest::mouseClick(view.viewport(), Qt::RightButton, {}, view.mapFromScene(item->scenePos()));
        if (!HarakiriItem::dead)
            QTest::mouseMove(view.viewport(), view.mapFromScene(item->scenePos() + QPointF(20, -20)));
        if (!HarakiriItem::dead)
            item->setFocus();
        if (!HarakiriItem::dead)
            item->clearFocus();
        if (!HarakiriItem::dead)
            item->setFocus();
        if (!HarakiriItem::dead)
            QTest::keyPress(view.viewport(), Qt::Key_A);
        if (!HarakiriItem::dead)
            QTest::keyRelease(view.viewport(), Qt::Key_A);
        if (!HarakiriItem::dead)
            QTest::keyPress(view.viewport(), Qt::Key_A);
        if (!HarakiriItem::dead)
            QTest::keyRelease(view.viewport(), Qt::Key_A);
    }
}

class ItemPaintsOutsideShape : public QGraphicsItem
{
public:
    QRectF boundingRect() const override
    {
        return QRectF(0, 0, 100, 100);
    }

    void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override
    {
        painter->fillRect(-50, -50, 200, 200, Qt::red);
        painter->fillRect(0, 0, 100, 100, Qt::blue);
    }
};

void tst_QGraphicsItem::itemClipsToShape()
{
    QGraphicsItem *clippedItem = new ItemPaintsOutsideShape;
    clippedItem->setFlag(QGraphicsItem::ItemClipsToShape);

    QGraphicsItem *unclippedItem = new ItemPaintsOutsideShape;
    unclippedItem->setPos(200, 0);

    QGraphicsScene scene(-50, -50, 400, 200);
    scene.addItem(clippedItem);
    scene.addItem(unclippedItem);

    QImage image(400, 200, QImage::Format_ARGB32_Premultiplied);
    image.fill(0);
    QPainter painter(&image);
    painter.setRenderHint(QPainter::Antialiasing);
    scene.render(&painter);
    painter.end();

    QCOMPARE(image.pixel(45, 100), QRgb(0));
    QCOMPARE(image.pixel(100, 45), QRgb(0));
    QCOMPARE(image.pixel(155, 100), QRgb(0));
    QCOMPARE(image.pixel(45, 155), QRgb(0));
    QCOMPARE(image.pixel(55, 100), QColor(Qt::blue).rgba());
    QCOMPARE(image.pixel(100, 55), QColor(Qt::blue).rgba());
    QCOMPARE(image.pixel(145, 100), QColor(Qt::blue).rgba());
    QCOMPARE(image.pixel(55, 145), QColor(Qt::blue).rgba());
    QCOMPARE(image.pixel(245, 100), QColor(Qt::red).rgba());
    QCOMPARE(image.pixel(300, 45), QColor(Qt::red).rgba());
    QCOMPARE(image.pixel(355, 100), QColor(Qt::red).rgba());
    QCOMPARE(image.pixel(245, 155), QColor(Qt::red).rgba());
    QCOMPARE(image.pixel(255, 100), QColor(Qt::blue).rgba());
    QCOMPARE(image.pixel(300, 55), QColor(Qt::blue).rgba());
    QCOMPARE(image.pixel(345, 100), QColor(Qt::blue).rgba());
    QCOMPARE(image.pixel(255, 145), QColor(Qt::blue).rgba());
}

void tst_QGraphicsItem::itemClipsChildrenToShape()
{
    QGraphicsScene scene;
    QGraphicsItem *rect = scene.addRect(0, 0, 50, 50, QPen(Qt::NoPen), QBrush(Qt::yellow));

    QGraphicsItem *ellipse = scene.addEllipse(0, 0, 100, 100, QPen(Qt::NoPen), QBrush(Qt::green));
    ellipse->setParentItem(rect);

    QGraphicsItem *clippedEllipse = scene.addEllipse(0, 0, 50, 50, QPen(Qt::NoPen), QBrush(Qt::blue));
    clippedEllipse->setParentItem(ellipse);

    QGraphicsItem *clippedEllipse2 = scene.addEllipse(0, 0, 25, 25, QPen(Qt::NoPen), QBrush(Qt::red));
    clippedEllipse2->setParentItem(clippedEllipse);

    QGraphicsItem *clippedEllipse3 = scene.addEllipse(50, 50, 25, 25, QPen(Qt::NoPen), QBrush(Qt::red));
    clippedEllipse3->setParentItem(clippedEllipse);

    QVERIFY(!(ellipse->flags() & QGraphicsItem::ItemClipsChildrenToShape));
    ellipse->setFlag(QGraphicsItem::ItemClipsChildrenToShape);
    QVERIFY((ellipse->flags() & QGraphicsItem::ItemClipsChildrenToShape));

    QImage image(100, 100, QImage::Format_ARGB32_Premultiplied);
    image.fill(0);
    QPainter painter(&image);
    painter.setRenderHint(QPainter::Antialiasing);
    scene.render(&painter);
    painter.end();

    QCOMPARE(image.pixel(16, 16), QColor(255, 0, 0).rgba());
    QCOMPARE(image.pixel(32, 32), QColor(0, 0, 255).rgba());
    QCOMPARE(image.pixel(50, 50), QColor(0, 255, 0).rgba());
    QCOMPARE(image.pixel(12, 12), QColor(255, 255, 0).rgba());
    QCOMPARE(image.pixel(60, 60), QColor(255, 0, 0).rgba());
}

void tst_QGraphicsItem::itemClipsChildrenToShape2()
{
    QGraphicsRectItem *parent = new QGraphicsRectItem(QRectF(0, 0, 10, 10));
    QGraphicsEllipseItem *child1 = new QGraphicsEllipseItem(QRectF(50, 50, 100, 100));
    QGraphicsRectItem *child2 = new QGraphicsRectItem(QRectF(15, 15, 80, 80));

    child1->setParentItem(parent);
    child1->setFlag(QGraphicsItem::ItemClipsChildrenToShape);
    child2->setParentItem(child1);

    parent->setBrush(Qt::blue);
    child1->setBrush(Qt::green);
    child2->setBrush(Qt::red);

    QGraphicsScene scene;
    scene.addItem(parent);

    QCOMPARE(scene.items(QPointF(5, 5)).value(0, nullptr), parent);
    QVERIFY(scene.items(QPointF(15, 5)).isEmpty());
    QVERIFY(scene.items(QPointF(5, 15)).isEmpty());
    QVERIFY(scene.items(QPointF(60, 60)).isEmpty());
    QVERIFY(scene.items(QPointF(140, 60)).isEmpty());
    QVERIFY(scene.items(QPointF(60, 140)).isEmpty());
    QVERIFY(scene.items(QPointF(140, 140)).isEmpty());
    QCOMPARE(scene.items(QPointF(75, 75)).value(0, nullptr), child2);
    QCOMPARE(scene.items(QPointF(75, 100)).value(0, nullptr), child1);
    QCOMPARE(scene.items(QPointF(100, 75)).value(0, nullptr), child1);

    QImage image(100, 100, QImage::Format_ARGB32_Premultiplied);
    image.fill(0);
    QPainter painter(&image);
    scene.render(&painter);
    painter.end();

    QCOMPARE(image.pixel(5, 5), QColor(0, 0, 255).rgba());
    QCOMPARE(image.pixel(5, 10), QRgb(0));
    QCOMPARE(image.pixel(10, 5), QRgb(0));
    QCOMPARE(image.pixel(40, 40), QRgb(0));
    QCOMPARE(image.pixel(90, 40), QRgb(0));
    QCOMPARE(image.pixel(40, 90), QRgb(0));
    QCOMPARE(image.pixel(95, 95), QRgb(0));
    QCOMPARE(image.pixel(50, 70), QColor(0, 255, 0).rgba());
    QCOMPARE(image.pixel(70, 50), QColor(0, 255, 0).rgba());
    QCOMPARE(image.pixel(50, 60), QColor(255, 0, 0).rgba());
    QCOMPARE(image.pixel(60, 50), QColor(255, 0, 0).rgba());
}

void tst_QGraphicsItem::itemClipsChildrenToShape3()
{
    // Construct a scene with nested children, each 50 pixels offset from the elder.
    // Set a top-level clipping flag
    QGraphicsScene scene;
    QGraphicsRectItem *parent = scene.addRect( 0, 0, 150, 150 );
    QGraphicsRectItem *child = scene.addRect( 0, 0, 150, 150 );
    QGraphicsRectItem *grandchild = scene.addRect( 0, 0, 150, 150 );
    child->setParentItem(parent);
    grandchild->setParentItem(child);
    child->setPos( 50, 50 );
    grandchild->setPos( 50, 50 );
    parent->setFlag(QGraphicsItem::ItemClipsChildrenToShape);

    QCOMPARE(scene.items(QPointF(25, 25)).value(0, nullptr), parent);
    QCOMPARE(scene.items(QPointF(75, 75)).value(0, nullptr), child);
    QCOMPARE(scene.items(QPointF(125, 125)).value(0, nullptr), grandchild);
    QVERIFY(scene.items(QPointF(175, 175)).isEmpty());

    // Move child to fully overlap the parent.  The grandchild should
    // now occupy two-thirds of the scene
    child->prepareGeometryChange();
    child->setPos( 0, 0 );

    QCOMPARE(scene.items(QPointF(25, 25)).value(0, nullptr), child);
    QCOMPARE(scene.items(QPointF(75, 75)).value(0, nullptr), grandchild);
    QCOMPARE(scene.items(QPointF(125, 125)).value(0, nullptr), grandchild);
    QVERIFY(scene.items(QPointF(175, 175)).isEmpty());
}

class MyProxyWidget : public QGraphicsProxyWidget
{
public:
    using QGraphicsProxyWidget::QGraphicsProxyWidget;

    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override
    {
        QGraphicsProxyWidget::paint(painter, option, widget);
        painted = true;
    }

    bool painted = false;
};

void tst_QGraphicsItem::itemClipsChildrenToShape4()
{
    QGraphicsScene scene;
    QGraphicsView view(&scene);

    QGraphicsWidget * outerWidget = new QGraphicsWidget();
    outerWidget->setFlag(QGraphicsItem::ItemClipsChildrenToShape, true);
    MyProxyWidget * innerWidget = new MyProxyWidget(outerWidget);
    QLabel * label = new QLabel();
    label->setText("Welcome back my friends to the show that never ends...");
    innerWidget->setWidget(label);
    view.resize(300, 300);
    scene.addItem(outerWidget);
    outerWidget->resize( 200, 100 );
    scene.addEllipse( 100, 100, 100, 50 );   // <-- this is important to trigger the right codepath*
    //now the label is shown
    outerWidget->setFlag(QGraphicsItem::ItemClipsChildrenToShape, false );
    QApplicationPrivate::setActiveWindow(&view);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.show();
    QTRY_COMPARE(QApplication::activeWindow(), &view);
    QTRY_COMPARE(innerWidget->painted, true);
}

//#define DEBUG_ITEM_CLIPS_CHILDREN_TO_SHAPE_5
static inline void renderSceneToImage(QGraphicsScene *scene, QImage *image, const QString &filename)
{
    image->fill(0);
    QPainter painter(image);
    scene->render(&painter);
    painter.end();
#ifdef DEBUG_ITEM_CLIPS_CHILDREN_TO_SHAPE_5
    image->save(filename);
#else
    Q_UNUSED(filename);
#endif
}

void tst_QGraphicsItem::itemClipsChildrenToShape5()
{
    class ParentItem : public QGraphicsRectItem
    {
    public:
        using QGraphicsRectItem::QGraphicsRectItem;

        QPainterPath shape() const override
        {
            QPainterPath path;
            path.addRect(50, 50, 200, 200);
            return path;
        }
    };

    ParentItem *parent = new ParentItem(0, 0, 300, 300);
    parent->setBrush(Qt::blue);
    parent->setOpacity(0.5);

    const QRegion parentRegion(0, 0, 300, 300);
    const QRegion clippedParentRegion = parentRegion & QRect(50, 50, 200, 200);
    QRegion childRegion;
    QRegion grandChildRegion;

    QGraphicsRectItem *topLeftChild = new QGraphicsRectItem(0, 0, 100, 100);
    topLeftChild->setBrush(Qt::red);
    topLeftChild->setParentItem(parent);
    childRegion += QRect(0, 0, 100, 100);

    QGraphicsRectItem *topRightChild = new QGraphicsRectItem(0, 0, 100, 100);
    topRightChild->setBrush(Qt::red);
    topRightChild->setParentItem(parent);
    topRightChild->setFlag(QGraphicsItem::ItemClipsChildrenToShape);
    topRightChild->setPos(200, 0);
    childRegion += QRect(200, 0, 100, 100);

    QGraphicsRectItem *topRightGrandChild = new QGraphicsRectItem(0, 0, 100, 100);
    topRightGrandChild->setBrush(Qt::green);
    topRightGrandChild->setParentItem(topRightChild);
    topRightGrandChild->setPos(-40, 40);
    grandChildRegion += QRect(200 - 40, 0 + 40, 100, 100) & QRect(200, 0, 100, 100);

    QGraphicsRectItem *bottomLeftChild = new QGraphicsRectItem(0, 0, 100, 100);
    bottomLeftChild->setBrush(Qt::red);
    bottomLeftChild->setParentItem(parent);
    bottomLeftChild->setFlag(QGraphicsItem::ItemClipsToShape);
    bottomLeftChild->setPos(0, 200);
    childRegion += QRect(0, 200, 100, 100);

    QGraphicsRectItem *bottomLeftGrandChild = new QGraphicsRectItem(0, 0, 160, 160);
    bottomLeftGrandChild->setBrush(Qt::green);
    bottomLeftGrandChild->setParentItem(bottomLeftChild);
    bottomLeftGrandChild->setFlag(QGraphicsItem::ItemClipsToShape);
    bottomLeftGrandChild->setPos(0, -60);
    grandChildRegion += QRect(0, 200 - 60, 160, 160);

    QGraphicsRectItem *bottomRightChild = new QGraphicsRectItem(0, 0, 100, 100);
    bottomRightChild->setBrush(Qt::red);
    bottomRightChild->setParentItem(parent);
    bottomRightChild->setPos(200, 200);
    childRegion += QRect(200, 200, 100, 100);

    const QPoint controlPoints[17] = {
        {5, 5},   {95, 5},   {205, 5},    {295, 5},
        {5, 95},  {95, 95},  {205, 95},   {295, 95},
                             {150, 150},
        {5, 205}, {95, 205}, {205, 205}, {295, 205},
        {5, 295}, {95, 295}, {205, 295}, {295, 295},
    };

    const QRegion clippedChildRegion = childRegion & QRect(50, 50, 200, 200);
    const QRegion clippedGrandChildRegion = grandChildRegion & QRect(50, 50, 200, 200);

    QGraphicsScene scene;
    scene.addItem(parent);
    QImage sceneImage(300, 300, QImage::Format_ARGB32);

#define VERIFY_CONTROL_POINTS(pRegion, cRegion, gRegion) \
    for (const QPoint &controlPoint : controlPoints) { \
        QRgb pixel = sceneImage.pixel(controlPoint.x(), controlPoint.y()); \
        if (pRegion.contains(controlPoint)) \
            QVERIFY(qBlue(pixel) != 0); \
        else \
            QVERIFY(qBlue(pixel) == 0); \
        if (cRegion.contains(controlPoint)) \
            QVERIFY(qRed(pixel) != 0); \
        else \
            QVERIFY(qRed(pixel) == 0); \
        if (gRegion.contains(controlPoint)) \
            QVERIFY(qGreen(pixel) != 0); \
        else \
            QVERIFY(qGreen(pixel) == 0); \
    }

    const QList<QGraphicsItem *> children = parent->childItems();
    const int childrenCount = children.size();

    for (int i = 0; i < 5; ++i) {
        QString clipString;
        QString childString;
        switch (i) {
        case 0:
            // All children stacked in front.
            childString = QLatin1String("ChildrenInFront.png");
            for (QGraphicsItem *child : children)
                child->setFlag(QGraphicsItem::ItemStacksBehindParent, false);
            break;
        case 1:
            // All children stacked behind.
            childString = QLatin1String("ChildrenBehind.png");
            for (QGraphicsItem *child : children)
                child->setFlag(QGraphicsItem::ItemStacksBehindParent, true);
            break;
        case 2:
            // First half of the children behind, second half in front.
            childString = QLatin1String("FirstHalfBehind_SecondHalfInFront.png");
            for (int j = 0; j < childrenCount; ++j) {
                QGraphicsItem *child = children.at(j);
                child->setFlag(QGraphicsItem::ItemStacksBehindParent, (j < childrenCount / 2));
            }
            break;
        case 3:
            // First half of the children in front, second half behind.
            childString = QLatin1String("FirstHalfInFront_SecondHalfBehind.png");
            for (int j = 0; j < childrenCount; ++j) {
                QGraphicsItem *child = children.at(j);
                child->setFlag(QGraphicsItem::ItemStacksBehindParent, (j >= childrenCount / 2));
            }
            break;
        case 4:
            // Child2 and child4 behind, rest in front.
            childString = QLatin1String("Child2And4Behind_RestInFront.png");
            for (int j = 0; j < childrenCount; ++j) {
                QGraphicsItem *child = children.at(j);
                if (j == 1 || j == 3)
                    child->setFlag(QGraphicsItem::ItemStacksBehindParent, true);
                else
                    child->setFlag(QGraphicsItem::ItemStacksBehindParent, false);
            }
            break;
        default:
            qFatal("internal error");
        }

        // Nothing is clipped.
        parent->setFlag(QGraphicsItem::ItemClipsChildrenToShape, false);
        parent->setFlag(QGraphicsItem::ItemClipsToShape, false);
        clipString = QLatin1String("nothingClipped_");
        renderSceneToImage(&scene, &sceneImage, clipString + childString);
        VERIFY_CONTROL_POINTS(parentRegion, childRegion, grandChildRegion);

        // Parent clips children to shape.
        parent->setFlag(QGraphicsItem::ItemClipsChildrenToShape);
        clipString = QLatin1String("parentClipsChildrenToShape_");
        renderSceneToImage(&scene, &sceneImage, clipString + childString);
        VERIFY_CONTROL_POINTS(parentRegion, clippedChildRegion, clippedGrandChildRegion);

        // Parent clips itself and children to shape.
        parent->setFlag(QGraphicsItem::ItemClipsToShape);
        clipString = QLatin1String("parentClipsItselfAndChildrenToShape_");
        renderSceneToImage(&scene, &sceneImage, clipString + childString);
        VERIFY_CONTROL_POINTS(clippedParentRegion, clippedChildRegion, clippedGrandChildRegion);

        // Parent clips itself to shape.
        parent->setFlag(QGraphicsItem::ItemClipsChildrenToShape, false);
        clipString = QLatin1String("parentClipsItselfToShape_");
        renderSceneToImage(&scene, &sceneImage, clipString + childString);
        VERIFY_CONTROL_POINTS(clippedParentRegion, childRegion, grandChildRegion);
    }
}

void tst_QGraphicsItem::itemClipsTextChildToShape()
{
    // Construct a scene with a rect that clips its children, with one text
    // child that has text that exceeds the size of the rect.
    QGraphicsScene scene;
    QGraphicsItem *rect = scene.addRect(0, 0, 50, 50, QPen(Qt::black), Qt::black);
    rect->setFlag(QGraphicsItem::ItemClipsChildrenToShape);
    QGraphicsTextItem *text = new QGraphicsTextItem("This is a long sentence that's wider than 50 pixels.");
    text->setParentItem(rect);

    // Render this scene to a transparent image.
    QRectF sr = scene.itemsBoundingRect();
    QImage image(sr.size().toSize(), QImage::Format_ARGB32_Premultiplied);
    image.fill(0);
    QPainter painter(&image);
    scene.render(&painter);

    // Erase the area immediately underneath the rect.
    painter.setCompositionMode(QPainter::CompositionMode_Source);
    painter.fillRect(rect->sceneBoundingRect().translated(-sr.topLeft()).adjusted(-0.5, -0.5, 0.5, 0.5),
                     Qt::transparent);
    painter.end();

    // Check that you get a truly transparent image back (i.e., the text was
    // clipped away, so there should be no trails left after erasing only the
    // rect's area).
    QImage emptyImage(scene.itemsBoundingRect().size().toSize(), QImage::Format_ARGB32_Premultiplied);
    emptyImage.fill(0);
    QCOMPARE(image, emptyImage);
}

void tst_QGraphicsItem::itemClippingDiscovery()
{
    // A simple scene with an ellipse parent and two rect children, one a
    // child of the other.
    QGraphicsScene scene;
    QGraphicsEllipseItem *clipItem = scene.addEllipse(0, 0, 100, 100);
    QGraphicsRectItem *leftRectItem = scene.addRect(0, 0, 50, 100);
    QGraphicsRectItem *rightRectItem = scene.addRect(50, 0, 50, 100);
    leftRectItem->setParentItem(clipItem);
    rightRectItem->setParentItem(clipItem);

    // The rects item are both visible at these points.
    QCOMPARE(scene.items(QPointF(10, 10)).value(0, nullptr), leftRectItem);
    QCOMPARE(scene.items(QPointF(90, 90)).value(0, nullptr), rightRectItem);

    // The ellipse clips the rects now.
    clipItem->setFlag(QGraphicsItem::ItemClipsChildrenToShape);

    // The rect items are no longer visible at these points.
    QVERIFY(scene.items(QPointF(10, 10)).isEmpty());
    if (sizeof(qreal) != sizeof(double))
        QSKIP("This fails due to internal rounding errors");
    QVERIFY(scene.items(QPointF(90, 90)).isEmpty());
}

class ItemCountsBoundingRectCalls : public QGraphicsRectItem
{
public:
    using QGraphicsRectItem::QGraphicsRectItem;

    QRectF boundingRect () const override
    {
        ++boundingRectCalls;
        return QGraphicsRectItem::boundingRect();
    }

    mutable int boundingRectCalls = 0;
};

void tst_QGraphicsItem::itemContainsChildrenInShape()
{
    ItemCountsBoundingRectCalls *parent = new ItemCountsBoundingRectCalls(QRectF(0,0, 10, 10));
    ItemCountsBoundingRectCalls *childOutsideShape = new ItemCountsBoundingRectCalls(QRectF(0,0, 10, 10), parent);
    childOutsideShape->setPos(20,0);

    QGraphicsScene scene;
    scene.setItemIndexMethod(QGraphicsScene::NoIndex);
    scene.addItem(parent);

    QCOMPARE(parent->boundingRectCalls, childOutsideShape->boundingRectCalls);

    int oldParentBoundingRectCalls = parent->boundingRectCalls;
    int oldChildBoundingRectCalls = childOutsideShape->boundingRectCalls;

    // First test that both items are searched if no optimization flags are set
    QGraphicsItem* item = scene.items(QPointF(25, 5)).value(0, nullptr);

    QCOMPARE(item, childOutsideShape);
    QVERIFY(parent->boundingRectCalls > oldParentBoundingRectCalls);
    QVERIFY(childOutsideShape->boundingRectCalls > oldChildBoundingRectCalls);
    QCOMPARE(parent->boundingRectCalls, childOutsideShape->boundingRectCalls);

    oldParentBoundingRectCalls = parent->boundingRectCalls;
    oldChildBoundingRectCalls = childOutsideShape->boundingRectCalls;

    // Repeat the test to make sure that no caching/indexing is in effect
    item = scene.items(QPointF(25, 5)).value(0, nullptr);

    QCOMPARE(item, childOutsideShape);
    QVERIFY(parent->boundingRectCalls > oldParentBoundingRectCalls);
    QVERIFY(childOutsideShape->boundingRectCalls > oldChildBoundingRectCalls);
    QCOMPARE(parent->boundingRectCalls, childOutsideShape->boundingRectCalls);

    oldParentBoundingRectCalls = parent->boundingRectCalls;
    oldChildBoundingRectCalls = childOutsideShape->boundingRectCalls;

    // Set the optimization flag and make sure that the child is not returned
    // and that the child's boundingRect() method is never called.
    parent->setFlag(QGraphicsItem::ItemContainsChildrenInShape);
    item = scene.items(QPointF(25, 5)).value(0, nullptr);

    QVERIFY(!(item));
    QVERIFY(parent->boundingRectCalls > oldParentBoundingRectCalls);
    QCOMPARE(childOutsideShape->boundingRectCalls, oldChildBoundingRectCalls);
    QVERIFY(parent->boundingRectCalls > childOutsideShape->boundingRectCalls);
}

void tst_QGraphicsItem::itemContainsChildrenInShape2()
{
    //The tested flag behaves almost identically to ItemClipsChildrenToShape
    //in terms of optimizations but does not enforce the clip.
    //This test makes sure there is no clip.
    QGraphicsScene scene;
    QGraphicsItem *rect = scene.addRect(0, 0, 50, 50, QPen(Qt::NoPen), QBrush(Qt::yellow));

    QGraphicsItem *ellipse = scene.addEllipse(0, 0, 100, 100, QPen(Qt::NoPen), QBrush(Qt::green));
    ellipse->setParentItem(rect);

    QGraphicsItem *clippedEllipse = scene.addEllipse(0, 0, 50, 50, QPen(Qt::NoPen), QBrush(Qt::blue));
    clippedEllipse->setParentItem(ellipse);

    QGraphicsItem *clippedEllipse2 = scene.addEllipse(0, 0, 25, 25, QPen(Qt::NoPen), QBrush(Qt::red));
    clippedEllipse2->setParentItem(clippedEllipse);

    QVERIFY(!(ellipse->flags() & QGraphicsItem::ItemClipsChildrenToShape));
    QVERIFY(!(ellipse->flags() & QGraphicsItem::ItemContainsChildrenInShape));
    ellipse->setFlags(QGraphicsItem::ItemContainsChildrenInShape);
    QVERIFY(!(ellipse->flags() & QGraphicsItem::ItemClipsChildrenToShape));
    QVERIFY((ellipse->flags() & QGraphicsItem::ItemContainsChildrenInShape));

    QImage image(100, 100, QImage::Format_ARGB32_Premultiplied);
    image.fill(0);
    QPainter painter(&image);
    painter.setRenderHint(QPainter::Antialiasing);
    scene.render(&painter);
    painter.end();

    QCOMPARE(image.pixel(2, 2), QColor(Qt::yellow).rgba());
    QCOMPARE(image.pixel(12, 12), QColor(Qt::red).rgba());
    QCOMPARE(image.pixel(2, 25), QColor(Qt::blue).rgba());
    QCOMPARE(image.pixel(2, 50), QColor(Qt::green).rgba());
}

void tst_QGraphicsItem::ancestorFlags()
{
    QGraphicsItem *level1 = new QGraphicsRectItem;
    QGraphicsItem *level21 = new QGraphicsRectItem;
    level21->setParentItem(level1);
    QGraphicsItem *level22 = new QGraphicsRectItem;
    level22->setParentItem(level1);
    QGraphicsItem *level31 = new QGraphicsRectItem;
    level31->setParentItem(level21);
    QGraphicsItem *level32 = new QGraphicsRectItem;
    level32->setParentItem(level21);

    QCOMPARE(int(level1->d_ptr->ancestorFlags), 0);
    QCOMPARE(int(level21->d_ptr->ancestorFlags), 0);
    QCOMPARE(int(level22->d_ptr->ancestorFlags), 0);
    QCOMPARE(int(level31->d_ptr->ancestorFlags), 0);
    QCOMPARE(int(level32->d_ptr->ancestorFlags), 0);

    // HandlesChildEvents: 1) Root level sets a flag
    level1->setHandlesChildEvents(true);
    QCOMPARE(int(level1->d_ptr->ancestorFlags), 0);
    QCOMPARE(int(level21->d_ptr->ancestorFlags), 1);
    QCOMPARE(int(level22->d_ptr->ancestorFlags), 1);
    QCOMPARE(int(level31->d_ptr->ancestorFlags), 1);
    QCOMPARE(int(level32->d_ptr->ancestorFlags), 1);

    // HandlesChildEvents: 2) Root level set it again
    level1->setHandlesChildEvents(true);
    QCOMPARE(int(level1->d_ptr->ancestorFlags), 0);
    QCOMPARE(int(level21->d_ptr->ancestorFlags), 1);
    QCOMPARE(int(level22->d_ptr->ancestorFlags), 1);
    QCOMPARE(int(level31->d_ptr->ancestorFlags), 1);
    QCOMPARE(int(level32->d_ptr->ancestorFlags), 1);

    // HandlesChildEvents: 3) Root level unsets a flag
    level1->setHandlesChildEvents(false);
    QCOMPARE(int(level1->d_ptr->ancestorFlags), 0);
    QCOMPARE(int(level21->d_ptr->ancestorFlags), 0);
    QCOMPARE(int(level22->d_ptr->ancestorFlags), 0);
    QCOMPARE(int(level31->d_ptr->ancestorFlags), 0);
    QCOMPARE(int(level32->d_ptr->ancestorFlags), 0);

    // HandlesChildEvents: 4) Child item sets a flag
    level21->setHandlesChildEvents(true);
    QCOMPARE(int(level1->d_ptr->ancestorFlags), 0);
    QCOMPARE(int(level21->d_ptr->ancestorFlags), 0);
    QCOMPARE(int(level22->d_ptr->ancestorFlags), 0);
    QCOMPARE(int(level31->d_ptr->ancestorFlags), 1);
    QCOMPARE(int(level32->d_ptr->ancestorFlags), 1);

    // HandlesChildEvents: 5) Parent item sets a flag
    level1->setHandlesChildEvents(true);
    QCOMPARE(int(level1->d_ptr->ancestorFlags), 0);
    QCOMPARE(int(level21->d_ptr->ancestorFlags), 1);
    QCOMPARE(int(level22->d_ptr->ancestorFlags), 1);
    QCOMPARE(int(level31->d_ptr->ancestorFlags), 1);
    QCOMPARE(int(level32->d_ptr->ancestorFlags), 1);

    // HandlesChildEvents: 6) Child item unsets a flag
    level21->setHandlesChildEvents(false);
    QCOMPARE(int(level1->d_ptr->ancestorFlags), 0);
    QCOMPARE(int(level21->d_ptr->ancestorFlags), 1);
    QCOMPARE(int(level22->d_ptr->ancestorFlags), 1);
    QCOMPARE(int(level31->d_ptr->ancestorFlags), 1);
    QCOMPARE(int(level32->d_ptr->ancestorFlags), 1);

    // HandlesChildEvents: 7) Parent item unsets a flag
    level21->setHandlesChildEvents(true);
    level1->setHandlesChildEvents(false);
    QCOMPARE(int(level1->d_ptr->ancestorFlags), 0);
    QCOMPARE(int(level21->d_ptr->ancestorFlags), 0);
    QCOMPARE(int(level22->d_ptr->ancestorFlags), 0);
    QCOMPARE(int(level31->d_ptr->ancestorFlags), 1);
    QCOMPARE(int(level32->d_ptr->ancestorFlags), 1);

    // Reparent the child to root
    level21->setParentItem(nullptr);
    QCOMPARE(int(level21->d_ptr->ancestorFlags), 0);
    QCOMPARE(int(level22->d_ptr->ancestorFlags), 0);
    QCOMPARE(int(level31->d_ptr->ancestorFlags), 1);
    QCOMPARE(int(level32->d_ptr->ancestorFlags), 1);

    // Reparent the child to level1 again.
    level1->setHandlesChildEvents(true);
    level21->setParentItem(level1);
    QCOMPARE(int(level1->d_ptr->ancestorFlags), 0);
    QCOMPARE(int(level21->d_ptr->ancestorFlags), 1);
    QCOMPARE(int(level22->d_ptr->ancestorFlags), 1);
    QCOMPARE(int(level31->d_ptr->ancestorFlags), 1);
    QCOMPARE(int(level32->d_ptr->ancestorFlags), 1);

    // Reparenting level31 back to level1.
    level31->setParentItem(level1);
    QCOMPARE(int(level1->d_ptr->ancestorFlags), 0);
    QCOMPARE(int(level21->d_ptr->ancestorFlags), 1);
    QCOMPARE(int(level22->d_ptr->ancestorFlags), 1);
    QCOMPARE(int(level31->d_ptr->ancestorFlags), 1);
    QCOMPARE(int(level32->d_ptr->ancestorFlags), 1);

    // Reparenting level31 back to level21.
    level31->setParentItem(nullptr);
    QCOMPARE(int(level31->d_ptr->ancestorFlags), 0);
    level31->setParentItem(level21);
    QCOMPARE(int(level1->d_ptr->ancestorFlags), 0);
    QCOMPARE(int(level21->d_ptr->ancestorFlags), 1);
    QCOMPARE(int(level22->d_ptr->ancestorFlags), 1);
    QCOMPARE(int(level31->d_ptr->ancestorFlags), 1);
    QCOMPARE(int(level32->d_ptr->ancestorFlags), 1);

    // Level1 doesn't handle child events
    level1->setHandlesChildEvents(false);
    QCOMPARE(int(level1->d_ptr->ancestorFlags), 0);
    QCOMPARE(int(level21->d_ptr->ancestorFlags), 0);
    QCOMPARE(int(level22->d_ptr->ancestorFlags), 0);
    QCOMPARE(int(level31->d_ptr->ancestorFlags), 1);
    QCOMPARE(int(level32->d_ptr->ancestorFlags), 1);

    // Nobody handles child events
    level21->setHandlesChildEvents(false);

    for (int i = 0; i < 3; ++i) {
        QGraphicsItem::GraphicsItemFlag flag;
        int ancestorFlag;

        switch (i) {
        case(0):
            flag = QGraphicsItem::ItemClipsChildrenToShape;
            ancestorFlag = QGraphicsItemPrivate::AncestorClipsChildren;
            break;
        case(1):
            flag = QGraphicsItem::ItemIgnoresTransformations;
            ancestorFlag = QGraphicsItemPrivate::AncestorIgnoresTransformations;
            break;
        case(2):
            flag = QGraphicsItem::ItemContainsChildrenInShape;
            ancestorFlag = QGraphicsItemPrivate::AncestorContainsChildren;
            break;
        default:
            qFatal("Unknown ancestor flag, please fix!");
        }

        QCOMPARE(int(level1->d_ptr->ancestorFlags), 0);
        QCOMPARE(int(level21->d_ptr->ancestorFlags), 0);
        QCOMPARE(int(level22->d_ptr->ancestorFlags), 0);
        QCOMPARE(int(level31->d_ptr->ancestorFlags), 0);
        QCOMPARE(int(level32->d_ptr->ancestorFlags), 0);

        // HandlesChildEvents: 1) Root level sets a flag
        level1->setFlag(flag, true);
        QCOMPARE(int(level1->d_ptr->ancestorFlags), 0);
        QCOMPARE(int(level21->d_ptr->ancestorFlags), ancestorFlag);
        QCOMPARE(int(level22->d_ptr->ancestorFlags), ancestorFlag);
        QCOMPARE(int(level31->d_ptr->ancestorFlags), ancestorFlag);
        QCOMPARE(int(level32->d_ptr->ancestorFlags), ancestorFlag);

        // HandlesChildEvents: 2) Root level set it again
        level1->setFlag(flag, true);
        QCOMPARE(int(level1->d_ptr->ancestorFlags), 0);
        QCOMPARE(int(level21->d_ptr->ancestorFlags), ancestorFlag);
        QCOMPARE(int(level22->d_ptr->ancestorFlags), ancestorFlag);
        QCOMPARE(int(level31->d_ptr->ancestorFlags), ancestorFlag);
        QCOMPARE(int(level32->d_ptr->ancestorFlags), ancestorFlag);

        // HandlesChildEvents: 3) Root level unsets a flag
        level1->setFlag(flag, false);
        QCOMPARE(int(level1->d_ptr->ancestorFlags), 0);
        QCOMPARE(int(level21->d_ptr->ancestorFlags), 0);
        QCOMPARE(int(level22->d_ptr->ancestorFlags), 0);
        QCOMPARE(int(level31->d_ptr->ancestorFlags), 0);
        QCOMPARE(int(level32->d_ptr->ancestorFlags), 0);

        // HandlesChildEvents: 4) Child item sets a flag
        level21->setFlag(flag, true);
        QCOMPARE(int(level1->d_ptr->ancestorFlags), 0);
        QCOMPARE(int(level21->d_ptr->ancestorFlags), 0);
        QCOMPARE(int(level22->d_ptr->ancestorFlags), 0);
        QCOMPARE(int(level31->d_ptr->ancestorFlags), ancestorFlag);
        QCOMPARE(int(level32->d_ptr->ancestorFlags), ancestorFlag);

        // HandlesChildEvents: 5) Parent item sets a flag
        level1->setFlag(flag, true);
        QCOMPARE(int(level1->d_ptr->ancestorFlags), 0);
        QCOMPARE(int(level21->d_ptr->ancestorFlags), ancestorFlag);
        QCOMPARE(int(level22->d_ptr->ancestorFlags), ancestorFlag);
        QCOMPARE(int(level31->d_ptr->ancestorFlags), ancestorFlag);
        QCOMPARE(int(level32->d_ptr->ancestorFlags), ancestorFlag);

        // HandlesChildEvents: 6) Child item unsets a flag
        level21->setFlag(flag, false);
        QCOMPARE(int(level1->d_ptr->ancestorFlags), 0);
        QCOMPARE(int(level21->d_ptr->ancestorFlags), ancestorFlag);
        QCOMPARE(int(level22->d_ptr->ancestorFlags), ancestorFlag);
        QCOMPARE(int(level31->d_ptr->ancestorFlags), ancestorFlag);
        QCOMPARE(int(level32->d_ptr->ancestorFlags), ancestorFlag);

        // HandlesChildEvents: 7) Parent item unsets a flag
        level21->setFlag(flag, true);
        level1->setFlag(flag, false);
        QCOMPARE(int(level1->d_ptr->ancestorFlags), 0);
        QCOMPARE(int(level21->d_ptr->ancestorFlags), 0);
        QCOMPARE(int(level22->d_ptr->ancestorFlags), 0);
        QCOMPARE(int(level31->d_ptr->ancestorFlags), ancestorFlag);
        QCOMPARE(int(level32->d_ptr->ancestorFlags), ancestorFlag);

        // Reparent the child to root
        level21->setParentItem(nullptr);
        QCOMPARE(int(level21->d_ptr->ancestorFlags), 0);
        QCOMPARE(int(level22->d_ptr->ancestorFlags), 0);
        QCOMPARE(int(level31->d_ptr->ancestorFlags), ancestorFlag);
        QCOMPARE(int(level32->d_ptr->ancestorFlags), ancestorFlag);

        // Reparent the child to level1 again.
        level1->setFlag(flag, true);
        level21->setParentItem(level1);
        QCOMPARE(int(level1->d_ptr->ancestorFlags), 0);
        QCOMPARE(int(level21->d_ptr->ancestorFlags), ancestorFlag);
        QCOMPARE(int(level22->d_ptr->ancestorFlags), ancestorFlag);
        QCOMPARE(int(level31->d_ptr->ancestorFlags), ancestorFlag);
        QCOMPARE(int(level32->d_ptr->ancestorFlags), ancestorFlag);

        // Reparenting level31 back to level1.
        level31->setParentItem(level1);
        QCOMPARE(int(level1->d_ptr->ancestorFlags), 0);
        QCOMPARE(int(level21->d_ptr->ancestorFlags), ancestorFlag);
        QCOMPARE(int(level22->d_ptr->ancestorFlags), ancestorFlag);
        QCOMPARE(int(level31->d_ptr->ancestorFlags), ancestorFlag);
        QCOMPARE(int(level32->d_ptr->ancestorFlags), ancestorFlag);

        // Reparenting level31 back to level21.
        level31->setParentItem(nullptr);
        QCOMPARE(int(level31->d_ptr->ancestorFlags), 0);
        level31->setParentItem(level21);
        QCOMPARE(int(level1->d_ptr->ancestorFlags), 0);
        QCOMPARE(int(level21->d_ptr->ancestorFlags), ancestorFlag);
        QCOMPARE(int(level22->d_ptr->ancestorFlags), ancestorFlag);
        QCOMPARE(int(level31->d_ptr->ancestorFlags), ancestorFlag);
        QCOMPARE(int(level32->d_ptr->ancestorFlags), ancestorFlag);

        // Level1 doesn't handle child events
        level1->setFlag(flag, false);
        QCOMPARE(int(level1->d_ptr->ancestorFlags), 0);
        QCOMPARE(int(level21->d_ptr->ancestorFlags), 0);
        QCOMPARE(int(level22->d_ptr->ancestorFlags), 0);
        QCOMPARE(int(level31->d_ptr->ancestorFlags), ancestorFlag);
        QCOMPARE(int(level32->d_ptr->ancestorFlags), ancestorFlag);

        // Nobody handles child events
        level21->setFlag(flag, false);
        QCOMPARE(int(level1->d_ptr->ancestorFlags), 0);
        QCOMPARE(int(level21->d_ptr->ancestorFlags), 0);
        QCOMPARE(int(level22->d_ptr->ancestorFlags), 0);
        QCOMPARE(int(level31->d_ptr->ancestorFlags), 0);
        QCOMPARE(int(level32->d_ptr->ancestorFlags), 0);
    }

    delete level1;
}

void tst_QGraphicsItem::untransformable()
{
    auto item1 = new QGraphicsEllipseItem(QRectF(-50, -50, 100, 100));
    item1->setZValue(1);
    item1->setFlag(QGraphicsItem::ItemIgnoresTransformations);
    item1->setTransform(QTransform().rotate(45), true);
    item1->setBrush(Qt::red);

    auto item2 = new QGraphicsEllipseItem(QRectF(-50, -50, 100, 100));
    item2->setParentItem(item1);
    item2->setTransform(QTransform().rotate(45), true);
    item2->setPos(100, 0);
    item2->setBrush(Qt::green);

    auto item3 = new QGraphicsEllipseItem(QRectF(-50, -50, 100, 100));
    item3->setParentItem(item2);
    item3->setPos(100, 0);
    item3->setBrush(Qt::blue);

    QGraphicsScene scene(-500, -500, 1000, 1000);
    scene.addItem(item1);

    QWidget topLevel;
    QGraphicsView view(&scene,&topLevel);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.resize(300, 300);
    topLevel.show();
    view.scale(8, 8);
    view.centerOn(0, 0);

// Painting with the DiagCrossPattern is really slow on Mac
// when zoomed out. (The test times out). Task to fix is 155567.
#if !defined(Q_OS_MAC) || 1
    view.setBackgroundBrush(QBrush(Qt::black, Qt::DiagCrossPattern));
#endif

    QVERIFY(QTest::qWaitForWindowExposed(&topLevel));

    for (int i = 0; i < 10; ++i) {
        QPoint center = view.viewport()->rect().center();
        QCOMPARE(view.itemAt(center), item1);
        QCOMPARE(view.itemAt(center - QPoint(40, 0)), item1);
        QCOMPARE(view.itemAt(center - QPoint(-40, 0)), item1);
        QCOMPARE(view.itemAt(center - QPoint(0, 40)), item1);
        QCOMPARE(view.itemAt(center - QPoint(0, -40)), item1);

        center += QPoint(70, 70);
        QCOMPARE(view.itemAt(center - QPoint(40, 0)), item2);
        QCOMPARE(view.itemAt(center - QPoint(-40, 0)), item2);
        QCOMPARE(view.itemAt(center - QPoint(0, 40)), item2);
        QCOMPARE(view.itemAt(center - QPoint(0, -40)), item2);

        center += QPoint(0, 100);
        QCOMPARE(view.itemAt(center - QPoint(40, 0)), item3);
        QCOMPARE(view.itemAt(center - QPoint(-40, 0)), item3);
        QCOMPARE(view.itemAt(center - QPoint(0, 40)), item3);
        QCOMPARE(view.itemAt(center - QPoint(0, -40)), item3);

        view.scale(0.5, 0.5);
        view.rotate(13);
        view.shear(qreal(0.01), qreal(0.01));
        view.translate(10, 10);
    }
}

#ifndef QT_NO_CONTEXTMENU
class ContextMenuItem : public QGraphicsRectItem
{
public:
    using QGraphicsRectItem::QGraphicsRectItem;

    bool ignoreEvent = true;
    bool gotEvent = false;
    bool eventWasAccepted = false;

protected:
    void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override
    {
        gotEvent = true;
        eventWasAccepted = event->isAccepted();
        if (ignoreEvent)
            event->ignore();
    }
};

void tst_QGraphicsItem::contextMenuEventPropagation()
{
    ContextMenuItem *bottomItem = new ContextMenuItem;
    bottomItem->setRect(0, 0, 100, 100);
    ContextMenuItem *topItem = new ContextMenuItem;
    topItem->setParentItem(bottomItem);
    topItem->setRect(0, 0, 100, 100);

    QGraphicsScene scene;

    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.setAlignment(Qt::AlignLeft | Qt::AlignTop);
    view.show();
    view.resize(200, 200);
    QVERIFY(QTest::qWaitForWindowExposed(&view));

    QContextMenuEvent event(QContextMenuEvent::Mouse, QPoint(10, 10),
                            view.viewport()->mapToGlobal(QPoint(10, 10)));
    event.ignore();
    QCoreApplication::sendEvent(view.viewport(), &event);
    QVERIFY(!event.isAccepted());

    scene.addItem(bottomItem);
    topItem->ignoreEvent = true;
    bottomItem->ignoreEvent = true;

    QCoreApplication::sendEvent(view.viewport(), &event);
    QVERIFY(!event.isAccepted());
    QCOMPARE(topItem->gotEvent, true);
    QCOMPARE(topItem->eventWasAccepted, true);
    QCOMPARE(bottomItem->gotEvent, true);
    QCOMPARE(bottomItem->eventWasAccepted, true);

    topItem->ignoreEvent = false;
    topItem->gotEvent = false;
    bottomItem->gotEvent = false;

    QCoreApplication::sendEvent(view.viewport(), &event);
    QVERIFY(event.isAccepted());
    QCOMPARE(topItem->gotEvent, true);
    QCOMPARE(bottomItem->gotEvent, false);
    QCOMPARE(topItem->eventWasAccepted, true);
}
#endif // QT_NO_CONTEXTMENU

void tst_QGraphicsItem::itemIsMovable()
{
    QGraphicsRectItem *rect = new QGraphicsRectItem(-50, -50, 100, 100);
    rect->setFlag(QGraphicsItem::ItemIsMovable);

    QGraphicsScene scene;
    scene.addItem(rect);

    {
        QGraphicsSceneMouseEvent event(QEvent::GraphicsSceneMousePress);
        event.setButton(Qt::LeftButton);
        event.setButtons(Qt::LeftButton);
        QCoreApplication::sendEvent(&scene, &event);
    }
    {
        QGraphicsSceneMouseEvent event(QEvent::GraphicsSceneMouseMove);
        event.setButton(Qt::LeftButton);
        event.setButtons(Qt::LeftButton);
        QCoreApplication::sendEvent(&scene, &event);
    }
    QCOMPARE(rect->pos(), QPointF(0, 0));
    {
        QGraphicsSceneMouseEvent event(QEvent::GraphicsSceneMouseMove);
        event.setButtons(Qt::LeftButton);
        event.setScenePos(QPointF(10, 10));
        QCoreApplication::sendEvent(&scene, &event);
    }
    QCOMPARE(rect->pos(), QPointF(10, 10));
    {
        QGraphicsSceneMouseEvent event(QEvent::GraphicsSceneMouseMove);
        event.setButtons(Qt::RightButton);
        event.setScenePos(QPointF(20, 20));
        QCoreApplication::sendEvent(&scene, &event);
    }
    QCOMPARE(rect->pos(), QPointF(10, 10));
    {
        QGraphicsSceneMouseEvent event(QEvent::GraphicsSceneMouseMove);
        event.setButtons(Qt::LeftButton);
        event.setScenePos(QPointF(30, 30));
        QCoreApplication::sendEvent(&scene, &event);
    }
    QCOMPARE(rect->pos(), QPointF(30, 30));
}

class ItemAddScene : public QGraphicsScene
{
    Q_OBJECT
public:
    ItemAddScene()
    {
        QTimer::singleShot(500, this, &ItemAddScene::newTextItem);
    }

public slots:
    void newTextItem()
    {
        // Add a text item
        QGraphicsItem *item = addText("This item will not ensure that it's visible");
        item->setPos(.0, .0);
        item->show();
    }
};

void tst_QGraphicsItem::task141694_textItemEnsureVisible()
{
    ItemAddScene scene;
    scene.setSceneRect(-1000, -1000, 2000, 2000);

    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.setFixedSize(200, 200);
    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));

    view.ensureVisible(-1000, -1000, 5, 5);
    int hscroll = view.horizontalScrollBar()->value();
    int vscroll = view.verticalScrollBar()->value();

    QTest::qWait(10);

    // This should not cause the view to scroll
    QTRY_COMPARE(view.horizontalScrollBar()->value(), hscroll);
    QCOMPARE(view.verticalScrollBar()->value(), vscroll);
}

void tst_QGraphicsItem::task128696_textItemEnsureMovable()
{
    QGraphicsTextItem *item = new QGraphicsTextItem;
    item->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
    item->setTextInteractionFlags(Qt::TextEditorInteraction);
    item->setPlainText("abc de\nf ghi\n   j k l");

    QGraphicsScene scene;
    scene.setSceneRect(-100, -100, 200, 200);
    scene.addItem(item);

    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.setFixedSize(200, 200);
    view.show();

    QGraphicsSceneMouseEvent event1(QEvent::GraphicsSceneMousePress);
    event1.setScenePos(QPointF(0, 0));
    event1.setButton(Qt::LeftButton);
    event1.setButtons(Qt::LeftButton);
    QCoreApplication::sendEvent(&scene, &event1);
    QCOMPARE(scene.mouseGrabberItem(), item);

    QGraphicsSceneMouseEvent event2(QEvent::GraphicsSceneMouseMove);
    event2.setScenePos(QPointF(10, 10));
    event2.setButton(Qt::LeftButton);
    event2.setButtons(Qt::LeftButton);
    QCoreApplication::sendEvent(&scene, &event2);
    QCOMPARE(item->pos(), QPointF(10, 10));
}

void tst_QGraphicsItem::task177918_lineItemUndetected()
{
    QGraphicsScene scene;
    QGraphicsLineItem *line = scene.addLine(10, 10, 10, 10);
    QCOMPARE(line->boundingRect(), QRectF(10, 10, 0, 0));

    const QRectF rect(9, 9, 2, 2);
    QVERIFY(!scene.items(rect, Qt::IntersectsItemShape).isEmpty());
    QVERIFY(!scene.items(rect, Qt::ContainsItemShape).isEmpty());
    QVERIFY(!scene.items(rect, Qt::IntersectsItemBoundingRect).isEmpty());
    QVERIFY(!scene.items(rect, Qt::ContainsItemBoundingRect).isEmpty());
}

void tst_QGraphicsItem::task240400_clickOnTextItem_data()
{
    using Flags = QGraphicsItem::GraphicsItemFlags;
    QTest::addColumn<Flags>("flags");
    QTest::addColumn<Qt::TextInteractionFlags>("textFlags");
    QTest::newRow("editor, noflags")
        << Flags{}
        << Qt::TextInteractionFlags(Qt::TextEditorInteraction);
    QTest::newRow("editor, movable")
        << Flags(QGraphicsItem::ItemIsMovable)
        << Qt::TextInteractionFlags(Qt::TextEditorInteraction);
    QTest::newRow("editor, selectable")
        << Flags(QGraphicsItem::ItemIsSelectable)
        << Qt::TextInteractionFlags(Qt::TextEditorInteraction);
    QTest::newRow("editor, movable | selectable")
        << Flags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable)
        << Qt::TextInteractionFlags(Qt::TextEditorInteraction);
    QTest::newRow("noninteractive, noflags")
        << Flags{} << Qt::TextInteractionFlags(Qt::NoTextInteraction);
    QTest::newRow("noninteractive, movable")
        << Flags(QGraphicsItem::ItemIsMovable) << Qt::TextInteractionFlags(Qt::NoTextInteraction);
    QTest::newRow("noninteractive, selectable")
        << Flags(QGraphicsItem::ItemIsSelectable) << Qt::TextInteractionFlags(Qt::NoTextInteraction);
    QTest::newRow("noninteractive, movable | selectable")
        << Flags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable)
        << Qt::TextInteractionFlags(Qt::NoTextInteraction);
}

void tst_QGraphicsItem::task240400_clickOnTextItem()
{
    QFETCH(QGraphicsItem::GraphicsItemFlags, flags);
    QFETCH(Qt::TextInteractionFlags, textFlags);

    QGraphicsScene scene;
    QEvent activate(QEvent::WindowActivate);
    QCoreApplication::sendEvent(&scene, &activate);

    QGraphicsTextItem *item = scene.addText("Hello");
    item->setFlags(flags);
    item->setTextInteractionFlags(textFlags);
    const bool focusable = item->flags().testFlag(QGraphicsItem::ItemIsFocusable);
    QVERIFY(textFlags ? focusable : !focusable);

    int column = item->textCursor().columnNumber();
    QCOMPARE(column, 0);

    QVERIFY(!item->hasFocus());

    // Click in the top-left corner of the item
    {
        QGraphicsSceneMouseEvent event(QEvent::GraphicsSceneMousePress);
        event.setScenePos(item->sceneBoundingRect().topLeft() + QPointF(0.1, 0.1));
        event.setButton(Qt::LeftButton);
        event.setButtons(Qt::LeftButton);
        QCoreApplication::sendEvent(&scene, &event);
    }
    if (flags || textFlags)
        QCOMPARE(scene.mouseGrabberItem(), item);
    else
        QCOMPARE(scene.mouseGrabberItem(), nullptr);
    {
        QGraphicsSceneMouseEvent event(QEvent::GraphicsSceneMouseRelease);
        event.setScenePos(item->sceneBoundingRect().topLeft() + QPointF(0.1, 0.1));
        event.setButton(Qt::LeftButton);
        event.setButtons({});
        QCoreApplication::sendEvent(&scene, &event);
    }
    if (textFlags)
        QVERIFY(item->hasFocus());
    else
        QVERIFY(!item->hasFocus());
    QVERIFY(!scene.mouseGrabberItem());
    bool selectable = flags.testFlag(QGraphicsItem::ItemIsSelectable);
    QVERIFY(selectable ? item->isSelected() : !item->isSelected());

    // Now click in the middle and check that the cursor moved.
    {
        QGraphicsSceneMouseEvent event(QEvent::GraphicsSceneMousePress);
        event.setScenePos(item->sceneBoundingRect().center());
        event.setButton(Qt::LeftButton);
        event.setButtons(Qt::LeftButton);
        QCoreApplication::sendEvent(&scene, &event);
    }
    if (flags || textFlags)
        QCOMPARE(scene.mouseGrabberItem(), item);
    else
        QCOMPARE(scene.mouseGrabberItem(), nullptr);
    {
        QGraphicsSceneMouseEvent event(QEvent::GraphicsSceneMouseRelease);
        event.setScenePos(item->sceneBoundingRect().center());
        event.setButton(Qt::LeftButton);
        event.setButtons({});
        QCoreApplication::sendEvent(&scene, &event);
    }
    if (textFlags)
        QVERIFY(item->hasFocus());
    else
        QVERIFY(!item->hasFocus());
    QVERIFY(!scene.mouseGrabberItem());

    QVERIFY(selectable ? item->isSelected() : !item->isSelected());

    //
    if (textFlags.testFlag(Qt::TextEditorInteraction))
        QVERIFY(item->textCursor().columnNumber() > column);
    else
        QCOMPARE(item->textCursor().columnNumber(), 0);
}

class TextItem : public QGraphicsSimpleTextItem
{
public:
    using QGraphicsSimpleTextItem::QGraphicsSimpleTextItem;

    void paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget) override
    {
        updates++;
        QGraphicsSimpleTextItem::paint(painter, option, widget);
    }

    int updates = 0;
};

void tst_QGraphicsItem::ensureUpdateOnTextItem()
{
    QGraphicsScene scene;
    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));
    QCoreApplication::processEvents(); // Process all queued paint events
    TextItem *text1 = new TextItem(QLatin1String("123"));
    scene.addItem(text1);
    QTRY_COMPARE(text1->updates,1);

    //same bouding rect but we have to update
    text1->setText(QLatin1String("321"));
    QTRY_COMPARE(text1->updates,2);
}

void tst_QGraphicsItem::task243707_addChildBeforeParent()
{
    // Task reports that adding the child before the parent leads to an
    // inconsistent internal state that can cause a crash.  This test shows
    // one such crash.
    QGraphicsScene scene;
    QGraphicsWidget *widget = new QGraphicsWidget;
    QGraphicsWidget *widget2 = new QGraphicsWidget(widget);
    scene.addItem(widget2);
    QVERIFY(!widget2->parentItem());
    scene.addItem(widget);
    QVERIFY(!widget->commonAncestorItem(widget2));
    QVERIFY(!widget2->commonAncestorItem(widget));
}

void tst_QGraphicsItem::task197802_childrenVisibility()
{
    QGraphicsScene scene;
    QGraphicsRectItem item(QRectF(0,0,20,20));

    QGraphicsRectItem *item2 = new QGraphicsRectItem(QRectF(0,0,10,10), &item);
    scene.addItem(&item);

    //freshly created: both visible
    QVERIFY(item.isVisible());
    QVERIFY(item2->isVisible());

    //hide child: parent visible, child not
    item2->hide();
    QVERIFY(item.isVisible());
    QVERIFY(!item2->isVisible());

    //hide parent: parent and child invisible
    item.hide();
    QVERIFY(!item.isVisible());
    QVERIFY(!item2->isVisible());

    //ask to show the child: parent and child invisible anyways
    item2->show();
    QVERIFY(!item.isVisible());
    QVERIFY(!item2->isVisible());

    //show the parent: both parent and child visible
    item.show();
    QVERIFY(item.isVisible());
    QVERIFY(item2->isVisible());

    delete item2;
}

void tst_QGraphicsItem::boundingRegion_data()
{
    QTest::addColumn<QLineF>("line");
    QTest::addColumn<qreal>("granularity");
    QTest::addColumn<QTransform>("transform");
    QTest::addColumn<QRegion>("expectedRegion");

    QTest::newRow("(0, 0, 10, 10) | 0.0 | identity | {(0, 0, 10, 10)}") << QLineF(0, 0, 10, 10) << qreal(0.0) << QTransform()
                                                                        << QRegion(QRect(0, 0, 10, 10));
    QTest::newRow("(0, 0, 10, 0) | 0.0 | identity | {(0, 0, 10, 10)}") << QLineF(0, 0, 10, 0) << qreal(0.0) << QTransform()
                                                                       << QRegion(QRect(0, 0, 10, 1));
    QTest::newRow("(0, 0, 10, 0) | 0.5 | identity | {(0, 0, 10, 1)}") << QLineF(0, 0, 10, 0) << qreal(0.5) << QTransform()
                                                                      << QRegion(QRect(0, 0, 10, 1));
    QTest::newRow("(0, 0, 10, 0) | 1.0 | identity | {(0, 0, 10, 1)}") << QLineF(0, 0, 10, 0) << qreal(1.0) << QTransform()
                                                                      << QRegion(QRect(0, 0, 10, 1));
    QTest::newRow("(0, 0, 0, 10) | 0.0 | identity | {(0, 0, 10, 10)}") << QLineF(0, 0, 0, 10) << qreal(0.0) << QTransform()
                                                                       << QRegion(QRect(0, 0, 1, 10));
    QTest::newRow("(0, 0, 0, 10) | 0.5 | identity | {(0, 0, 1, 10)}") << QLineF(0, 0, 0, 10) << qreal(0.5) << QTransform()
                                                                      << QRegion(QRect(0, 0, 1, 10));
    QTest::newRow("(0, 0, 0, 10) | 1.0 | identity | {(0, 0, 1, 10)}") << QLineF(0, 0, 0, 10) << qreal(1.0) << QTransform()
                                                                      << QRegion(QRect(0, 0, 1, 10));
}

void tst_QGraphicsItem::boundingRegion()
{
    QFETCH(QLineF, line);
    QFETCH(qreal, granularity);
    QFETCH(QTransform, transform);
    QFETCH(QRegion, expectedRegion);

    QGraphicsLineItem item(line);
    item.setPen(QPen(Qt::black, 0));
    QCOMPARE(item.boundingRegionGranularity(), qreal(0.0));
    item.setBoundingRegionGranularity(granularity);
    QCOMPARE(item.boundingRegionGranularity(), granularity);
    QCOMPARE(item.boundingRegion(transform), expectedRegion);
}

void tst_QGraphicsItem::itemTransform_parentChild()
{
    QGraphicsScene scene;
    QGraphicsItem *parent = scene.addRect(0, 0, 100, 100);
    QGraphicsItem *child = scene.addRect(0, 0, 100, 100);
    child->setParentItem(parent);
    child->setPos(10, 10);
    child->setTransform(QTransform::fromScale(2, 2), true);
    child->setTransform(QTransform().rotate(90), true);

    QCOMPARE(child->itemTransform(parent).map(QPointF(10, 10)), QPointF(-10, 30));
    QCOMPARE(parent->itemTransform(child).map(QPointF(-10, 30)), QPointF(10, 10));
}

void tst_QGraphicsItem::itemTransform_siblings()
{
    QGraphicsScene scene;
    QGraphicsItem *parent = scene.addRect(0, 0, 100, 100);
    QGraphicsItem *brother = scene.addRect(0, 0, 100, 100);
    QGraphicsItem *sister = scene.addRect(0, 0, 100, 100);
    parent->setTransform(QTransform::fromScale(10, 5), true);
    parent->setTransform(QTransform().rotate(-180), true);
    parent->setTransform(QTransform().shear(2, 3), true);

    brother->setParentItem(parent);
    sister->setParentItem(parent);

    brother->setPos(10, 10);
    brother->setTransform(QTransform::fromScale(2, 2), true);
    brother->setTransform(QTransform().rotate(90), true);
    sister->setPos(10, 10);
    sister->setTransform(QTransform::fromScale(2, 2), true);
    sister->setTransform(QTransform().rotate(90), true);

    QCOMPARE(brother->itemTransform(sister).map(QPointF(10, 10)), QPointF(10, 10));
    QCOMPARE(sister->itemTransform(brother).map(QPointF(10, 10)), QPointF(10, 10));
}

void tst_QGraphicsItem::itemTransform_unrelated()
{
    QGraphicsScene scene;
    QGraphicsItem *stranger1 = scene.addRect(0, 0, 100, 100);
    QGraphicsItem *stranger2 = scene.addRect(0, 0, 100, 100);
    stranger1->setPos(10, 10);
    stranger1->setTransform(QTransform::fromScale(2, 2), true);
    stranger1->setTransform(QTransform().rotate(90), true);
    stranger2->setPos(10, 10);
    stranger2->setTransform(QTransform::fromScale(2, 2), true);
    stranger2->setTransform(QTransform().rotate(90), true);

    QCOMPARE(stranger1->itemTransform(stranger2).map(QPointF(10, 10)), QPointF(10, 10));
    QCOMPARE(stranger2->itemTransform(stranger1).map(QPointF(10, 10)), QPointF(10, 10));
}

void tst_QGraphicsItem::opacity_data()
{
    QTest::addColumn<qreal>("p_opacity");
    QTest::addColumn<int>("p_opacityFlags");
    QTest::addColumn<qreal>("c1_opacity");
    QTest::addColumn<int>("c1_opacityFlags");
    QTest::addColumn<qreal>("c2_opacity");
    QTest::addColumn<int>("c2_opacityFlags");
    QTest::addColumn<qreal>("p_effectiveOpacity");
    QTest::addColumn<qreal>("c1_effectiveOpacity");
    QTest::addColumn<qreal>("c2_effectiveOpacity");
    QTest::addColumn<qreal>("c3_effectiveOpacity");

    // Modify the opacity and see how it propagates
    QTest::newRow("A: 1.0 0 1.0 0 1.0 1.0 1.0 1.0 1.0") << qreal(1.0) << 0 << qreal(1.0) << 0 << qreal(1.0) << 0
                                                        << qreal(1.0) << qreal(1.0) << qreal(1.0) << qreal(1.0);
    QTest::newRow("B: 0.5 0 1.0 0 1.0 1.0 1.0 1.0 1.0") << qreal(0.5) << 0 << qreal(1.0) << 0 << qreal(1.0) << 0
                                                        << qreal(0.5) << qreal(0.5) << qreal(0.5) << qreal(0.5);
    QTest::newRow("C: 0.5 0 0.1 0 1.0 1.0 1.0 1.0 1.0") << qreal(0.5) << 0 << qreal(0.1) << 0 << qreal(1.0) << 0
                                                        << qreal(0.5) << qreal(0.05) << qreal(0.05) << qreal(0.05);
    QTest::newRow("D: 0.0 0 1.0 0 1.0 1.0 1.0 1.0 1.0") << qreal(0.0) << 0 << qreal(1.0) << 0 << qreal(1.0) << 0
                                                        << qreal(0.0) << qreal(0.0) << qreal(0.0) << qreal(0.0);

    // Parent doesn't propagate to children - now modify the opacity and see how it propagates
    int flags = QGraphicsItem::ItemDoesntPropagateOpacityToChildren;
    QTest::newRow("E: 1.0 2 1.0 0 1.0 1.0 1.0 1.0 1.0") << qreal(1.0) << flags << qreal(1.0) << 0 << qreal(1.0) << 0
                                                        << qreal(1.0) << qreal(1.0) << qreal(1.0) << qreal(1.0);
    QTest::newRow("F: 0.5 2 1.0 0 1.0 1.0 1.0 1.0 1.0") << qreal(0.5) << flags << qreal(1.0) << 0 << qreal(1.0) << 0
                                                        << qreal(0.5) << qreal(1.0) << qreal(1.0) << qreal(1.0);
    QTest::newRow("G: 0.5 2 0.1 0 1.0 1.0 1.0 1.0 1.0") << qreal(0.5) << flags << qreal(0.1) << 0 << qreal(1.0) << 0
                                                        << qreal(0.5) << qreal(0.1) << qreal(0.1) << qreal(0.1);
    QTest::newRow("H: 0.0 2 1.0 0 1.0 1.0 1.0 1.0 1.0") << qreal(0.0) << flags << qreal(1.0) << 0 << qreal(1.0) << 0
                                                        << qreal(0.0) << qreal(1.0) << qreal(1.0) << qreal(1.0);

    // Child ignores parent - now modify the opacity and see how it propagates
    flags = QGraphicsItem::ItemIgnoresParentOpacity;
    QTest::newRow("I: 1.0 0 1.0 1 1.0 1.0 1.0 1.0 1.0") << qreal(1.0) << 0 << qreal(1.0) << flags << qreal(1.0) << 0
                                                        << qreal(1.0) << qreal(1.0) << qreal(1.0) << qreal(1.0);
    QTest::newRow("J: 1.0 0 1.0 1 1.0 1.0 1.0 1.0 1.0") << qreal(0.5) << 0 << qreal(0.5) << flags << qreal(0.5) << 0
                                                        << qreal(0.5) << qreal(0.5) << qreal(0.25) << qreal(0.25);
    QTest::newRow("K: 1.0 0 1.0 1 1.0 1.0 1.0 1.0 1.0") << qreal(0.2) << 0 << qreal(0.2) << flags << qreal(0.2) << 0
                                                        << qreal(0.2) << qreal(0.2) << qreal(0.04) << qreal(0.04);
    QTest::newRow("L: 1.0 0 1.0 1 1.0 1.0 1.0 1.0 1.0") << qreal(0.0) << 0 << qreal(0.0) << flags << qreal(0.0) << 0
                                                        << qreal(0.0) << qreal(0.0) << qreal(0.0) << qreal(0.0);

    // Child ignores parent and doesn't propagate - now modify the opacity and see how it propagates
    flags = QGraphicsItem::ItemIgnoresParentOpacity | QGraphicsItem::ItemDoesntPropagateOpacityToChildren;
    QTest::newRow("M: 1.0 0 1.0 1 1.0 1.0 1.0 1.0 1.0") << qreal(1.0) << 0 // p
                                                        << qreal(1.0) << flags // c1 (no prop)
                                                        << qreal(1.0) << 0 // c2
                                                        << qreal(1.0) << qreal(1.0) << qreal(1.0) << qreal(1.0);
    QTest::newRow("M: 0.5 0 1.0 1 1.0 1.0 1.0 1.0 1.0") << qreal(0.5) << 0 // p
                                                        << qreal(1.0) << flags // c1 (no prop)
                                                        << qreal(1.0) << 0 // c2
                                                        << qreal(0.5) << qreal(1.0) << qreal(1.0) << qreal(1.0);
    QTest::newRow("M: 0.5 0 0.5 1 1.0 1.0 1.0 1.0 1.0") << qreal(0.5) << 0 // p
                                                        << qreal(0.5) << flags // c1 (no prop)
                                                        << qreal(1.0) << 0 // c2
                                                        << qreal(0.5) << qreal(0.5) << qreal(1.0) << qreal(1.0);
    QTest::newRow("M: 0.5 0 0.5 1 0.5 1.0 1.0 1.0 1.0") << qreal(0.5) << 0 // p
                                                        << qreal(0.5) << flags // c1 (no prop)
                                                        << qreal(0.5) << 0 // c2
                                                        << qreal(0.5) << qreal(0.5) << qreal(0.5) << qreal(0.5);
    QTest::newRow("M: 1.0 0 0.5 1 0.5 1.0 1.0 1.0 1.0") << qreal(1.0) << 0 // p
                                                        << qreal(0.5) << flags // c1 (no prop)
                                                        << qreal(0.5) << 0 // c2
                                                        << qreal(1.0) << qreal(0.5) << qreal(0.5) << qreal(0.5);
}

void tst_QGraphicsItem::opacity()
{
    QFETCH(qreal, p_opacity);
    QFETCH(int, p_opacityFlags);
    QFETCH(qreal, p_effectiveOpacity);
    QFETCH(qreal, c1_opacity);
    QFETCH(int, c1_opacityFlags);
    QFETCH(qreal, c1_effectiveOpacity);
    QFETCH(qreal, c2_opacity);
    QFETCH(int, c2_opacityFlags);
    QFETCH(qreal, c2_effectiveOpacity);
    QFETCH(qreal, c3_effectiveOpacity);

    const QScopedPointer<QGraphicsRectItem> p(new QGraphicsRectItem);
    QGraphicsRectItem *c1 = new QGraphicsRectItem(p.data());
    QGraphicsRectItem *c2 = new QGraphicsRectItem(c1);
    QGraphicsRectItem *c3 = new QGraphicsRectItem(c2);

    QCOMPARE(p->opacity(), qreal(1.0));
    QCOMPARE(p->effectiveOpacity(), qreal(1.0));
    int opacityMask = QGraphicsItem::ItemIgnoresParentOpacity | QGraphicsItem::ItemDoesntPropagateOpacityToChildren;
    QVERIFY(!(p->flags() & opacityMask));

    p->setOpacity(p_opacity);
    c1->setOpacity(c1_opacity);
    c2->setOpacity(c2_opacity);
    p->setFlags(QGraphicsItem::GraphicsItemFlags(p->flags() | p_opacityFlags));
    c1->setFlags(QGraphicsItem::GraphicsItemFlags(c1->flags() | c1_opacityFlags));
    c2->setFlags(QGraphicsItem::GraphicsItemFlags(c2->flags() | c2_opacityFlags));

    QCOMPARE(int(p->flags() & opacityMask), p_opacityFlags);
    QCOMPARE(int(c1->flags() & opacityMask), c1_opacityFlags);
    QCOMPARE(int(c2->flags() & opacityMask), c2_opacityFlags);
    QCOMPARE(p->opacity(), p_opacity);
    QCOMPARE(p->effectiveOpacity(), p_effectiveOpacity);
    QCOMPARE(c1->effectiveOpacity(), c1_effectiveOpacity);
    QCOMPARE(c2->effectiveOpacity(), c2_effectiveOpacity);
    QCOMPARE(c3->effectiveOpacity(), c3_effectiveOpacity);
}

void tst_QGraphicsItem::opacity2()
{
    EventTester *parent = new EventTester;
    EventTester *child = new EventTester(parent);
    EventTester *grandChild = new EventTester(child);

    QGraphicsScene scene;
    scene.addItem(parent);

    MyGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));
    if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation))
        QVERIFY(QTest::qWaitForWindowActive(&view));
    QCoreApplication::processEvents(); // Process all queued paint events
    QTRY_VERIFY(view.repaints >= 1);

#define RESET_REPAINT_COUNTERS \
    parent->repaints = 0; \
    child->repaints = 0; \
    grandChild->repaints = 0; \
    view.repaints = 0;

    RESET_REPAINT_COUNTERS

    child->setOpacity(0.0);
    QTRY_COMPARE(view.repaints, 1);
    QCOMPARE(parent->repaints, 1);
    QCOMPARE(child->repaints, 0);
    QCOMPARE(grandChild->repaints, 0);

    RESET_REPAINT_COUNTERS

    child->setOpacity(1.0);
    QTRY_COMPARE(view.repaints, 1);
    QCOMPARE(parent->repaints, 1);
    QCOMPARE(child->repaints, 1);
    QCOMPARE(grandChild->repaints, 1);

    RESET_REPAINT_COUNTERS

    parent->setOpacity(0.0);
    QTRY_COMPARE(view.repaints, 1);
    QCOMPARE(parent->repaints, 0);
    QCOMPARE(child->repaints, 0);
    QCOMPARE(grandChild->repaints, 0);

    RESET_REPAINT_COUNTERS

    parent->setOpacity(1.0);
    QTRY_COMPARE(view.repaints, 1);
    QCOMPARE(parent->repaints, 1);
    QCOMPARE(child->repaints, 1);
    QCOMPARE(grandChild->repaints, 1);

    grandChild->setFlag(QGraphicsItem::ItemIgnoresParentOpacity);
    RESET_REPAINT_COUNTERS

    child->setOpacity(0.0);
    QTRY_COMPARE(view.repaints, 1);
    QCOMPARE(parent->repaints, 1);
    QCOMPARE(child->repaints, 0);
    QCOMPARE(grandChild->repaints, 1);

    RESET_REPAINT_COUNTERS

    child->setOpacity(0.0); // Already 0.0; no change.
    QTest::qWait(10);
    QCOMPARE(view.repaints, 0);
    QCOMPARE(parent->repaints, 0);
    QCOMPARE(child->repaints, 0);
    QCOMPARE(grandChild->repaints, 0);
}

void tst_QGraphicsItem::opacityZeroUpdates()
{
    EventTester *parent = new EventTester;
    EventTester *child = new EventTester(parent);

    child->setPos(10, 10);

    QGraphicsScene scene;
    scene.addItem(parent);

    MyGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));
    if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) {
        view.window()->activateWindow();
        QVERIFY(QTest::qWaitForWindowActive(&view));
    }
    QCoreApplication::processEvents(); // Process all queued paint events
    QTRY_VERIFY(view.repaints > 0);

    view.reset();
    parent->setOpacity(0.0);

    QTRY_COMPARE(view.repaints, 1);

    // transforming items bounding rect to view coordinates
    const QRect childDeviceBoundingRect = child->deviceTransform(view.viewportTransform())
                                           .mapRect(child->boundingRect()).toRect();
    const QRect parentDeviceBoundingRect = parent->deviceTransform(view.viewportTransform())
                                           .mapRect(parent->boundingRect()).toRect();

    QRegion expectedRegion = parentDeviceBoundingRect.adjusted(-2, -2, 2, 2);
    expectedRegion += childDeviceBoundingRect.adjusted(-2, -2, 2, 2);

    COMPARE_REGIONS(view.paintedRegion, expectedRegion);
}

class StacksBehindParentHelper : public QGraphicsRectItem
{
public:
    StacksBehindParentHelper(GraphicsItems *paintedItems, const QRectF &rect, QGraphicsItem *parent = nullptr)
        : QGraphicsRectItem(rect, parent), m_paintedItems(paintedItems)
    { }

    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override
    {
        QGraphicsRectItem::paint(painter, option, widget);
        m_paintedItems->append(this);
    }

private:
    GraphicsItems *m_paintedItems;
};

void tst_QGraphicsItem::itemStacksBehindParent()
{
    StacksBehindParentHelper *parent1 = new StacksBehindParentHelper(&paintedItems, QRectF(0, 0, 100, 50));
    StacksBehindParentHelper *child11 = new StacksBehindParentHelper(&paintedItems, QRectF(-10, 10, 50, 50), parent1);
    StacksBehindParentHelper *grandChild111 = new StacksBehindParentHelper(&paintedItems, QRectF(-20, 20, 50, 50), child11);
    StacksBehindParentHelper *child12 = new StacksBehindParentHelper(&paintedItems, QRectF(60, 10, 50, 50), parent1);
    StacksBehindParentHelper *grandChild121 = new StacksBehindParentHelper(&paintedItems, QRectF(70, 20, 50, 50), child12);

    StacksBehindParentHelper *parent2 = new StacksBehindParentHelper(&paintedItems, QRectF(0, 0, 100, 50));
    StacksBehindParentHelper *child21 = new StacksBehindParentHelper(&paintedItems, QRectF(-10, 10, 50, 50), parent2);
    StacksBehindParentHelper *grandChild211 = new StacksBehindParentHelper(&paintedItems, QRectF(-20, 20, 50, 50), child21);
    StacksBehindParentHelper *child22 = new StacksBehindParentHelper(&paintedItems, QRectF(60, 10, 50, 50), parent2);
    StacksBehindParentHelper *grandChild221 = new StacksBehindParentHelper(&paintedItems, QRectF(70, 20, 50, 50), child22);

    parent1->setData(0, "parent1");
    child11->setData(0, "child11");
    grandChild111->setData(0, "grandChild111");
    child12->setData(0, "child12");
    grandChild121->setData(0, "grandChild121");
    parent2->setData(0, "parent2");
    child21->setData(0, "child21");
    grandChild211->setData(0, "grandChild211");
    child22->setData(0, "child22");
    grandChild221->setData(0, "grandChild221");

    // Disambiguate siblings
    parent1->setZValue(1);
    child11->setZValue(1);
    child21->setZValue(1);

    QGraphicsScene scene;
    scene.addItem(parent1);
    scene.addItem(parent2);

    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));
    QTRY_VERIFY(!paintedItems.isEmpty());
    paintedItems.clear();
    view.viewport()->update();
    QApplication::processEvents();
    QRectF rect(0, 0, 100, 100);
    const GraphicsItemsList expected1{grandChild111, child11, grandChild121, child12, parent1,
                                      grandChild211, child21, grandChild221, child22, parent2};
    QTRY_COMPARE(scene.items(rect), expected1);

    const GraphicsItems expected2{parent2, child22, grandChild221, child21, grandChild211,
                                  parent1, child12, grandChild121, child11, grandChild111};
    QTRY_COMPARE(paintedItems, expected2);

    child11->setFlag(QGraphicsItem::ItemStacksBehindParent);
    scene.update();
    paintedItems.clear();

    const GraphicsItemsList expected3{grandChild121, child12, parent1, grandChild111, child11,
                                      grandChild211, child21, grandChild221, child22, parent2};

    QTRY_COMPARE(scene.items(rect), expected3);
    const GraphicsItems expected4{parent2, child22, grandChild221, child21, grandChild211, child11, grandChild111,
                                  parent1, child12, grandChild121};
    QTRY_COMPARE(paintedItems, expected4);

    child12->setFlag(QGraphicsItem::ItemStacksBehindParent);
    paintedItems.clear();
    scene.update();

    const GraphicsItemsList expected5{parent1, grandChild111, child11, grandChild121, child12,
                                      grandChild211, child21, grandChild221, child22, parent2};
    QTRY_COMPARE(scene.items(rect), expected5);

    const GraphicsItems expected6{parent2, child22, grandChild221, child21, grandChild211,
                                  child12, grandChild121, child11, grandChild111, parent1};
    QTRY_COMPARE(paintedItems, expected6);
}

class ClippingAndTransformsScene : public QGraphicsScene
{
    Q_OBJECT
public:
    GraphicsItems drawnItems;
protected:
    void drawItems(QPainter *painter, int numItems, QGraphicsItem *items[],
                   const QStyleOptionGraphicsItem options[], QWidget *widget = nullptr) override
    {
        drawnItems.clear();
        for (int i = 0; i < numItems; ++i)
            drawnItems << items[i];
        QGraphicsScene::drawItems(painter, numItems, items, options, widget);
    }
};

void tst_QGraphicsItem::nestedClipping()
{
    ClippingAndTransformsScene scene;
    scene.setSceneRect(-50, -50, 200, 200);

    QGraphicsRectItem *root = new QGraphicsRectItem(QRectF(0, 0, 100, 100));
    root->setBrush(QColor(0, 0, 255));
    root->setFlag(QGraphicsItem::ItemClipsChildrenToShape);
    QGraphicsRectItem *l1 = new QGraphicsRectItem(QRectF(0, 0, 100, 100));
    l1->setParentItem(root);
    l1->setPos(-50, 0);
    l1->setBrush(QColor(255, 0, 0));
    l1->setFlag(QGraphicsItem::ItemClipsChildrenToShape);
    QGraphicsEllipseItem *l2 = new QGraphicsEllipseItem(QRectF(0, 0, 100, 100));
    l2->setParentItem(l1);
    l2->setPos(50, 50);
    l2->setFlag(QGraphicsItem::ItemClipsChildrenToShape);
    l2->setBrush(QColor(255, 255, 0));
    QGraphicsRectItem *l3 = new QGraphicsRectItem(QRectF(0, 0, 25, 25));
    l3->setParentItem(l2);
    l3->setBrush(QColor(0, 255, 0));
    l3->setPos(50 - 12, -12);

    scene.addItem(root);

    root->setData(0, "root");
    l1->setData(0, "l1");
    l2->setData(0, "l2");
    l3->setData(0, "l3");

    QGraphicsView view(&scene);
    view.setOptimizationFlag(QGraphicsView::IndirectPainting);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));

    GraphicsItems expected{root, l1, l2, l3};
    QTRY_COMPARE(scene.drawnItems, expected);

    QImage image(200, 200, QImage::Format_ARGB32_Premultiplied);
    image.fill(0);

    QPainter painter(&image);
    scene.render(&painter);
    painter.end();

    // Check transparent areas
    QCOMPARE(image.pixel(100, 25), qRgba(0, 0, 0, 0));
    QCOMPARE(image.pixel(100, 175), qRgba(0, 0, 0, 0));
    QCOMPARE(image.pixel(25, 100), qRgba(0, 0, 0, 0));
    QCOMPARE(image.pixel(175, 100), qRgba(0, 0, 0, 0));
    QCOMPARE(image.pixel(70, 80), qRgba(255, 0, 0, 255));
    QCOMPARE(image.pixel(80, 130), qRgba(255, 255, 0, 255));
    QCOMPARE(image.pixel(92, 105), qRgba(0, 255, 0, 255));
    QCOMPARE(image.pixel(105, 105), qRgba(0, 0, 255, 255));
#if 0
    // Enable this to compare if the test starts failing.
    image.save("nestedClipping_reference.png");
#endif
}

class TransformDebugItem : public QGraphicsRectItem
{
public:
    TransformDebugItem()
        : QGraphicsRectItem(QRectF(-10, -10, 20, 20))
    {
        setPen(QPen(Qt::black, 0));
        setBrush(QColor(QRandomGenerator::global()->bounded(256), QRandomGenerator::global()->bounded(256), QRandomGenerator::global()->bounded(256)));
    }

    QTransform x;

    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
               QWidget *widget = nullptr) override
    {
        x = painter->worldTransform();
        QGraphicsRectItem::paint(painter, option, widget);
    }
};

void tst_QGraphicsItem::nestedClippingTransforms()
{
    TransformDebugItem *rootClipper = new TransformDebugItem;
    rootClipper->setFlag(QGraphicsItem::ItemClipsChildrenToShape);
    TransformDebugItem *child = new TransformDebugItem;
    child->setParentItem(rootClipper);
    child->setPos(2, 2);
    TransformDebugItem *grandChildClipper = new TransformDebugItem;
    grandChildClipper->setParentItem(child);
    grandChildClipper->setFlag(QGraphicsItem::ItemClipsChildrenToShape);
    grandChildClipper->setPos(4, 4);
    TransformDebugItem *greatGrandChild = new TransformDebugItem;
    greatGrandChild->setPos(2, 2);
    greatGrandChild->setParentItem(grandChildClipper);
    TransformDebugItem *grandChildClipper2 = new TransformDebugItem;
    grandChildClipper2->setParentItem(child);
    grandChildClipper2->setFlag(QGraphicsItem::ItemClipsChildrenToShape);
    grandChildClipper2->setPos(8, 8);
    TransformDebugItem *greatGrandChild2 = new TransformDebugItem;
    greatGrandChild2->setPos(2, 2);
    greatGrandChild2->setParentItem(grandChildClipper2);
    TransformDebugItem *grandChildClipper3 = new TransformDebugItem;
    grandChildClipper3->setParentItem(child);
    grandChildClipper3->setFlag(QGraphicsItem::ItemClipsChildrenToShape);
    grandChildClipper3->setPos(12, 12);
    TransformDebugItem *greatGrandChild3 = new TransformDebugItem;
    greatGrandChild3->setPos(2, 2);
    greatGrandChild3->setParentItem(grandChildClipper3);

    QGraphicsScene scene;
    scene.addItem(rootClipper);

    QImage image(scene.itemsBoundingRect().size().toSize(), QImage::Format_ARGB32_Premultiplied);
    image.fill(0);
    QPainter p(&image);
    scene.render(&p);
    p.end();

    QCOMPARE(rootClipper->x, QTransform(1, 0, 0, 0, 1, 0, 10, 10, 1));
    QCOMPARE(child->x, QTransform(1, 0, 0, 0, 1, 0, 12, 12, 1));
    QCOMPARE(grandChildClipper->x, QTransform(1, 0, 0, 0, 1, 0, 16, 16, 1));
    QCOMPARE(greatGrandChild->x, QTransform(1, 0, 0, 0, 1, 0, 18, 18, 1));
    QCOMPARE(grandChildClipper2->x, QTransform(1, 0, 0, 0, 1, 0, 20, 20, 1));
    QCOMPARE(greatGrandChild2->x, QTransform(1, 0, 0, 0, 1, 0, 22, 22, 1));
    QCOMPARE(grandChildClipper3->x, QTransform(1, 0, 0, 0, 1, 0, 24, 24, 1));
    QCOMPARE(greatGrandChild3->x, QTransform(1, 0, 0, 0, 1, 0, 26, 26, 1));
}

void tst_QGraphicsItem::sceneTransformCache()
{
    // Test that an item's scene transform is updated correctly when the
    // parent is transformed.
    QGraphicsScene scene;

    const QScopedPointer<QGraphicsRectItem> rect(scene.addRect(0, 0, 100, 100));
    rect->setPen(QPen(Qt::black, 0));
    QGraphicsRectItem *rect2 = scene.addRect(0, 0, 100, 100);
    rect2->setPen(QPen(Qt::black, 0));
    rect2->setParentItem(rect.data());
    rect2->setTransform(QTransform().rotate(90), true);
    rect->setTransform(QTransform::fromTranslate(0, 50), true);
    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));
    rect->setTransform(QTransform::fromTranslate(0, 100), true);
    QTransform x;
    x.translate(0, 150);
    x.rotate(90);
    QCOMPARE(rect2->sceneTransform(), x);

    scene.removeItem(rect.data());

    //Crazy use case : rect4 child of rect3 so the transformation of rect4 will be cached.Good!
    //We remove rect4 from the scene, then the validTransform bit flag is set to 0 and the index of the cache
    //add to the freeTransformSlots. The problem was that sceneTransformIndex was not set to -1 so if a new item arrive
    //with a child (rect6) that will be cached then it will take the freeSlot (ex rect4) and put it his transform. But if rect4 is
    //added back to the scene then it will set the transform to his old sceneTransformIndex value that will erase the new
    //value of rect6 so rect6 transform will be wrong.
    QGraphicsRectItem *rect3 = scene.addRect(0, 0, 100, 100);
    QGraphicsRectItem *rect4 = scene.addRect(0, 0, 100, 100);
    rect3->setPos(QPointF(10,10));
    rect3->setPen(QPen(Qt::black, 0));

    rect4->setParentItem(rect3);
    rect4->setPos(QPointF(10,10));
    rect4->setPen(QPen(Qt::black, 0));

    QCOMPARE(rect4->mapToScene(rect4->boundingRect().topLeft()), QPointF(20,20));

    scene.removeItem(rect4);
    //rect4 transform is local only
    QCOMPARE(rect4->mapToScene(rect4->boundingRect().topLeft()), QPointF(10,10));

    QGraphicsRectItem *rect5 = scene.addRect(0, 0, 100, 100);
    QGraphicsRectItem *rect6 = scene.addRect(0, 0, 100, 100);
    rect5->setPos(QPointF(20,20));
    rect5->setPen(QPen(Qt::black, 0));

    rect6->setParentItem(rect5);
    rect6->setPos(QPointF(10,10));
    rect6->setPen(QPen(Qt::black, 0));
    //test if rect6 transform is ok
    QCOMPARE(rect6->mapToScene(rect6->boundingRect().topLeft()), QPointF(30,30));

    scene.addItem(rect4);

    QCOMPARE(rect4->mapToScene(rect4->boundingRect().topLeft()), QPointF(10,10));
    //test if rect6 transform is still correct
    QCOMPARE(rect6->mapToScene(rect6->boundingRect().topLeft()), QPointF(30,30));
}

void tst_QGraphicsItem::tabChangesFocus_data()
{
    QTest::addColumn<bool>("tabChangesFocus");
    QTest::newRow("tab changes focus") << true;
    QTest::newRow("tab doesn't change focus") << false;
}

void tst_QGraphicsItem::tabChangesFocus()
{
    if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation))
        QSKIP("Window activation is not supported");

    QFETCH(bool, tabChangesFocus);

    QGraphicsScene scene;
    QGraphicsTextItem *item = scene.addText("Hello");
    item->setTabChangesFocus(tabChangesFocus);
    item->setTextInteractionFlags(Qt::TextEditorInteraction);
    item->setFocus();

    QDial *dial1 = new QDial;
    QGraphicsView *view = new QGraphicsView(&scene);

    QDial *dial2 = new QDial;
    QVBoxLayout *layout = new QVBoxLayout;
    layout->addWidget(dial1);
    layout->addWidget(view);
    layout->addWidget(dial2);

    QWidget widget;
    widget.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    widget.setLayout(layout);
    widget.show();
    view->window()->activateWindow();
    QVERIFY(QTest::qWaitForWindowActive(&widget));

    QTRY_VERIFY(scene.isActive());

    dial1->setFocus();
    QTRY_VERIFY(dial1->hasFocus());

    QTest::keyPress(QApplication::focusWidget(), Qt::Key_Tab);
    QTRY_VERIFY(view->hasFocus());
    QTRY_VERIFY(item->hasFocus());

    QTest::keyPress(QApplication::focusWidget(), Qt::Key_Tab);

    if (tabChangesFocus) {
        QTRY_VERIFY(!view->hasFocus());
        QTRY_VERIFY(!item->hasFocus());
        QTRY_VERIFY(dial2->hasFocus());
    } else {
        QTRY_VERIFY(view->hasFocus());
        QTRY_VERIFY(item->hasFocus());
        QCOMPARE(item->toPlainText(), QString("\tHello"));
    }
}

void tst_QGraphicsItem::cacheMode()
{
    if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive))
        QSKIP("Wayland: This fails. Figure out why.");

    QGraphicsScene scene(0, 0, 100, 100);
    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.resize(150, 150);
    view.show();
    view.window()->activateWindow();
    QVERIFY(QTest::qWaitForWindowExposed(&view));
    QVERIFY(QTest::qWaitForWindowActive(&view));
    QCoreApplication::processEvents(); // Process all queued paint events

    EventTester *tester = new EventTester;
    EventTester *testerChild = new EventTester;
    testerChild->setParentItem(tester);
    EventTester *testerChild2 = new EventTester;
    testerChild2->setParentItem(testerChild);
    testerChild2->setFlag(QGraphicsItem::ItemIgnoresTransformations);

    scene.addItem(tester);

    for (int i = 0; i < 2; ++i) {
        // No visual change.
        QTRY_COMPARE(tester->repaints, 1);
        QCOMPARE(testerChild->repaints, 1);
        QCOMPARE(testerChild2->repaints, 1);
        tester->setCacheMode(QGraphicsItem::NoCache);
        testerChild->setCacheMode(QGraphicsItem::NoCache);
        testerChild2->setCacheMode(QGraphicsItem::NoCache);
        QTest::qWait(25);
        QTRY_COMPARE(tester->repaints, 1);
        QCOMPARE(testerChild->repaints, 1);
        QCOMPARE(testerChild2->repaints, 1);
        tester->setCacheMode(QGraphicsItem::DeviceCoordinateCache);
        testerChild->setCacheMode(QGraphicsItem::DeviceCoordinateCache);
        testerChild2->setCacheMode(QGraphicsItem::DeviceCoordinateCache);
        QTest::qWait(25);
    }

    // The first move causes a repaint as the item is painted into its pixmap.
    // (Only occurs if the item has previously been painted without cache).
    tester->setPos(10, 10);
    testerChild->setPos(10, 10);
    testerChild2->setPos(10, 10);
    QTRY_COMPARE(tester->repaints, 2);
    QCOMPARE(testerChild->repaints, 2);
    QCOMPARE(testerChild2->repaints, 2);

    // Consecutive moves should not repaint.
    tester->setPos(20, 20);
    testerChild->setPos(20, 20);
    testerChild2->setPos(20, 20);
    QTest::qWait(250);
    QCOMPARE(tester->repaints, 2);
    QCOMPARE(testerChild->repaints, 2);
    QCOMPARE(testerChild2->repaints, 2);

    // Translating does not result in a repaint.
    tester->setTransform(QTransform::fromTranslate(10, 10), true);
    QTest::qWait(25);
    QCOMPARE(tester->repaints, 2);
    QCOMPARE(testerChild->repaints, 2);
    QCOMPARE(testerChild2->repaints, 2);

    // Rotating results in a repaint.
    tester->setTransform(QTransform().rotate(45), true);
    QTRY_COMPARE(tester->repaints, 3);
    QCOMPARE(testerChild->repaints, 3);
    QCOMPARE(testerChild2->repaints, 2);

    // Change to ItemCoordinateCache (triggers repaint).
    tester->setCacheMode(QGraphicsItem::ItemCoordinateCache); // autosize
    testerChild->setCacheMode(QGraphicsItem::ItemCoordinateCache); // autosize
    testerChild2->setCacheMode(QGraphicsItem::ItemCoordinateCache); // autosize
    QTRY_COMPARE(tester->repaints, 4);
    QCOMPARE(testerChild->repaints, 4);
    QCOMPARE(testerChild2->repaints, 3);

    // Rotating items with ItemCoordinateCache doesn't cause a repaint.
    tester->setTransform(QTransform().rotate(22), true);
    testerChild->setTransform(QTransform().rotate(22), true);
    testerChild2->setTransform(QTransform().rotate(22), true);
    QTest::qWait(25);
    QCOMPARE(tester->repaints, 4);
    QCOMPARE(testerChild->repaints, 4);
    QCOMPARE(testerChild2->repaints, 3);
    tester->resetTransform();
    testerChild->resetTransform();
    testerChild2->resetTransform();

    // Explicit update causes a repaint.
    tester->update(0, 0, 5, 5);
    QTRY_COMPARE(tester->repaints, 5);
    QCOMPARE(testerChild->repaints, 4);
    QCOMPARE(testerChild2->repaints, 3);

    // Updating outside the item's bounds does not cause a repaint.
    tester->update(10, 10, 5, 5);
    QTest::qWait(25);
    QCOMPARE(tester->repaints, 5);
    QCOMPARE(testerChild->repaints, 4);
    QCOMPARE(testerChild2->repaints, 3);

    // Resizing an item should cause a repaint of that item. (because of
    // autosize).
    tester->setGeometry(QRectF(-15, -15, 30, 30));
    QTRY_COMPARE(tester->repaints, 6);
    QCOMPARE(testerChild->repaints, 4);
    QCOMPARE(testerChild2->repaints, 3);

    // Set fixed size.
    tester->setCacheMode(QGraphicsItem::ItemCoordinateCache, QSize(30, 30));
    testerChild->setCacheMode(QGraphicsItem::ItemCoordinateCache, QSize(30, 30));
    testerChild2->setCacheMode(QGraphicsItem::ItemCoordinateCache, QSize(30, 30));
    QTRY_COMPARE(tester->repaints, 7);
    QCOMPARE(testerChild->repaints, 5);
    QCOMPARE(testerChild2->repaints, 4);

    // Resizing the item should cause a repaint.
    testerChild->setGeometry(QRectF(-15, -15, 30, 30));
    QTRY_COMPARE(testerChild->repaints, 6);
    QCOMPARE(tester->repaints, 7);
    QCOMPARE(testerChild2->repaints, 4);

    // Scaling the view does not cause a repaint.
    view.scale(0.7, 0.7);
    QTest::qWait(25);
    QCOMPARE(tester->repaints, 7);
    QCOMPARE(testerChild->repaints, 6);
    QCOMPARE(testerChild2->repaints, 4);

    // Switch to device coordinate cache.
    tester->setCacheMode(QGraphicsItem::DeviceCoordinateCache);
    testerChild->setCacheMode(QGraphicsItem::DeviceCoordinateCache);
    testerChild2->setCacheMode(QGraphicsItem::DeviceCoordinateCache);
    QTRY_COMPARE(tester->repaints, 8);
    QCOMPARE(testerChild->repaints, 7);
    QCOMPARE(testerChild2->repaints, 5);

    // Scaling the view back should cause repaints for two of the items.
    view.setTransform(QTransform());
    QTRY_COMPARE(tester->repaints, 9);
    QCOMPARE(testerChild->repaints, 8);
    QCOMPARE(testerChild2->repaints, 5);

    // Rotating the base item (perspective) should repaint two items.
    tester->setTransform(QTransform().rotate(10, Qt::XAxis));
    QTRY_COMPARE(tester->repaints, 10);
    QCOMPARE(testerChild->repaints, 9);
    QCOMPARE(testerChild2->repaints, 5);

    // Moving the middle item should cause a repaint even if it's a move,
    // because the parent is rotated with a perspective.
    testerChild->setPos(1, 1);
    QTRY_COMPARE(tester->repaints, 11);
    QTRY_COMPARE(testerChild->repaints, 10);
    QCOMPARE(testerChild2->repaints, 5);
    tester->resetTransform();

    // Make a huge item
    tester->setGeometry(QRectF(-4000, -4000, 8000, 8000));
    QTRY_COMPARE(tester->repaints, 12);
    QTRY_COMPARE(testerChild->repaints, 11);
    QCOMPARE(testerChild2->repaints, 5);

    // Move the large item - will cause a repaint as the
    // cache is clipped.
    tester->setPos(5, 0);
    QTRY_COMPARE(tester->repaints, 13);
    QTRY_COMPARE(testerChild->repaints, 11);
    QCOMPARE(testerChild2->repaints, 5);

    // Hiding and showing should invalidate the cache
    tester->hide();
    QTest::qWait(25);
    tester->show();
    QTRY_COMPARE(tester->repaints, 14);
    QTRY_COMPARE(testerChild->repaints, 12);
    QTRY_COMPARE(testerChild2->repaints, 6);
}

void tst_QGraphicsItem::cacheMode2()
{
    if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive))
        QSKIP("Wayland: This fails. Figure out why.");

    QGraphicsScene scene(0, 0, 100, 100);
    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.resize(150, 150);
    view.show();
    view.window()->activateWindow();
    QVERIFY(QTest::qWaitForWindowExposed(&view));
    QVERIFY(QTest::qWaitForWindowActive(&view));
    QCoreApplication::processEvents(); // Process all queued paint events

    EventTester *tester = new EventTester;
    scene.addItem(tester);
    QTRY_COMPARE(tester->repaints, 1);

    // Switching from NoCache to NoCache (no repaint)
    tester->setCacheMode(QGraphicsItem::NoCache);
    QTest::qWait(50);
    QCOMPARE(tester->repaints, 1);

    // Switching from NoCache to DeviceCoordinateCache (no repaint)
    tester->setCacheMode(QGraphicsItem::DeviceCoordinateCache);
    QTest::qWait(50);
    QCOMPARE(tester->repaints, 1);

    // Switching from DeviceCoordinateCache to DeviceCoordinateCache (no repaint)
    tester->setCacheMode(QGraphicsItem::DeviceCoordinateCache);
    QTest::qWait(50);
    QCOMPARE(tester->repaints, 1);

    // Switching from DeviceCoordinateCache to NoCache (no repaint)
    tester->setCacheMode(QGraphicsItem::NoCache);
    QTest::qWait(50);
    QCOMPARE(tester->repaints, 1);

    // Switching from NoCache to ItemCoordinateCache (repaint)
    tester->setCacheMode(QGraphicsItem::ItemCoordinateCache);
    QTRY_COMPARE(tester->repaints, 2);

    // Switching from ItemCoordinateCache to ItemCoordinateCache (no repaint)
    tester->setCacheMode(QGraphicsItem::ItemCoordinateCache);
    QTest::qWait(50);
    QCOMPARE(tester->repaints, 2);

    // Switching from ItemCoordinateCache to ItemCoordinateCache with different size (repaint)
    tester->setCacheMode(QGraphicsItem::ItemCoordinateCache, QSize(100, 100));
    QTRY_COMPARE(tester->repaints, 3);

    // Switching from ItemCoordinateCache to NoCache (repaint)
    tester->setCacheMode(QGraphicsItem::NoCache);
    QTRY_COMPARE(tester->repaints, 4);

    // Switching from DeviceCoordinateCache to ItemCoordinateCache (repaint)
    tester->setCacheMode(QGraphicsItem::DeviceCoordinateCache);
    QTest::qWait(50);
    QCOMPARE(tester->repaints, 4);
    tester->setCacheMode(QGraphicsItem::ItemCoordinateCache);
    QTRY_COMPARE(tester->repaints, 5);

    // Switching from ItemCoordinateCache to DeviceCoordinateCache (repaint)
    tester->setCacheMode(QGraphicsItem::DeviceCoordinateCache);
    QTRY_COMPARE(tester->repaints, 6);
}

void tst_QGraphicsItem::updateCachedItemAfterMove()
{
    // A simple item that uses ItemCoordinateCache
    EventTester *tester = new EventTester;
    tester->setCacheMode(QGraphicsItem::ItemCoordinateCache);

    // Add to a scene, show in a view, ensure it's painted and reset its
    // repaint counter.
    QGraphicsScene scene;
    scene.addItem(tester);
    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));

    QTRY_VERIFY(tester->repaints > 0);
    tester->repaints = 0;

    // Move the item, should not cause repaints
    tester->setPos(10, 0);
    QTest::qWait(12);
    QCOMPARE(tester->repaints, 0);

    // Move then update, should cause one repaint
    tester->setPos(20, 0);
    tester->update();
    QTRY_COMPARE(tester->repaints, 1);

    // Hiding the item doesn't cause a repaint
    tester->hide();
    QTest::qWait(12);
    QCOMPARE(tester->repaints, 1);

    // Moving a hidden item doesn't cause a repaint
    tester->setPos(30, 0);
    tester->update();
    QTest::qWait(12);
    QCOMPARE(tester->repaints, 1);
}

class Track : public QGraphicsRectItem
{
public:
    Track(const QRectF &rect)
        : QGraphicsRectItem(rect)
    {
        setAcceptHoverEvents(true);
    }

    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override
    {
        QGraphicsRectItem::paint(painter, option, widget);
        const QString text = QString::number(p.x()) + QLatin1Char('x') + QString::number(p.y())
            + QLatin1Char('\n') + QString::number(sp.x()) + QLatin1Char('x') + QString::number(sp.y());
        painter->drawText(boundingRect(), Qt::AlignCenter, text);
    }

protected:
    void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override
    {
        p = event->pos();
        sp = event->widget()->mapFromGlobal(event->screenPos());
        update();
    }
private:
    QPointF p;
    QPoint sp;
};

void tst_QGraphicsItem::deviceTransform_data()
{
    QTest::addColumn<bool>("untransformable1");
    QTest::addColumn<bool>("untransformable2");
    QTest::addColumn<bool>("untransformable3");
    QTest::addColumn<qreal>("rotation1");
    QTest::addColumn<qreal>("rotation2");
    QTest::addColumn<qreal>("rotation3");
    QTest::addColumn<QTransform>("deviceX");
    QTest::addColumn<QPointF>("mapResult1");
    QTest::addColumn<QPointF>("mapResult2");
    QTest::addColumn<QPointF>("mapResult3");

    QTest::newRow("nil") << false << false << false
                         << qreal(0.0) << qreal(0.0) << qreal(0.0)
                         << QTransform()
                         << QPointF(150, 150) << QPointF(250, 250) << QPointF(350, 350);
    QTest::newRow("deviceX rot 90") << false << false << false
                         << qreal(0.0) << qreal(0.0) << qreal(0.0)
                         << QTransform().rotate(90)
                         << QPointF(-150, 150) << QPointF(-250, 250) << QPointF(-350, 350);
    QTest::newRow("deviceX rot 90 100") << true << false << false
                         << qreal(0.0) << qreal(0.0) << qreal(0.0)
                         << QTransform().rotate(90)
                         << QPointF(-50, 150) << QPointF(50, 250) << QPointF(150, 350);
    QTest::newRow("deviceX rot 90 010") << false << true << false
                         << qreal(0.0) << qreal(0.0) << qreal(0.0)
                         << QTransform().rotate(90)
                         << QPointF(-150, 150) << QPointF(-150, 250) << QPointF(-50, 350);
    QTest::newRow("deviceX rot 90 001") << false << false << true
                         << qreal(0.0) << qreal(0.0) << qreal(0.0)
                         << QTransform().rotate(90)
                         << QPointF(-150, 150) << QPointF(-250, 250) << QPointF(-250, 350);
    QTest::newRow("deviceX rot 90 111") << true << true << true
                         << qreal(0.0) << qreal(0.0) << qreal(0.0)
                         << QTransform().rotate(90)
                         << QPointF(-50, 150) << QPointF(50, 250) << QPointF(150, 350);
    QTest::newRow("deviceX rot 90 101") << true << false << true
                         << qreal(0.0) << qreal(0.0) << qreal(0.0)
                         << QTransform().rotate(90)
                         << QPointF(-50, 150) << QPointF(50, 250) << QPointF(150, 350);
}

void tst_QGraphicsItem::deviceTransform()
{
    QFETCH(bool, untransformable1);
    QFETCH(bool, untransformable2);
    QFETCH(bool, untransformable3);
    QFETCH(qreal, rotation1);
    QFETCH(qreal, rotation2);
    QFETCH(qreal, rotation3);
    QFETCH(QTransform, deviceX);
    QFETCH(QPointF, mapResult1);
    QFETCH(QPointF, mapResult2);
    QFETCH(QPointF, mapResult3);

    QGraphicsScene scene;
    Track *rect1 = new Track(QRectF(0, 0, 100, 100));
    Track *rect2 = new Track(QRectF(0, 0, 100, 100));
    Track *rect3 = new Track(QRectF(0, 0, 100, 100));
    rect2->setParentItem(rect1);
    rect3->setParentItem(rect2);
    rect1->setPos(100, 100);
    rect2->setPos(100, 100);
    rect3->setPos(100, 100);
    rect1->setTransform(QTransform().rotate(rotation1), true);
    rect2->setTransform(QTransform().rotate(rotation2), true);
    rect3->setTransform(QTransform().rotate(rotation3), true);
    rect1->setFlag(QGraphicsItem::ItemIgnoresTransformations, untransformable1);
    rect2->setFlag(QGraphicsItem::ItemIgnoresTransformations, untransformable2);
    rect3->setFlag(QGraphicsItem::ItemIgnoresTransformations, untransformable3);
    rect1->setBrush(Qt::red);
    rect2->setBrush(Qt::green);
    rect3->setBrush(Qt::blue);
    scene.addItem(rect1);

    QCOMPARE(rect1->deviceTransform(deviceX).map(QPointF(50, 50)), mapResult1);
    QCOMPARE(rect2->deviceTransform(deviceX).map(QPointF(50, 50)), mapResult2);
    QCOMPARE(rect3->deviceTransform(deviceX).map(QPointF(50, 50)), mapResult3);
}

void tst_QGraphicsItem::update()
{
    QGraphicsScene scene;
    scene.setSceneRect(-100, -100, 200, 200);
    QWidget topLevel;
    topLevel.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    MyGraphicsView view(&scene,&topLevel);

    topLevel.resize(300, 300);
    topLevel.show();
    QVERIFY(QTest::qWaitForWindowExposed(&topLevel));

    EventTester *item = new EventTester;
    scene.addItem(item);
    QTRY_VERIFY(item->repaints > 0); // Wait for painting
    item->repaints = 0;

    item->update(); // Item marked as dirty
    scene.update(); // Entire scene marked as dirty
    QTRY_COMPARE(item->repaints, 1);

    // Make sure the dirty state from the previous update is reset so that
    // the item don't think it is already dirty and discards this update.
    item->update();
    QTRY_COMPARE(item->repaints, 2);

    // Make sure a partial update doesn't cause a full update to be discarded.
    view.reset();
    item->repaints = 0;
    item->update(QRectF(0, 0, 5, 5));
    item->update();
    QTRY_COMPARE(item->repaints, 1);
    QTRY_COMPARE(view.repaints, 1);
    QRect itemDeviceBoundingRect = item->deviceTransform(view.viewportTransform())
                                                         .mapRect(item->boundingRect()).toAlignedRect();
    QRegion expectedRegion = itemDeviceBoundingRect.adjusted(-2, -2, 2, 2);
    // The entire item's bounding rect (adjusted for antialiasing) should have been painted.
    QCOMPARE(view.paintedRegion, expectedRegion);

    // Make sure update requests outside the bounding rect are discarded.
    view.reset();
    item->repaints = 0;
    item->update(-15, -15, 5, 5); // Item's brect: (-10, -10, 20, 20)
    QCoreApplication::processEvents();
    QCOMPARE(item->repaints, 0);
    QCOMPARE(view.repaints, 0);

    // Make sure the area occupied by an item is repainted when hiding it.
    view.reset();
    item->repaints = 0;
    item->update(); // Full update; all sub-sequent update requests are discarded.
    item->hide(); // visible set to 0. ignoreVisible must be set to 1; the item won't be processed otherwise.
    QCoreApplication::processEvents();
    QCOMPARE(item->repaints, 0);
    QCOMPARE(view.repaints, 1);
    // The entire item's bounding rect (adjusted for antialiasing) should have been painted.
    QCOMPARE(view.paintedRegion, expectedRegion);

    // Make sure item is repainted when shown (after being hidden).
    view.reset();
    item->repaints = 0;
    item->show();
    QCoreApplication::processEvents();
    QCOMPARE(item->repaints, 1);
    QCOMPARE(view.repaints, 1);
    // The entire item's bounding rect (adjusted for antialiasing) should have been painted.
    QCOMPARE(view.paintedRegion, expectedRegion);

    item->repaints = 0;
    item->hide();
    QCoreApplication::processEvents();
    view.reset();
    const QPointF originalPos = item->pos();
    item->setPos(5000, 5000);
    QCoreApplication::processEvents();
    QCOMPARE(item->repaints, 0);
    QCOMPARE(view.repaints, 0);
    QCoreApplication::processEvents();

    item->setPos(originalPos);
    QCoreApplication::processEvents();
    QCOMPARE(item->repaints, 0);
    QCOMPARE(view.repaints, 0);
    item->show();
    QCoreApplication::processEvents();
    QCOMPARE(item->repaints, 1);
    QCOMPARE(view.repaints, 1);
    // The entire item's bounding rect (adjusted for antialiasing) should have been painted.
    QCOMPARE(view.paintedRegion, expectedRegion);

    QGraphicsViewPrivate *viewPrivate = static_cast<QGraphicsViewPrivate *>(qt_widget_private(&view));
    item->setPos(originalPos + QPoint(50, 50));
    viewPrivate->updateAll();
    QVERIFY(viewPrivate->fullUpdatePending);
    QTRY_VERIFY(view.repaints > 1 && item->repaints > 1);
    item->repaints = 0;
    view.reset();
    item->setPos(originalPos);
    QTRY_COMPARE(item->repaints, 1);
    QCOMPARE(view.repaints, 1);
    COMPARE_REGIONS(view.paintedRegion, expectedRegion + expectedRegion.translated(50, 50));

    // Make sure moving a parent item triggers an update on the children
    // (even though the parent itself is outside the viewport).
    QGraphicsRectItem *parent = new QGraphicsRectItem(0, 0, 10, 10);
    parent->setPos(-400, 0);
    item->setParentItem(parent);
    item->setPos(400, 0);
    scene.addItem(parent);
    itemDeviceBoundingRect = item->deviceTransform(view.viewportTransform())
                                                   .mapRect(item->boundingRect()).toAlignedRect();
    expectedRegion = itemDeviceBoundingRect.adjusted(-2, -2, 2, 2);
    view.reset();
    item->repaints = 0;
    parent->setTransform(QTransform::fromTranslate(-400, 0), true);
    QCoreApplication::processEvents();
    QCOMPARE(item->repaints, 0);
    QCOMPARE(view.repaints, 1);
    QCOMPARE(view.paintedRegion, expectedRegion);
    view.reset();
    item->repaints = 0;
    parent->setTransform(QTransform::fromTranslate(400, 0), true);
    QCoreApplication::processEvents();
    QCOMPARE(item->repaints, 1);
    QCOMPARE(view.repaints, 1);
    QCOMPARE(view.paintedRegion, expectedRegion);
    QCOMPARE(view.paintedRegion, expectedRegion);
}

void tst_QGraphicsItem::setTransformProperties_data()
{
    QTest::addColumn<QPointF>("origin");
    QTest::addColumn<qreal>("rotation");
    QTest::addColumn<qreal>("scale");

    QTest::newRow("nothing") << QPointF() << qreal(0.0) << qreal(1.0);

    QTest::newRow("rotation") << QPointF() << qreal(42.2) << qreal(1.0);

    QTest::newRow("rotation dicentred") << QPointF(qreal(22.3), qreal(-56.2))
                                << qreal(-2578.2)
                                << qreal(1.0);

    QTest::newRow("Scale")    << QPointF() << qreal(0.0)
                                          << qreal(6);

    QTest::newRow("Everything dicentred")  << QPointF(qreal(22.3), qreal(-56.2)) << qreal(-175) << qreal(196);
}

/**
 * the normal QCOMPARE doesn't work because it doesn't use qFuzzyCompare
 */
#define QCOMPARE_TRANSFORM(X1, X2)   QVERIFY(((X1)*(X2).inverted()).isIdentity())

void tst_QGraphicsItem::setTransformProperties()
{
    QFETCH(QPointF,origin);
    QFETCH(qreal,rotation);
    QFETCH(qreal,scale);

    QTransform result;
    result.translate(origin.x(), origin.y());
    result.rotate(rotation, Qt::ZAxis);
    result.scale(scale, scale);
    result.translate(-origin.x(), -origin.y());

    QGraphicsScene scene;
    QGraphicsRectItem *item = new QGraphicsRectItem(QRectF(0, 0, 100, 100));
    scene.addItem(item);

    item->setRotation(rotation);
    item->setScale(scale);
    item->setTransformOriginPoint(origin);

    QCOMPARE(item->rotation(), rotation);
    QCOMPARE(item->scale(), scale);
    QCOMPARE(item->transformOriginPoint(), origin);

    QCOMPARE(QTransform(), item->transform());
    QCOMPARE(result, item->sceneTransform());

    //-----------------------------------------------------------------
    //Change the rotation Z
    item->setRotation(45);
    QTransform result2;
    result2.translate(origin.x(), origin.y());
    result2.rotate(45);
    result2.scale(scale, scale);
    result2.translate(-origin.x(), -origin.y());

    QCOMPARE(item->rotation(), 45.);
    QCOMPARE(item->scale(), scale);
    QCOMPARE(item->transformOriginPoint(), origin);

    QCOMPARE(QTransform(), item->transform());
    QCOMPARE(result2, item->sceneTransform());

    //-----------------------------------------------------------------
    // calling setTransform() and setPos should change the sceneTransform
    item->setTransform(result);
    item->setPos(100, -150.5);

    QCOMPARE(item->rotation(), 45.);
    QCOMPARE(item->scale(), scale);
    QCOMPARE(item->transformOriginPoint(), origin);
    QCOMPARE(result, item->transform());

    QTransform result3(result);

    result3.translate(origin.x(), origin.y());
    result3.rotate(45);
    result3.scale(scale, scale);
    result3.translate(-origin.x(), -origin.y());

    result3 *= QTransform::fromTranslate(100, -150.5); //the pos;

    QCOMPARE(result3, item->sceneTransform());

    //-----------------------------------------------------
    // setting the propertiees should be the same as setting a transform
    {//with center origin on the matrix
        QGraphicsRectItem *item1 = new QGraphicsRectItem(QRectF(50.2, -150, 230.5, 119));
        scene.addItem(item1);
        QGraphicsRectItem *item2 = new QGraphicsRectItem(QRectF(50.2, -150, 230.5, 119));
        scene.addItem(item2);

        item1->setPos(12.3, -5);
        item2->setPos(12.3, -5);
        item1->setRotation(rotation);
        item1->setScale(scale);
        item1->setTransformOriginPoint(origin);

        item2->setTransform(result);

        QCOMPARE_TRANSFORM(item1->sceneTransform(), item2->sceneTransform());

        QCOMPARE_TRANSFORM(item1->itemTransform(item2), QTransform());
        QCOMPARE_TRANSFORM(item2->itemTransform(item1), QTransform());
    }
}

class MyStyleOptionTester : public QGraphicsRectItem
{
public:
    using QGraphicsRectItem::QGraphicsRectItem;

    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override
    {
        ++repaints;
        if (startTrack) {
            //Doesn't use the extended style option so the exposed rect is the boundingRect
            if (!(flags() & QGraphicsItem::ItemUsesExtendedStyleOption)) {
                QCOMPARE(option->exposedRect, boundingRect());
            } else {
                QVERIFY(option->exposedRect != QRect());
                QVERIFY(option->exposedRect != boundingRect());
            }
        }
        QGraphicsRectItem::paint(painter, option, widget);
    }
    bool startTrack = false;
    int repaints = 0;
};

void tst_QGraphicsItem::itemUsesExtendedStyleOption()
{
    QGraphicsScene scene(0, 0, 300, 300);
    QGraphicsPixmapItem item;
    item.setFlag(QGraphicsItem::ItemUsesExtendedStyleOption, true);
    QCOMPARE(item.flags(), QGraphicsItem::GraphicsItemFlags(QGraphicsItem::ItemUsesExtendedStyleOption));
    item.setFlag(QGraphicsItem::ItemUsesExtendedStyleOption, false);
    QCOMPARE(item.flags(), 0);

    //We now test the content of the style option
    MyStyleOptionTester *rect = new MyStyleOptionTester(QRect(0, 0, 100, 100));
    scene.addItem(rect);
    rect->setPos(200, 200);
    QWidget topLevel;
    topLevel.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    topLevel.resize(200, 200);
    QGraphicsView view(&scene, &topLevel);
    topLevel.setWindowFlags(Qt::X11BypassWindowManagerHint);
    rect->startTrack = false;
    topLevel.show();
    QVERIFY(QTest::qWaitForWindowExposed(&topLevel));
    QCoreApplication::processEvents(); // Process all queued paint events
    QTRY_VERIFY(rect->repaints > 0);
    rect->repaints = 0;
    rect->startTrack = true;
    rect->update(10, 10, 10, 10);
    QTRY_COMPARE(rect->repaints, 1);
    rect->repaints = 0;
    rect->startTrack = false;
    rect->setFlag(QGraphicsItem::ItemUsesExtendedStyleOption, true);
    QVERIFY((rect->flags() & QGraphicsItem::ItemUsesExtendedStyleOption));
    QTRY_COMPARE(rect->repaints, 1);
    rect->repaints = 0;
    rect->startTrack = true;
    rect->update(10, 10, 10, 10);
    QTest::qWait(60);
    // MyStyleOptionTester does not receive a paint event. Why not?
}

void tst_QGraphicsItem::itemSendsGeometryChanges()
{
    ItemChangeTester item;
    item.setFlags({ });
    item.clear();

    QTransform x = QTransform().rotate(45);
    QPointF pos(10, 10);
    qreal o(0.5);
    qreal r(10.0);
    qreal s(1.5);
    QPointF origin(1.0, 1.0);
    item.setTransform(x);
    item.setPos(pos);
    item.setRotation(r);
    item.setScale(s);
    item.setTransformOriginPoint(origin);
    QCOMPARE(item.transform(), x);
    QCOMPARE(item.pos(), pos);
    QCOMPARE(item.rotation(), r);
    QCOMPARE(item.scale(), s);
    QCOMPARE(item.transformOriginPoint(), origin);
    QCOMPARE(item.changes.size(), 0);

    item.setOpacity(o);
    QCOMPARE(item.changes.size(), 2); // opacity

    item.setFlag(QGraphicsItem::ItemSendsGeometryChanges);
    QCOMPARE(item.changes.size(), 4); // flags
    item.setTransform(QTransform());
    item.setPos(QPointF());
    QCOMPARE(item.changes.size(), 8); // transform + pos
    QCOMPARE(item.transform(), QTransform());
    QCOMPARE(item.pos(), QPointF());
    QCOMPARE(item.opacity(), o);
    item.setRotation(0.0);
    item.setScale(1.0);
    item.setTransformOriginPoint(0.0, 0.0);
    QCOMPARE(item.changes.size(), 14); // rotation + scale + origin
    QCOMPARE(item.rotation(), qreal(0.0));
    QCOMPARE(item.scale(), qreal(1.0));
    QCOMPARE(item.transformOriginPoint(), QPointF(0.0, 0.0));

    const QList<QGraphicsItem::GraphicsItemChange> expected {
        QGraphicsItem::ItemOpacityChange,
        QGraphicsItem::ItemOpacityHasChanged,
        QGraphicsItem::ItemFlagsChange,
        QGraphicsItem::ItemFlagsHaveChanged,
        QGraphicsItem::ItemTransformChange,
        QGraphicsItem::ItemTransformHasChanged,
        QGraphicsItem::ItemPositionChange,
        QGraphicsItem::ItemPositionHasChanged,
        QGraphicsItem::ItemRotationChange,
        QGraphicsItem::ItemRotationHasChanged,
        QGraphicsItem::ItemScaleChange,
        QGraphicsItem::ItemScaleHasChanged,
        QGraphicsItem::ItemTransformOriginPointChange,
        QGraphicsItem::ItemTransformOriginPointHasChanged
    };
    QCOMPARE(item.changes, expected);
}

// Make sure we update moved items correctly.
void tst_QGraphicsItem::moveItem()
{
    QGraphicsScene scene;
    scene.setSceneRect(-50, -50, 200, 200);

    MyGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));
    QCoreApplication::processEvents(); // Process all queued paint events

    EventTester *parent = new EventTester;
    EventTester *child = new EventTester(parent);
    EventTester *grandChild = new EventTester(child);

#define RESET_COUNTERS \
    parent->repaints = 0; \
    child->repaints = 0; \
    grandChild->repaints = 0; \
    view.reset();

    RESET_COUNTERS
    scene.addItem(parent);
    QTRY_COMPARE(view.repaints, 1);

    RESET_COUNTERS

    // Item's boundingRect:  (-10, -10, 20, 20).
    QRect parentDeviceBoundingRect = parent->deviceTransform(view.viewportTransform())
                                     .mapRect(parent->boundingRect()).toAlignedRect()
                                     .adjusted(-2, -2, 2, 2); // Adjusted for antialiasing.

    parent->setPos(20, 20);
    QCoreApplication::processEvents();
    QCOMPARE(parent->repaints, 1);
    QCOMPARE(view.repaints, 1);
    QRegion expectedParentRegion = parentDeviceBoundingRect; // old position
    parentDeviceBoundingRect.translate(20, 20);
    expectedParentRegion += parentDeviceBoundingRect; // new position
    COMPARE_REGIONS(view.paintedRegion, expectedParentRegion);

    RESET_COUNTERS

    child->setPos(20, 20);
    QCoreApplication::processEvents();
    QCOMPARE(parent->repaints, 1);
    QCOMPARE(child->repaints, 1);
    QCOMPARE(view.repaints, 1);
    const QRegion expectedChildRegion = expectedParentRegion.translated(20, 20);
    COMPARE_REGIONS(view.paintedRegion, expectedChildRegion);

    RESET_COUNTERS

    grandChild->setPos(20, 20);
    QCoreApplication::processEvents();
    QCOMPARE(parent->repaints, 1);
    QCOMPARE(child->repaints, 1);
    QCOMPARE(grandChild->repaints, 1);
    QCOMPARE(view.repaints, 1);
    const QRegion expectedGrandChildRegion = expectedParentRegion.translated(40, 40);
    COMPARE_REGIONS(view.paintedRegion, expectedGrandChildRegion);

    RESET_COUNTERS

    parent->setTransform(QTransform::fromTranslate(20, 20), true);
    QCoreApplication::processEvents();
    QCOMPARE(parent->repaints, 1);
    QCOMPARE(child->repaints, 1);
    QCOMPARE(grandChild->repaints, 1);
    QCOMPARE(view.repaints, 1);
    expectedParentRegion.translate(20, 20);
    expectedParentRegion += expectedChildRegion.translated(20, 20);
    expectedParentRegion += expectedGrandChildRegion.translated(20, 20);
    COMPARE_REGIONS(view.paintedRegion, expectedParentRegion);
}

void tst_QGraphicsItem::moveLineItem()
{
    QGraphicsScene scene;
    scene.setSceneRect(0, 0, 200, 200);
    QGraphicsLineItem *item = new QGraphicsLineItem(0, 0, 100, 0);
    item->setPos(50, 50);
    scene.addItem(item);

    MyGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.show();
    if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) {
        view.window()->activateWindow();
        QVERIFY(QTest::qWaitForWindowActive(&view));
    }
    QVERIFY(QTest::qWaitForWindowExposed(&view));
    QCoreApplication::processEvents(); // Process all queued paint events
    view.reset();

    QRectF brect = item->boundingRect();
    // Do same adjustments as in qgraphicsscene.cpp
    if (qFuzzyIsNull(brect.width()))
        brect.adjust(qreal(-0.00001), 0, qreal(0.00001), 0);
    if (qFuzzyIsNull(brect.height()))
        brect.adjust(0, qreal(-0.00001), 0, qreal(0.00001));
    const QRect itemDeviceBoundingRect = item->deviceTransform(view.viewportTransform())
                                         .mapRect(brect).toAlignedRect();
    QRegion expectedRegion = itemDeviceBoundingRect.adjusted(-2, -2, 2, 2); // antialiasing

    // Make sure the calculated region is correct.
    item->update();
    QTRY_COMPARE(view.paintedRegion, expectedRegion);
    view.reset();

    // Old position: (50, 50)
    item->setPos(50, 100);
    expectedRegion += expectedRegion.translated(0, 50);
    QTRY_COMPARE(view.paintedRegion, expectedRegion);
}

void tst_QGraphicsItem::sorting_data()
{
    QTest::addColumn<int>("index");

    QTest::newRow("NoIndex") << int(QGraphicsScene::NoIndex);
    QTest::newRow("BspTreeIndex") << int(QGraphicsScene::BspTreeIndex);
}

void tst_QGraphicsItem::sorting()
{
    if (qGuiApp->styleHints()->showIsFullScreen())
        QSKIP("Skipped because Platform is auto maximizing");

    _paintedItems.clear();

    QGraphicsScene scene;
    QGraphicsItem *grid[100][100];
    for (int x = 0; x < 100; ++x) {
        const QString prefix = QString::number(x) + QLatin1Char('x');
        for (int y = 0; y < 100; ++y) {
            PainterItem *item = new PainterItem;
            item->setPos(x * 25, y * 25);
            item->setData(0, prefix + QString::number(y));
            grid[x][y] = item;
            scene.addItem(item);
        }
    }

    PainterItem *item1 = new PainterItem;
    PainterItem *item2 = new PainterItem;
    item1->setData(0, "item1");
    item2->setData(0, "item2");
    scene.addItem(item1);
    scene.addItem(item2);

    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    // Use Qt::Tool as fully decorated windows have a minimum width of 160 on Windows.
    view.setWindowFlags(view.windowFlags() | Qt::Tool);
    view.setResizeAnchor(QGraphicsView::NoAnchor);
    view.setTransformationAnchor(QGraphicsView::NoAnchor);
    view.resize(120, 100);
    view.setFrameStyle(0);
    view.show();
    if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) {
        view.window()->activateWindow();
        QVERIFY(QTest::qWaitForWindowActive(&view));
    }
    QVERIFY(QTest::qWaitForWindowExposed(&view));
    QTRY_VERIFY(_paintedItems.size() > 0);

    _paintedItems.clear();

    view.viewport()->update();
    const GraphicsItems expected{grid[0][0], grid[0][1], grid[0][2], grid[0][3],
                                 grid[1][0], grid[1][1], grid[1][2], grid[1][3],
                                 grid[2][0], grid[2][1], grid[2][2], grid[2][3],
                                 grid[3][0], grid[3][1], grid[3][2], grid[3][3],
                                 grid[4][0], grid[4][1], grid[4][2], grid[4][3],
                                 item1, item2};
    QTRY_COMPARE(_paintedItems, expected);
}

void tst_QGraphicsItem::itemHasNoContents()
{
    PainterItem *item1 = new PainterItem;
    PainterItem *item2 = new PainterItem;
    item2->setParentItem(item1);
    item2->setPos(50, 50);
    item1->setFlag(QGraphicsItem::ItemHasNoContents);
    item1->setFlag(QGraphicsItem::ItemClipsChildrenToShape);

    QGraphicsScene scene;
    scene.addItem(item1);

    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.show();
    if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) {
        view.window()->activateWindow();
        QVERIFY(QTest::qWaitForWindowActive(&view));
    }
    QVERIFY(QTest::qWaitForWindowExposed(&view));
    QTRY_VERIFY(!_paintedItems.isEmpty());

    _paintedItems.clear();

    view.viewport()->update();
    QTRY_COMPARE(_paintedItems, GraphicsItems{item2});
}

void tst_QGraphicsItem::hitTestUntransformableItem()
{
    QGraphicsScene scene;
    scene.setSceneRect(-100, -100, 200, 200);

    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));

    // Confuse the BSP with dummy items.
    QGraphicsRectItem *dummy = new QGraphicsRectItem(0, 0, 20, 20);
    dummy->setPos(-100, -100);
    scene.addItem(dummy);
    for (int i = 0; i < 100; ++i) {
        QGraphicsItem *parent = dummy;
        dummy = new QGraphicsRectItem(0, 0, 20, 20);
        dummy->setPos(-100 + i, -100 + i);
        dummy->setParentItem(parent);
    }

    QGraphicsRectItem *item1 = new QGraphicsRectItem(0, 0, 20, 20);
    item1->setPos(-200, -200);

    QGraphicsRectItem *item2 = new QGraphicsRectItem(0, 0, 20, 20);
    item2->setFlag(QGraphicsItem::ItemIgnoresTransformations);
    item2->setParentItem(item1);
    item2->setPos(200, 200);

    QGraphicsRectItem *item3 = new QGraphicsRectItem(0, 0, 20, 20);
    item3->setParentItem(item2);
    item3->setPos(80, 80);

    scene.addItem(item1);

    QList<QGraphicsItem *> items = scene.items(QPointF(80, 80));
    QCOMPARE(items.size(), 1);
    QCOMPARE(items.at(0), item3);

    scene.setItemIndexMethod(QGraphicsScene::NoIndex);
    QTest::qWait(100);

    items = scene.items(QPointF(80, 80));
    QCOMPARE(items.size(), 1);
    QCOMPARE(items.at(0), static_cast<QGraphicsItem*>(item3));
}

void tst_QGraphicsItem::hitTestGraphicsEffectItem()
{
    QGraphicsScene scene;
    scene.setSceneRect(-100, -100, 200, 200);

    QWidget toplevel;
    toplevel.setWindowTitle(QLatin1String(QTest::currentTestFunction()));

    QGraphicsView view(&scene, &toplevel);
    toplevel.resize(300, 300);
    toplevel.show();
    QVERIFY(QTest::qWaitForWindowExposed(&toplevel));
    QCoreApplication::processEvents(); // Process all queued paint events

    // Confuse the BSP with dummy items.
    QGraphicsRectItem *dummy = new QGraphicsRectItem(0, 0, 20, 20);
    dummy->setPos(-100, -100);
    scene.addItem(dummy);
    for (int i = 0; i < 100; ++i) {
        QGraphicsItem *parent = dummy;
        dummy = new QGraphicsRectItem(0, 0, 20, 20);
        dummy->setPos(-100 + i, -100 + i);
        dummy->setParentItem(parent);
    }

    const QRectF itemBoundingRect(0, 0, 20, 20);
    EventTester *item1 = new EventTester;
    item1->br = itemBoundingRect;
    item1->setPos(-200, -200);
    item1->brush = Qt::red;

    EventTester *item2 = new EventTester;
    item2->br = itemBoundingRect;
    item2->setFlag(QGraphicsItem::ItemIgnoresTransformations);
    item2->setParentItem(item1);
    item2->setPos(200, 200);
    item2->brush = Qt::green;

    EventTester *item3 = new EventTester;
    item3->br = itemBoundingRect;
    item3->setParentItem(item2);
    item3->setPos(80, 80);
    item3->brush = Qt::blue;

    scene.addItem(item1);
    QTRY_COMPARE(item2->repaints, 1);
    QCOMPARE(item3->repaints, 1);

    item1->repaints = 0;
    item2->repaints = 0;
    item3->repaints = 0;

    // Apply shadow effect to the entire sub-tree.
    QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect;
    shadow->setOffset(-20, -20);
    item1->setGraphicsEffect(shadow);

    // Make sure all visible items are repainted.
    QTRY_COMPARE(item1->repaints, 1);
    QCOMPARE(item2->repaints, 1);
    QCOMPARE(item3->repaints, 1);

    // Make sure an item doesn't respond to a click on its shadow.
    QList<QGraphicsItem *> items = scene.items(QPointF(75, 75));
    QVERIFY(items.isEmpty());
    items = scene.items(QPointF(80, 80));
    QCOMPARE(items.size(), 1);
    QCOMPARE(items.at(0), item3);

    scene.setItemIndexMethod(QGraphicsScene::NoIndex);
    QTest::qWait(100);

    items = scene.items(QPointF(75, 75));
    QVERIFY(items.isEmpty());
    items = scene.items(QPointF(80, 80));
    QCOMPARE(items.size(), 1);
    QCOMPARE(items.at(0), static_cast<QGraphicsItem *>(item3));
}

void tst_QGraphicsItem::focusProxy()
{
    QGraphicsScene scene;
    QEvent activate(QEvent::WindowActivate);
    QCoreApplication::sendEvent(&scene, &activate);

    QGraphicsItem *item = scene.addRect(0, 0, 10, 10);
    item->setFlag(QGraphicsItem::ItemIsFocusable);
    QVERIFY(!item->focusProxy());

    QGraphicsItem *item2 = scene.addRect(0, 0, 10, 10);
    item2->setFlag(QGraphicsItem::ItemIsFocusable);
    item->setFocusProxy(item2);
    QCOMPARE(item->focusProxy(), item2);

    item->setFocus();
    QVERIFY(item->hasFocus());
    QVERIFY(item2->hasFocus());

    // Try to make a focus chain loop
    QString err;
    QTextStream stream(&err);
    stream << "QGraphicsItem::setFocusProxy: "
           << static_cast<const void*>(item) << " is already in the focus proxy chain" << Qt::flush;
    QTest::ignoreMessage(QtWarningMsg, err.toLatin1().constData());
    item2->setFocusProxy(item); // fails
    QCOMPARE(item->focusProxy(), item2);
    QCOMPARE(item2->focusProxy(), nullptr);

    // Try to assign self as focus proxy
    QTest::ignoreMessage(QtWarningMsg, "QGraphicsItem::setFocusProxy: cannot assign self as focus proxy");
    item->setFocusProxy(item); // fails
    QCOMPARE(item->focusProxy(), item2);
    QCOMPARE(item2->focusProxy(), nullptr);

    // Reset the focus proxy
    item->setFocusProxy(nullptr);
    QCOMPARE(item->focusProxy(), nullptr);
    QVERIFY(!item->hasFocus());
    QVERIFY(item2->hasFocus());

    // Test deletion
    item->setFocusProxy(item2);
    QCOMPARE(item->focusProxy(), item2);
    delete item2;
    QCOMPARE(item->focusProxy(), nullptr);

    // Test event delivery
    item2 = scene.addRect(0, 0, 10, 10);
    item2->setFlag(QGraphicsItem::ItemIsFocusable);
    item->setFocusProxy(item2);
    item->clearFocus();

    EventSpy focusInSpy(&scene, item, QEvent::FocusIn);
    EventSpy focusOutSpy(&scene, item, QEvent::FocusOut);
    EventSpy focusInSpy2(&scene, item2, QEvent::FocusIn);
    EventSpy focusOutSpy2(&scene, item2, QEvent::FocusOut);
    QCOMPARE(focusInSpy.count(), 0);
    QCOMPARE(focusOutSpy.count(), 0);
    QCOMPARE(focusInSpy2.count(), 0);
    QCOMPARE(focusOutSpy2.count(), 0);

    item->setFocus();
    QCOMPARE(focusInSpy.count(), 0);
    QCOMPARE(focusInSpy2.count(), 1);
    item->clearFocus();
    QCOMPARE(focusOutSpy.count(), 0);
    QCOMPARE(focusOutSpy2.count(), 1);

    // Test two items proxying one item.
    QGraphicsItem *item3 = scene.addRect(0, 0, 10, 10);
    item3->setFlag(QGraphicsItem::ItemIsFocusable);
    item3->setFocusProxy(item2); // item and item3 use item2 as proxy

    QCOMPARE(item->focusProxy(), item2);
    QCOMPARE(item2->focusProxy(), nullptr);
    QCOMPARE(item3->focusProxy(), item2);
    delete item2;
    QCOMPARE(item->focusProxy(), nullptr);
    QCOMPARE(item3->focusProxy(), nullptr);
}

void tst_QGraphicsItem::subFocus()
{
    // Construct a text item that's not part of a scene (yet)
    // and has no parent. Setting focus on it will not make
    // the item gain input focus; that requires a scene. But
    // it does set subfocus, indicating that the item wishes
    // to gain focus later.
    QGraphicsTextItem *text = new QGraphicsTextItem("Hello");
    text->setTextInteractionFlags(Qt::TextEditorInteraction);
    QVERIFY(!text->hasFocus());
    text->setFocus();
    QVERIFY(!text->hasFocus());
    QCOMPARE(text->focusItem(), text);

    // Add a sibling.
    QGraphicsTextItem *text2 = new QGraphicsTextItem("Hi");
    text2->setTextInteractionFlags(Qt::TextEditorInteraction);
    text2->setPos(30, 30);

    // Add both items to a scene and check that it's text that
    // got input focus.
    QGraphicsScene scene;
    QEvent activate(QEvent::WindowActivate);
    QCoreApplication::sendEvent(&scene, &activate);

    scene.addItem(text);
    scene.addItem(text2);
    QVERIFY(text->hasFocus());

    text->setData(0, "text");
    text2->setData(0, "text2");

    // Remove text2 and set subfocus on it. Then readd. Reparent it onto the
    // other item and see that it gains input focus.
    scene.removeItem(text2);
    text2->setFocus();
    scene.addItem(text2);
    QCOMPARE(text2->focusItem(), text2);
    text2->setParentItem(text);
    QCOMPARE(text->focusItem(), text2);
    QCOMPARE(text2->focusItem(), text2);
    QVERIFY(!text->hasFocus());
    QVERIFY(text2->hasFocus());

    // Remove both items from the scene, restore subfocus and
    // readd them. Now the subfocus should kick in and give
    // text2 focus.
    scene.removeItem(text);
    QCOMPARE(text->focusItem(), nullptr);
    QCOMPARE(text2->focusItem(), nullptr);
    text2->setFocus();
    QCOMPARE(text->focusItem(), text2);
    QCOMPARE(text2->focusItem(), text2);
    scene.addItem(text);

    // Hiding and showing text should pass focus to text2.
    QCOMPARE(text->focusItem(), text2);
    QVERIFY(text2->hasFocus());

    // Subfocus should repropagate to root when reparenting.
    QGraphicsRectItem *rect = new QGraphicsRectItem;
    QGraphicsRectItem *rect2 = new QGraphicsRectItem(rect);
    QGraphicsRectItem *rect3 = new QGraphicsRectItem(rect2);
    rect3->setFlag(QGraphicsItem::ItemIsFocusable);

    text->setData(0, "text");
    text2->setData(0, "text2");
    rect->setData(0, "rect");
    rect2->setData(0, "rect2");
    rect3->setData(0, "rect3");

    rect3->setFocus();
    QVERIFY(!rect3->hasFocus());
    QCOMPARE(rect->focusItem(), rect3);
    QCOMPARE(rect2->focusItem(), rect3);
    QCOMPARE(rect3->focusItem(), rect3);
    rect->setParentItem(text2);
    QCOMPARE(text->focusItem(), rect3);
    QCOMPARE(text2->focusItem(), rect3);
    QCOMPARE(rect->focusItem(), rect3);
    QCOMPARE(rect2->focusItem(), rect3);
    QCOMPARE(rect3->focusItem(), rect3);
    QVERIFY(!rect->hasFocus());
    QVERIFY(!rect2->hasFocus());
    QVERIFY(rect3->hasFocus());

    delete rect2;
    QCOMPARE(text->focusItem(), nullptr);
    QCOMPARE(text2->focusItem(), nullptr);
    QCOMPARE(rect->focusItem(), nullptr);
}

void tst_QGraphicsItem::focusProxyDeletion()
{
    QGraphicsRectItem *rect = new QGraphicsRectItem;
    QGraphicsRectItem *rect2 = new QGraphicsRectItem;
    rect->setFocusProxy(rect2);
    QCOMPARE(rect->focusProxy(), rect2);

    delete rect2;
    QCOMPARE(rect->focusProxy(), nullptr);

    rect2 = new QGraphicsRectItem;
    rect->setFocusProxy(rect2);
    QGraphicsItem **danglingFocusProxyRef = &rect->d_ptr->focusProxy;
    delete rect; // don't crash
    QVERIFY(!rect2->d_ptr->focusProxyRefs.contains(danglingFocusProxyRef));

    rect = new QGraphicsRectItem;
    rect->setFocusProxy(rect2);
    QGraphicsScene *scene = new QGraphicsScene;
    scene->addItem(rect);
    scene->addItem(rect2);
    delete rect2;
    QCOMPARE(rect->focusProxy(), nullptr);

    rect2 = new QGraphicsRectItem;
    QTest::ignoreMessage(QtWarningMsg, "QGraphicsItem::setFocusProxy: focus proxy must be in same scene");
    rect->setFocusProxy(rect2);
    QCOMPARE(rect->focusProxy(), nullptr);
    scene->addItem(rect2);
    rect->setFocusProxy(rect2);
    QCOMPARE(rect->focusProxy(), rect2);
    delete rect; // don't crash

    rect = new QGraphicsRectItem;
    rect2 = new QGraphicsRectItem;
    rect->setFocusProxy(rect2);
    QCOMPARE(rect->focusProxy(), rect2);
    scene->addItem(rect);
    scene->addItem(rect2);
    rect->setFocusProxy(rect2);
    delete scene; // don't crash
}

void tst_QGraphicsItem::negativeZStacksBehindParent()
{
    QGraphicsRectItem rect;
    QCOMPARE(rect.zValue(), qreal(0.0));
    QVERIFY(!(rect.flags() & QGraphicsItem::ItemNegativeZStacksBehindParent));
    QVERIFY(!(rect.flags() & QGraphicsItem::ItemStacksBehindParent));
    rect.setZValue(-1);
    QCOMPARE(rect.zValue(), qreal(-1.0));
    QVERIFY(!(rect.flags() & QGraphicsItem::ItemStacksBehindParent));
    rect.setZValue(0);
    rect.setFlag(QGraphicsItem::ItemNegativeZStacksBehindParent);
    QVERIFY(rect.flags() & QGraphicsItem::ItemNegativeZStacksBehindParent);
    QVERIFY(!(rect.flags() & QGraphicsItem::ItemStacksBehindParent));
    rect.setZValue(-1);
    QVERIFY(rect.flags() & QGraphicsItem::ItemStacksBehindParent);
    rect.setZValue(0);
    QVERIFY(!(rect.flags() & QGraphicsItem::ItemStacksBehindParent));
    rect.setFlag(QGraphicsItem::ItemNegativeZStacksBehindParent, false);
    rect.setZValue(-1);
    rect.setFlag(QGraphicsItem::ItemNegativeZStacksBehindParent, true);
    QVERIFY(rect.flags() & QGraphicsItem::ItemStacksBehindParent);
    rect.setFlag(QGraphicsItem::ItemNegativeZStacksBehindParent, false);
    QVERIFY(rect.flags() & QGraphicsItem::ItemStacksBehindParent);
}

void tst_QGraphicsItem::setGraphicsEffect()
{
    // Check that we don't have any effect by default.
    QGraphicsItem *item = new QGraphicsRectItem(0, 0, 10, 10);
    QVERIFY(!item->graphicsEffect());

    // SetGet check.
    QPointer<QGraphicsEffect> blurEffect = new QGraphicsBlurEffect;
    item->setGraphicsEffect(blurEffect);
    QCOMPARE(item->graphicsEffect(), static_cast<QGraphicsEffect *>(blurEffect));

    // Ensure the existing effect is deleted when setting a new one.
    QPointer<QGraphicsEffect> shadowEffect = new QGraphicsDropShadowEffect;
    item->setGraphicsEffect(shadowEffect);
    QVERIFY(!blurEffect);
    QCOMPARE(item->graphicsEffect(), static_cast<QGraphicsEffect *>(shadowEffect));
    blurEffect = new QGraphicsBlurEffect;

    // Ensure the effect is uninstalled when setting it on a new target.
    QGraphicsItem *anotherItem = new QGraphicsRectItem(0, 0, 10, 10);
    anotherItem->setGraphicsEffect(blurEffect);
    item->setGraphicsEffect(blurEffect);
    QVERIFY(!anotherItem->graphicsEffect());
    QVERIFY(!shadowEffect);

    // Ensure the existing effect is deleted when deleting the item.
    delete item;
    QVERIFY(!blurEffect);
    delete anotherItem;

    // Ensure the effect is uninstalled when deleting it
    item = new QGraphicsRectItem(0, 0, 10, 10);
    blurEffect = new QGraphicsBlurEffect;
    item->setGraphicsEffect(blurEffect);
    delete blurEffect;
    QVERIFY(!item->graphicsEffect());

    // Ensure the existing effect is uninstalled and deleted when setting a null effect
    blurEffect = new QGraphicsBlurEffect;
    item->setGraphicsEffect(blurEffect);
    item->setGraphicsEffect(nullptr);
    QVERIFY(!item->graphicsEffect());
    QVERIFY(!blurEffect);

    delete item;
}

void tst_QGraphicsItem::panel()
{
    QGraphicsScene scene;

    QGraphicsRectItem *panel1 = new QGraphicsRectItem;
    QGraphicsRectItem *panel2 = new QGraphicsRectItem;
    QGraphicsRectItem *panel3 = new QGraphicsRectItem;
    QGraphicsRectItem *panel4 = new QGraphicsRectItem;
    QGraphicsRectItem *notPanel1 = new QGraphicsRectItem;
    QGraphicsRectItem *notPanel2 = new QGraphicsRectItem;
    panel1->setFlag(QGraphicsItem::ItemIsPanel);
    panel2->setFlag(QGraphicsItem::ItemIsPanel);
    panel3->setFlag(QGraphicsItem::ItemIsPanel);
    panel4->setFlag(QGraphicsItem::ItemIsPanel);
    scene.addItem(panel1);
    scene.addItem(panel2);
    scene.addItem(panel3);
    scene.addItem(panel4);
    scene.addItem(notPanel1);
    scene.addItem(notPanel2);

    EventSpy spy_activate_panel1(&scene, panel1, QEvent::WindowActivate);
    EventSpy spy_deactivate_panel1(&scene, panel1, QEvent::WindowDeactivate);
    EventSpy spy_activate_panel2(&scene, panel2, QEvent::WindowActivate);
    EventSpy spy_deactivate_panel2(&scene, panel2, QEvent::WindowDeactivate);
    EventSpy spy_activate_panel3(&scene, panel3, QEvent::WindowActivate);
    EventSpy spy_deactivate_panel3(&scene, panel3, QEvent::WindowDeactivate);
    EventSpy spy_activate_panel4(&scene, panel4, QEvent::WindowActivate);
    EventSpy spy_deactivate_panel4(&scene, panel4, QEvent::WindowDeactivate);
    EventSpy spy_activate_notPanel1(&scene, notPanel1, QEvent::WindowActivate);
    EventSpy spy_deactivate_notPanel1(&scene, notPanel1, QEvent::WindowDeactivate);
    EventSpy spy_activate_notPanel2(&scene, notPanel1, QEvent::WindowActivate);
    EventSpy spy_deactivate_notPanel2(&scene, notPanel1, QEvent::WindowDeactivate);

    QCOMPARE(spy_activate_panel1.count(), 0);
    QCOMPARE(spy_deactivate_panel1.count(), 0);
    QCOMPARE(spy_activate_panel2.count(), 0);
    QCOMPARE(spy_deactivate_panel2.count(), 0);
    QCOMPARE(spy_activate_panel3.count(), 0);
    QCOMPARE(spy_deactivate_panel3.count(), 0);
    QCOMPARE(spy_activate_panel4.count(), 0);
    QCOMPARE(spy_deactivate_panel4.count(), 0);
    QCOMPARE(spy_activate_notPanel1.count(), 0);
    QCOMPARE(spy_deactivate_notPanel1.count(), 0);
    QCOMPARE(spy_activate_notPanel2.count(), 0);
    QCOMPARE(spy_deactivate_notPanel2.count(), 0);

    QVERIFY(!scene.activePanel());
    QVERIFY(!scene.isActive());

    QEvent activate(QEvent::WindowActivate);
    QEvent deactivate(QEvent::WindowDeactivate);

    QCoreApplication::sendEvent(&scene, &activate);

    // No previous activation, so the scene is active.
    QVERIFY(scene.isActive());
    QCOMPARE(scene.activePanel(), panel1);
    QVERIFY(panel1->isActive());
    QVERIFY(!panel2->isActive());
    QVERIFY(!panel3->isActive());
    QVERIFY(!panel4->isActive());
    QVERIFY(!notPanel1->isActive());
    QVERIFY(!notPanel2->isActive());
    QCOMPARE(spy_deactivate_notPanel1.count(), 0);
    QCOMPARE(spy_deactivate_notPanel2.count(), 0);
    QCOMPARE(spy_activate_panel1.count(), 1);
    QCOMPARE(spy_activate_panel2.count(), 0);
    QCOMPARE(spy_activate_panel3.count(), 0);
    QCOMPARE(spy_activate_panel4.count(), 0);

    // Switch back to scene.
    scene.setActivePanel(nullptr);
    QVERIFY(!scene.activePanel());
    QVERIFY(!panel1->isActive());
    QVERIFY(!panel2->isActive());
    QVERIFY(!panel3->isActive());
    QVERIFY(!panel4->isActive());
    QVERIFY(notPanel1->isActive());
    QVERIFY(notPanel2->isActive());
    QCOMPARE(spy_activate_notPanel1.count(), 1);
    QCOMPARE(spy_activate_notPanel2.count(), 1);

    // Deactivate the scene
    QCoreApplication::sendEvent(&scene, &deactivate);
    QVERIFY(!scene.activePanel());
    QVERIFY(!panel1->isActive());
    QVERIFY(!panel2->isActive());
    QVERIFY(!panel3->isActive());
    QVERIFY(!panel4->isActive());
    QVERIFY(!notPanel1->isActive());
    QVERIFY(!notPanel2->isActive());
    QCOMPARE(spy_deactivate_notPanel1.count(), 1);
    QCOMPARE(spy_deactivate_notPanel2.count(), 1);

    // Reactivate the scene
    QCoreApplication::sendEvent(&scene, &activate);
    QVERIFY(!scene.activePanel());
    QVERIFY(!panel1->isActive());
    QVERIFY(!panel2->isActive());
    QVERIFY(!panel3->isActive());
    QVERIFY(!panel4->isActive());
    QVERIFY(notPanel1->isActive());
    QVERIFY(notPanel2->isActive());
    QCOMPARE(spy_activate_notPanel1.count(), 2);
    QCOMPARE(spy_activate_notPanel2.count(), 2);

    // Switch to panel1
    scene.setActivePanel(panel1);
    QVERIFY(panel1->isActive());
    QCOMPARE(spy_deactivate_notPanel1.count(), 2);
    QCOMPARE(spy_deactivate_notPanel2.count(), 2);
    QCOMPARE(spy_activate_panel1.count(), 2);

    // Deactivate the scene
    QCoreApplication::sendEvent(&scene, &deactivate);
    QVERIFY(!panel1->isActive());
    QCOMPARE(spy_deactivate_panel1.count(), 2);

    // Reactivate the scene
    QCoreApplication::sendEvent(&scene, &activate);
    QVERIFY(panel1->isActive());
    QCOMPARE(spy_activate_panel1.count(), 3);

    // Deactivate the scene
    QCoreApplication::sendEvent(&scene, &deactivate);
    QVERIFY(!panel1->isActive());
    QVERIFY(!scene.activePanel());
    scene.setActivePanel(nullptr);

    // Reactivate the scene
    QCoreApplication::sendEvent(&scene, &activate);
    QVERIFY(!panel1->isActive());
}

void tst_QGraphicsItem::panelWithFocusItems()
{
    for (int i = 0; i < 2; ++i)
    {
        QGraphicsScene scene;
        QEvent activate(QEvent::WindowActivate);
        QCoreApplication::sendEvent(&scene, &activate);

        bool widget = (i == 1);
        auto parentPanel = widget
            ? static_cast<QGraphicsItem *>(new QGraphicsWidget)
            : static_cast<QGraphicsItem *>(new QGraphicsRectItem);
        auto parentPanelFocusItem = widget
            ? static_cast<QGraphicsItem *>(new QGraphicsWidget)
            : static_cast<QGraphicsItem *>(new QGraphicsRectItem);
        auto parentPanelFocusItemSibling = widget
            ? static_cast<QGraphicsItem *>(new QGraphicsWidget)
            : static_cast<QGraphicsItem *>(new QGraphicsRectItem);
        parentPanel->setFlag(QGraphicsItem::ItemIsPanel);
        parentPanelFocusItem->setFlag(QGraphicsItem::ItemIsFocusable);
        parentPanelFocusItemSibling->setFlag(QGraphicsItem::ItemIsFocusable);
        if (widget) {
            static_cast<QGraphicsWidget *>(parentPanelFocusItem)->setFocusPolicy(Qt::StrongFocus);
            static_cast<QGraphicsWidget *>(parentPanelFocusItemSibling)->setFocusPolicy(Qt::StrongFocus);
        }
        parentPanelFocusItem->setParentItem(parentPanel);
        parentPanelFocusItemSibling->setParentItem(parentPanel);
        parentPanelFocusItem->setFocus();
        scene.addItem(parentPanel);

        QVERIFY(parentPanel->isActive());
        QVERIFY(parentPanelFocusItem->hasFocus());
        QCOMPARE(parentPanel->focusItem(), parentPanelFocusItem);
        QCOMPARE(parentPanelFocusItem->focusItem(), parentPanelFocusItem);

        auto childPanel = widget
             ? static_cast<QGraphicsItem *>(new QGraphicsWidget)
             : static_cast<QGraphicsItem *>(new QGraphicsRectItem);
        auto childPanelFocusItem = widget
             ? static_cast<QGraphicsItem *>(new QGraphicsWidget)
             : static_cast<QGraphicsItem *>(new QGraphicsRectItem);
        auto grandChildPanelFocusItem = widget
             ? static_cast<QGraphicsItem *>(new QGraphicsWidget)
             : static_cast<QGraphicsItem *>(new QGraphicsRectItem);
        auto grandChildPanelFocusItem2 = widget
             ? static_cast<QGraphicsItem *>(new QGraphicsWidget)
             : static_cast<QGraphicsItem *>(new QGraphicsRectItem);

        childPanel->setFlag(QGraphicsItem::ItemIsPanel);
        childPanelFocusItem->setFlag(QGraphicsItem::ItemIsFocusable);
        grandChildPanelFocusItem->setFlag(QGraphicsItem::ItemIsFocusable);
        grandChildPanelFocusItem2->setFlag(QGraphicsItem::ItemIsFocusable);

        if (widget) {
            static_cast<QGraphicsWidget *>(childPanelFocusItem)->setFocusPolicy(Qt::StrongFocus);
            static_cast<QGraphicsWidget *>(grandChildPanelFocusItem)->setFocusPolicy(Qt::StrongFocus);
            static_cast<QGraphicsWidget *>(grandChildPanelFocusItem2)->setFocusPolicy(Qt::StrongFocus);
        }
        grandChildPanelFocusItem->setParentItem(childPanelFocusItem);
        grandChildPanelFocusItem2->setParentItem(childPanelFocusItem);
        childPanelFocusItem->setParentItem(childPanel);
        grandChildPanelFocusItem->setFocus();

        QVERIFY(!grandChildPanelFocusItem->hasFocus());
        QCOMPARE(childPanel->focusItem(), grandChildPanelFocusItem);
        QCOMPARE(childPanelFocusItem->focusItem(), grandChildPanelFocusItem);
        QCOMPARE(grandChildPanelFocusItem->focusItem(), grandChildPanelFocusItem);

        childPanel->setParentItem(parentPanel);

        QVERIFY(!parentPanel->isActive());
        QVERIFY(!parentPanelFocusItem->hasFocus());
        QCOMPARE(parentPanel->focusItem(), parentPanelFocusItem);
        QCOMPARE(parentPanelFocusItem->focusItem(), parentPanelFocusItem);

        QVERIFY(childPanel->isActive());
        QVERIFY(!childPanelFocusItem->hasFocus());
        QVERIFY(grandChildPanelFocusItem->hasFocus());
        QCOMPARE(childPanel->focusItem(), grandChildPanelFocusItem);
        QCOMPARE(childPanelFocusItem->focusItem(), grandChildPanelFocusItem);

        childPanel->hide();
        QCOMPARE(childPanel->focusItem(), grandChildPanelFocusItem);
        QVERIFY(!childPanel->focusItem()->hasFocus());
        QVERIFY(parentPanel->isActive());
        QVERIFY(parentPanelFocusItem->hasFocus());
        QCOMPARE(parentPanel->focusItem(), parentPanelFocusItem);
        QCOMPARE(parentPanelFocusItem->focusItem(), parentPanelFocusItem);
        QCOMPARE(grandChildPanelFocusItem->focusItem(), grandChildPanelFocusItem);

        childPanel->show();
        QVERIFY(childPanel->isActive());
        QVERIFY(grandChildPanelFocusItem->hasFocus());
        QCOMPARE(childPanel->focusItem(), grandChildPanelFocusItem);
        QCOMPARE(childPanelFocusItem->focusItem(), grandChildPanelFocusItem);
        QCOMPARE(grandChildPanelFocusItem->focusItem(), grandChildPanelFocusItem);

        childPanel->hide();

        QVERIFY(parentPanel->isActive());
        QVERIFY(parentPanelFocusItem->hasFocus());
        QCOMPARE(parentPanel->focusItem(), parentPanelFocusItem);
        QCOMPARE(parentPanelFocusItem->focusItem(), parentPanelFocusItem);
    }
}

void tst_QGraphicsItem::addPanelToActiveScene()
{
    QGraphicsScene scene;
    QVERIFY(!scene.isActive());

    QGraphicsRectItem *rect = new QGraphicsRectItem;
    scene.addItem(rect);
    QVERIFY(!rect->isActive());
    scene.removeItem(rect);

    QEvent activate(QEvent::WindowActivate);
    QEvent deactivate(QEvent::WindowDeactivate);

    QCoreApplication::sendEvent(&scene, &activate);
    QVERIFY(scene.isActive());
    scene.addItem(rect);
    QVERIFY(rect->isActive());
    scene.removeItem(rect);

    rect->setFlag(QGraphicsItem::ItemIsPanel);
    scene.addItem(rect);
    QVERIFY(rect->isActive());
    QCOMPARE(scene.activePanel(), rect);

    QGraphicsRectItem *rect2 = new QGraphicsRectItem;
    scene.addItem(rect2);
    QVERIFY(rect->isActive());
    QCOMPARE(scene.activePanel(), rect);
}

void tst_QGraphicsItem::activate()
{
    QGraphicsScene scene;
    QGraphicsRectItem *rect = scene.addRect(-10, -10, 20, 20);
    QVERIFY(!rect->isActive());

    QEvent activate(QEvent::WindowActivate);
    QEvent deactivate(QEvent::WindowDeactivate);

    QCoreApplication::sendEvent(&scene, &activate);

    // Non-panel item (active when scene is active).
    QVERIFY(rect->isActive());

    QGraphicsRectItem *rect2 = new QGraphicsRectItem;
    rect2->setFlag(QGraphicsItem::ItemIsPanel);
    QGraphicsRectItem *rect3 = new QGraphicsRectItem;
    rect3->setFlag(QGraphicsItem::ItemIsPanel);

    // Test normal activation.
    QVERIFY(!rect2->isActive());
    scene.addItem(rect2);
    QVERIFY(rect2->isActive()); // first panel item is activated
    scene.addItem(rect3);
    QVERIFY(!rect3->isActive()); // second panel item is _not_ activated
    rect3->setActive(true);
    QVERIFY(rect3->isActive());
    scene.removeItem(rect3);
    QVERIFY(!rect3->isActive()); // no panel is active anymore
    QCOMPARE(scene.activePanel(), nullptr);
    scene.addItem(rect3);
    QVERIFY(rect3->isActive()); // second panel item is activated

    // Test pending activation.
    scene.removeItem(rect3);
    rect2->setActive(true);
    QVERIFY(rect2->isActive()); // first panel item is activated
    rect3->setActive(true);
    QVERIFY(!rect3->isActive()); // not active (yet)
    scene.addItem(rect3);
    QVERIFY(rect3->isActive()); // now becomes active

    // Test pending deactivation.
    scene.removeItem(rect3);
    rect3->setActive(false);
    scene.addItem(rect3);
    QVERIFY(!rect3->isActive()); // doesn't become active

    // Child of panel activation.
    rect3->setActive(true);
    QGraphicsRectItem *rect4 = new QGraphicsRectItem;
    rect4->setFlag(QGraphicsItem::ItemIsPanel);
    QGraphicsRectItem *rect5 = new QGraphicsRectItem(rect4);
    QGraphicsRectItem *rect6 = new QGraphicsRectItem(rect5);
    scene.addItem(rect4);
    QCOMPARE(scene.activePanel(), rect3);
    scene.removeItem(rect4);
    rect6->setActive(true);
    scene.addItem(rect4);
    QVERIFY(rect4->isActive());
    QVERIFY(rect5->isActive());
    QVERIFY(rect6->isActive());
    QCOMPARE(scene.activePanel(), rect4);
    scene.removeItem(rect4); // no active panel
    rect6->setActive(false);
    scene.addItem(rect4);
    QVERIFY(!rect4->isActive());
    QVERIFY(!rect5->isActive());
    QVERIFY(!rect6->isActive());
    QCOMPARE(scene.activePanel(), nullptr);

    // Controlling auto-activation when the scene changes activation.
    rect4->setActive(true);
    QCoreApplication::sendEvent(&scene, &deactivate);
    QVERIFY(!scene.isActive());
    QVERIFY(!rect4->isActive());
    rect4->setActive(false);
    QCoreApplication::sendEvent(&scene, &activate);
    QVERIFY(scene.isActive());
    QVERIFY(!scene.activePanel());
    QVERIFY(!rect4->isActive());
}

void tst_QGraphicsItem::setActivePanelOnInactiveScene()
{
    QGraphicsScene scene;
    QGraphicsRectItem *item = scene.addRect(QRectF());
    QGraphicsRectItem *panel = scene.addRect(QRectF());
    panel->setFlag(QGraphicsItem::ItemIsPanel);

    EventSpy itemActivateSpy(&scene, item, QEvent::WindowActivate);
    EventSpy itemDeactivateSpy(&scene, item, QEvent::WindowDeactivate);
    EventSpy panelActivateSpy(&scene, panel, QEvent::WindowActivate);
    EventSpy panelDeactivateSpy(&scene, panel, QEvent::WindowDeactivate);
    EventSpy sceneActivationChangeSpy(&scene, QEvent::ActivationChange);

    scene.setActivePanel(panel);
    QCOMPARE(scene.activePanel(), nullptr);
    QCOMPARE(itemActivateSpy.count(), 0);
    QCOMPARE(itemDeactivateSpy.count(), 0);
    QCOMPARE(panelActivateSpy.count(), 0);
    QCOMPARE(panelDeactivateSpy.count(), 0);
    QCOMPARE(sceneActivationChangeSpy.count(), 0);
}

void tst_QGraphicsItem::activationOnShowHide()
{
    QGraphicsScene scene;
    QEvent activate(QEvent::WindowActivate);
    QCoreApplication::sendEvent(&scene, &activate);

    QGraphicsRectItem *rootPanel = scene.addRect(QRectF());
    rootPanel->setFlag(QGraphicsItem::ItemIsPanel);
    rootPanel->setActive(true);

    QGraphicsRectItem *subPanel = new QGraphicsRectItem;
    subPanel->setFlag(QGraphicsItem::ItemIsPanel);

    // Reparenting onto an active panel auto-activates the child panel.
    subPanel->setParentItem(rootPanel);
    QVERIFY(subPanel->isActive());
    QVERIFY(!rootPanel->isActive());

    // Hiding an active child panel will reactivate the parent panel.
    subPanel->hide();
    QVERIFY(rootPanel->isActive());

    // Showing a child panel will auto-activate it.
    subPanel->show();
    QVERIFY(subPanel->isActive());
    QVERIFY(!rootPanel->isActive());

    // Adding an unrelated panel doesn't affect activation.
    QGraphicsRectItem *otherPanel = new QGraphicsRectItem;
    otherPanel->setFlag(QGraphicsItem::ItemIsPanel);
    scene.addItem(otherPanel);
    QVERIFY(subPanel->isActive());

    // Showing an unrelated panel doesn't affect activation.
    otherPanel->hide();
    otherPanel->show();
    QVERIFY(subPanel->isActive());

    // Add a non-panel item.
    QGraphicsRectItem *otherItem = new QGraphicsRectItem;
    scene.addItem(otherItem);
    otherItem->setActive(true);
    QVERIFY(otherItem->isActive());

    // Reparent a panel onto an active non-panel item.
    subPanel->setParentItem(otherItem);
    QVERIFY(subPanel->isActive());

    // Showing a child panel of a non-panel item will activate it.
    subPanel->hide();
    QVERIFY(!subPanel->isActive());
    QVERIFY(otherItem->isActive());
    subPanel->show();
    QVERIFY(subPanel->isActive());

    // Hiding a toplevel active panel will pass activation back
    // to the non-panel items.
    rootPanel->setActive(true);
    rootPanel->hide();
    QVERIFY(!rootPanel->isActive());
    QVERIFY(otherItem->isActive());
}

class MoveWhileDying : public QGraphicsRectItem
{
public:
    using QGraphicsRectItem::QGraphicsRectItem;

    ~MoveWhileDying()
    {
        const auto children = childItems();
        for (QGraphicsItem *c : children) {
            const auto grandChildren = c->childItems();
            for (QGraphicsItem *cc : grandChildren)
                cc->moveBy(10, 10);
            c->moveBy(10, 10);
        }
        if (QGraphicsItem *p = parentItem())
            p->moveBy(10, 10);
    }
};

void tst_QGraphicsItem::deactivateInactivePanel()
{
    QGraphicsScene scene;
    QGraphicsItem *panel1 = scene.addRect(QRectF(0, 0, 10, 10));
    panel1->setFlag(QGraphicsItem::ItemIsPanel);

    QGraphicsItem *panel2 = scene.addRect(QRectF(0, 0, 10, 10));
    panel2->setFlag(QGraphicsItem::ItemIsPanel);

    QEvent event(QEvent::WindowActivate);
    QCoreApplication::sendEvent(&scene, &event);

    panel1->setActive(true);
    QVERIFY(scene.isActive());
    QVERIFY(panel1->isActive());
    QVERIFY(!panel2->isActive());
    QCOMPARE(scene.activePanel(), panel1);

    panel2->setActive(true);
    QVERIFY(panel2->isActive());
    QVERIFY(!panel1->isActive());
    QCOMPARE(scene.activePanel(), panel2);

    panel2->setActive(false);
    QVERIFY(panel1->isActive());
    QVERIFY(!panel2->isActive());
    QCOMPARE(scene.activePanel(), panel1);

    panel2->setActive(false);
    QVERIFY(panel1->isActive());
    QVERIFY(!panel2->isActive());
    QCOMPARE(scene.activePanel(), panel1);
}

void tst_QGraphicsItem::moveWhileDeleting()
{
    QGraphicsScene scene;
    QGraphicsRectItem *rect = new QGraphicsRectItem;
    MoveWhileDying *silly = new MoveWhileDying(rect);
    QGraphicsRectItem *child = new QGraphicsRectItem(silly);
    scene.addItem(rect);
    delete rect; // don't crash!

    rect = new QGraphicsRectItem;
    silly = new MoveWhileDying(rect);
    child = new QGraphicsRectItem(silly);

    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));

    delete rect;

    rect = new QGraphicsRectItem;
    rect->setFlag(QGraphicsItem::ItemClipsChildrenToShape);
    silly = new MoveWhileDying(rect);
    child = new QGraphicsRectItem(silly);

    QTest::qWait(125);

    delete rect;

    rect = new MoveWhileDying;
    rect->setFlag(QGraphicsItem::ItemClipsChildrenToShape);
    child = new QGraphicsRectItem(rect);
    silly = new MoveWhileDying(child);

    QTest::qWait(125);

    delete rect;
}

class MyRectItem : public QGraphicsWidget
{
    Q_OBJECT
public:
    using QGraphicsWidget::QGraphicsWidget;

    void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override
    {
        painter->setBrush(brush);
        painter->drawRect(boundingRect());
    }
    void move()
    {
        setPos(-100,-100);
        topLevel->collidingItems(Qt::IntersectsItemBoundingRect);
    }
public:
    QGraphicsItem *topLevel = nullptr;
    QBrush brush;
};


void tst_QGraphicsItem::ensureDirtySceneTransform()
{
    QGraphicsScene scene;

    MyRectItem *topLevel = new MyRectItem;
    topLevel->setGeometry(0, 0, 100, 100);
    topLevel->setPos(-50, -50);
    topLevel->brush = QBrush(QColor(Qt::black));
    scene.addItem(topLevel);

    MyRectItem *parent = new MyRectItem;
    parent->topLevel = topLevel;
    parent->setGeometry(0, 0, 100, 100);
    parent->setPos(0, 0);
    parent->brush = QBrush(QColor(Qt::magenta));
    parent->setObjectName("parent");
    scene.addItem(parent);

    MyRectItem *child = new MyRectItem(parent);
    child->setGeometry(0, 0, 80, 80);
    child->setPos(10, 10);
    child->setObjectName("child");
    child->brush = QBrush(QColor(Qt::blue));

    MyRectItem *child2 = new MyRectItem(parent);
    child2->setGeometry(0, 0, 80, 80);
    child2->setPos(15, 15);
    child2->setObjectName("child2");
    child2->brush = QBrush(QColor(Qt::green));

    MyRectItem *child3 = new MyRectItem(parent);
    child3->setGeometry(0, 0, 80, 80);
    child3->setPos(20, 20);
    child3->setObjectName("child3");
    child3->brush = QBrush(QColor(Qt::gray));

    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.show();
    if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) {
        view.window()->activateWindow();
        QVERIFY(QTest::qWaitForWindowActive(&view));
        QCOMPARE(QApplication::activeWindow(), static_cast<QWidget *>(&view));
    }
    QVERIFY(QTest::qWaitForWindowExposed(&view));

    //We move the parent
    parent->move();
    QApplication::processEvents();

    //We check if all items moved
    QCOMPARE(child->pos(), QPointF(10, 10));
    QCOMPARE(child2->pos(), QPointF(15, 15));
    QCOMPARE(child3->pos(), QPointF(20, 20));

    QCOMPARE(child->sceneBoundingRect(), QRectF(-90, -90, 80, 80));
    QCOMPARE(child2->sceneBoundingRect(), QRectF(-85, -85, 80, 80));
    QCOMPARE(child3->sceneBoundingRect(), QRectF(-80, -80, 80, 80));

    QCOMPARE(child->sceneTransform(), QTransform::fromTranslate(-90, -90));
    QCOMPARE(child2->sceneTransform(), QTransform::fromTranslate(-85, -85));
    QCOMPARE(child3->sceneTransform(), QTransform::fromTranslate(-80, -80));
}

void tst_QGraphicsItem::focusScope()
{
    // ItemIsFocusScope is an internal feature (for now).
    QGraphicsScene scene;

    QGraphicsRectItem *scope3 = new QGraphicsRectItem;
    scope3->setData(0, "scope3");
    scope3->setFlags(QGraphicsItem::ItemIsFocusable | QGraphicsItem::ItemIsFocusScope);
    scope3->setFocus();
    QVERIFY(!scope3->focusScopeItem());
    QCOMPARE(scope3->focusItem(), scope3);

    QGraphicsRectItem *scope2 = new QGraphicsRectItem;
    scope2->setData(0, "scope2");
    scope2->setFlags(QGraphicsItem::ItemIsFocusable | QGraphicsItem::ItemIsFocusScope);
    scope2->setFocus();
    QVERIFY(!scope2->focusScopeItem());
    scope3->setParentItem(scope2);
    QCOMPARE(scope2->focusScopeItem(), scope3);
    QCOMPARE(scope2->focusItem(), scope3);

    QGraphicsRectItem *scope1 = new QGraphicsRectItem;
    scope1->setData(0, "scope1");
    scope1->setFlags(QGraphicsItem::ItemIsFocusable | QGraphicsItem::ItemIsFocusScope);
    scope1->setFocus();
    QVERIFY(!scope1->focusScopeItem());
    scope2->setParentItem(scope1);

    QCOMPARE(scope1->focusItem(), scope3);
    QCOMPARE(scope2->focusItem(), scope3);
    QCOMPARE(scope3->focusItem(), scope3);
    QCOMPARE(scope1->focusScopeItem(), scope2);
    QCOMPARE(scope2->focusScopeItem(), scope3);
    QCOMPARE(scope3->focusScopeItem(), nullptr);

    scene.addItem(scope1);

    QEvent windowActivate(QEvent::WindowActivate);
    qApp->sendEvent(&scene, &windowActivate);
    scene.setFocus();

    QCOMPARE(scope1->focusItem(), scope3);
    QCOMPARE(scope2->focusItem(), scope3);
    QCOMPARE(scope3->focusItem(), scope3);
    QCOMPARE(scope1->focusScopeItem(), scope2);
    QCOMPARE(scope2->focusScopeItem(), scope3);
    QCOMPARE(scope3->focusScopeItem(), nullptr);

    QVERIFY(scope3->hasFocus());

    scope3->hide();
    QVERIFY(scope2->hasFocus());
    scope2->hide();
    QVERIFY(scope1->hasFocus());
    scope2->show();
    QVERIFY(scope2->hasFocus());
    scope3->show();
    QVERIFY(scope3->hasFocus());
    scope1->hide();
    QVERIFY(!scope3->hasFocus());
    scope1->show();
    QVERIFY(scope3->hasFocus());
    scope3->clearFocus();
    QVERIFY(scope2->hasFocus());
    scope2->clearFocus();
    QVERIFY(scope1->hasFocus());
    scope2->hide();
    scope2->show();
    QVERIFY(!scope2->hasFocus());
    QVERIFY(scope1->hasFocus());
    scope2->setFocus();
    QVERIFY(scope2->hasFocus());
    scope3->setFocus();
    QVERIFY(scope3->hasFocus());

    // clearFocus() on a focus scope will remove focus from its children.
    scope1->clearFocus();
    QVERIFY(!scope1->hasFocus());
    QVERIFY(!scope2->hasFocus());
    QVERIFY(!scope3->hasFocus());

    scope1->setFocus();
    QVERIFY(!scope1->hasFocus());
    QVERIFY(!scope2->hasFocus());
    QVERIFY(scope3->hasFocus());

    scope2->clearFocus();
    QVERIFY(scope1->hasFocus());
    QVERIFY(!scope2->hasFocus());
    QVERIFY(!scope3->hasFocus());

    scope2->setFocus();
    QVERIFY(!scope1->hasFocus());
    QVERIFY(!scope2->hasFocus());
    QVERIFY(scope3->hasFocus());

    // Focus cleared while a parent doesn't have focus remains cleared
    // when the parent regains focus.
    scope1->clearFocus();
    scope3->clearFocus();
    QVERIFY(!scope1->hasFocus());
    QVERIFY(!scope2->hasFocus());
    QVERIFY(!scope3->hasFocus());

    scope1->setFocus();
    QVERIFY(!scope1->hasFocus());
    QVERIFY(scope2->hasFocus());
    QVERIFY(!scope3->hasFocus());

    scope3->setFocus();
    QVERIFY(!scope1->hasFocus());
    QVERIFY(!scope2->hasFocus());
    QVERIFY(scope3->hasFocus());

    QGraphicsRectItem *rect4 = new QGraphicsRectItem;
    rect4->setData(0, "rect4");
    rect4->setParentItem(scope3);

    QGraphicsRectItem *rect5 = new QGraphicsRectItem;
    rect5->setData(0, "rect5");
    rect5->setFlags(QGraphicsItem::ItemIsFocusable | QGraphicsItem::ItemIsFocusScope);
    rect5->setFocus();
    rect5->setParentItem(rect4);
    QCOMPARE(scope3->focusScopeItem(), rect5);
    QVERIFY(rect5->hasFocus());

    rect4->setParentItem(nullptr);
    QVERIFY(rect5->hasFocus());
    QCOMPARE(scope3->focusScopeItem(), nullptr);
    QCOMPARE(scope3->focusItem(), nullptr);
    QVERIFY(!scope3->hasFocus());

    QGraphicsRectItem *rectA = new QGraphicsRectItem;
    QGraphicsRectItem *scopeA = new QGraphicsRectItem(rectA);
    scopeA->setFlags(QGraphicsItem::ItemIsFocusable | QGraphicsItem::ItemIsFocusScope);
    scopeA->setFocus();
    QGraphicsRectItem *scopeB = new QGraphicsRectItem(scopeA);
    scopeB->setFlags(QGraphicsItem::ItemIsFocusable | QGraphicsItem::ItemIsFocusScope);
    scopeB->setFocus();

    scene.addItem(rectA);
    QVERIFY(rect5->hasFocus());
    QVERIFY(!scopeB->hasFocus());

    scopeA->setFocus();
    QVERIFY(scopeB->hasFocus());
    QCOMPARE(scopeB->focusItem(), scopeB);
}

void tst_QGraphicsItem::focusScope2()
{
    QGraphicsRectItem *child1 = new QGraphicsRectItem;
    child1->setFlags(QGraphicsItem::ItemIsFocusable);
    child1->setFocus();
    QCOMPARE(child1->focusItem(), child1);

    QGraphicsRectItem *child2 = new QGraphicsRectItem;
    child2->setFlags(QGraphicsItem::ItemIsFocusable);

    QGraphicsRectItem *rootFocusScope = new QGraphicsRectItem;
    rootFocusScope->setFlags(QGraphicsItem::ItemIsFocusable | QGraphicsItem::ItemIsFocusScope);
    rootFocusScope->setFocus();
    QCOMPARE(rootFocusScope->focusItem(), rootFocusScope);

    child1->setParentItem(rootFocusScope);
    child2->setParentItem(rootFocusScope);

    QCOMPARE(rootFocusScope->focusScopeItem(), child1);
    QCOMPARE(rootFocusScope->focusItem(), child1);

    QGraphicsRectItem *siblingChild1 = new QGraphicsRectItem;
    siblingChild1->setFlags(QGraphicsItem::ItemIsFocusable);
    siblingChild1->setFocus();

    QGraphicsRectItem *siblingChild2 = new QGraphicsRectItem;
    siblingChild2->setFlags(QGraphicsItem::ItemIsFocusable);

    QGraphicsRectItem *siblingFocusScope = new QGraphicsRectItem;
    siblingFocusScope->setFlags(QGraphicsItem::ItemIsFocusable | QGraphicsItem::ItemIsFocusScope);

    siblingChild1->setParentItem(siblingFocusScope);
    siblingChild2->setParentItem(siblingFocusScope);

    QCOMPARE(siblingFocusScope->focusScopeItem(), siblingChild1);
    QCOMPARE(siblingFocusScope->focusItem(), nullptr);

    QGraphicsItem *root = new QGraphicsRectItem;
    rootFocusScope->setParentItem(root);
    siblingFocusScope->setParentItem(root);

    QCOMPARE(root->focusItem(), child1);

    QGraphicsScene scene;
    scene.addItem(root);

    QEvent activate(QEvent::WindowActivate);
    qApp->sendEvent(&scene, &activate);
    scene.setFocus();

    QCOMPARE(scene.focusItem(), child1);

    // You cannot set focus on a descendant of a focus scope directly;
    // this will only change the scope's focus scope item pointer. If
    // you want to give true input focus, you must set it directly on
    // the scope itself
    siblingChild2->setFocus();
    QVERIFY(!siblingChild2->hasFocus());
    QVERIFY(!siblingChild2->focusItem());
    QCOMPARE(siblingFocusScope->focusScopeItem(), siblingChild2);
    QCOMPARE(siblingFocusScope->focusItem(), nullptr);

    // Set focus on the scope; focus is forwarded to the focus scope item.
    siblingFocusScope->setFocus();
    QVERIFY(siblingChild2->hasFocus());
    QVERIFY(siblingChild2->focusItem());
    QCOMPARE(siblingFocusScope->focusScopeItem(), siblingChild2);
    QCOMPARE(siblingFocusScope->focusItem(), siblingChild2);
}

class FocusScopeItemPrivate;
class FocusScopeItem : public QGraphicsItem
{
    Q_DECLARE_PRIVATE(FocusScopeItem)
public:
    FocusScopeItem(QGraphicsItem *parent = nullptr);
    QRectF boundingRect() const override { return QRectF(); }
    void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *) override { }

    int focusScopeChanged = 0;
    FocusScopeItemPrivate *d_ptr;
};

class FocusScopeItemPrivate : QGraphicsItemPrivate
{
    Q_DECLARE_PUBLIC(FocusScopeItem)
public:
    void focusScopeItemChange(bool) override
    { ++q_func()->focusScopeChanged; }
};

FocusScopeItem::FocusScopeItem(QGraphicsItem *parent)
    : QGraphicsItem(*new FocusScopeItemPrivate, parent)
{
    setFlag(ItemIsFocusable);
}

void tst_QGraphicsItem::focusScopeItemChangedWhileScopeDoesntHaveFocus()
{
    QGraphicsRectItem rect;
    rect.setFlags(QGraphicsItem::ItemIsFocusScope | QGraphicsItem::ItemIsFocusable);

    FocusScopeItem *child1 = new FocusScopeItem(&rect);
    FocusScopeItem *child2 = new FocusScopeItem(&rect);

    QCOMPARE(rect.focusScopeItem(), nullptr);
    QCOMPARE(child1->focusScopeChanged, 0);
    QCOMPARE(child2->focusScopeChanged, 0);
    child1->setFocus();
    QCOMPARE(rect.focusScopeItem(), child1);
    QCOMPARE(child1->focusScopeChanged, 1);
    QCOMPARE(child2->focusScopeChanged, 0);
    child2->setFocus();
    QCOMPARE(rect.focusScopeItem(), child2);
    QCOMPARE(child1->focusScopeChanged, 2);
    QCOMPARE(child2->focusScopeChanged, 1);
    child1->setFocus();
    QCOMPARE(rect.focusScopeItem(), child1);
    QCOMPARE(child1->focusScopeChanged, 3);
    QCOMPARE(child2->focusScopeChanged, 2);
    child1->clearFocus();
    QCOMPARE(rect.focusScopeItem(), nullptr);
    QCOMPARE(child1->focusScopeChanged, 4);
    QCOMPARE(child2->focusScopeChanged, 2);
}

void tst_QGraphicsItem::stackBefore()
{
    QGraphicsRectItem parent;
    QGraphicsRectItem *child1 = new QGraphicsRectItem(QRectF(0, 0, 5, 5), &parent);
    QGraphicsRectItem *child2 = new QGraphicsRectItem(QRectF(0, 0, 5, 5), &parent);
    QGraphicsRectItem *child3 = new QGraphicsRectItem(QRectF(0, 0, 5, 5), &parent);
    QGraphicsRectItem *child4 = new QGraphicsRectItem(QRectF(0, 0, 5, 5), &parent);
    const GraphicsItemsList expected1234{child1, child2, child3, child4};
    QCOMPARE(parent.childItems(), expected1234);
    child1->setData(0, "child1");
    child2->setData(0, "child2");
    child3->setData(0, "child3");
    child4->setData(0, "child4");

    // Remove and append
    child2->setParentItem(nullptr);
    child2->setParentItem(&parent);
    const GraphicsItemsList expected1342{child1, child3, child4, child2};
    QCOMPARE(parent.childItems(), expected1342);

    // Move child2 before child1
    child2->stackBefore(child1); // 2134
    const GraphicsItemsList expected2134{child2, child1, child3, child4};
    QCOMPARE(parent.childItems(), expected2134);
    child2->stackBefore(child2); // 2134
    QCOMPARE(parent.childItems(), expected2134);
    child1->setZValue(1); // 2341
    const GraphicsItemsList expected2341{child2, child3, child4, child1};
    QCOMPARE(parent.childItems(), expected2341);
    child1->stackBefore(child2); // 2341
    QCOMPARE(parent.childItems(), expected2341);
    child1->setZValue(0); // 1234
    QCOMPARE(parent.childItems(), expected1234);
    child4->stackBefore(child1); // 4123
    const GraphicsItemsList expected4123{child4, child1, child2, child3};
    QCOMPARE(parent.childItems(), expected4123);
    child4->setZValue(1); // 1234 (4123)
    QCOMPARE(parent.childItems(), expected1234);
    child3->stackBefore(child1); // 3124 (4312)
    const GraphicsItemsList expected3124{child3, child1, child2, child4};
    QCOMPARE(parent.childItems(), expected3124);
    child4->setZValue(0); // 4312
    const GraphicsItemsList expected4312{child4, child3, child1, child2};
    QCOMPARE(parent.childItems(), expected4312);

    // Make them all toplevels
    child1->setParentItem(nullptr);
    child2->setParentItem(nullptr);
    child3->setParentItem(nullptr);
    child4->setParentItem(nullptr);

    QGraphicsScene scene;
    scene.addItem(child1);
    scene.addItem(child2);
    scene.addItem(child3);
    scene.addItem(child4);
    QCOMPARE(scene.items(QPointF(2, 2), Qt::IntersectsItemBoundingRect, Qt::AscendingOrder), expected1234);

    // Remove and append
    scene.removeItem(child2);
    scene.addItem(child2);
    QCOMPARE(scene.items(QPointF(2, 2), Qt::IntersectsItemBoundingRect, Qt::AscendingOrder), expected1342);

    // Move child2 before child1
    child2->stackBefore(child1); // 2134
    QCOMPARE(scene.items(QPointF(2, 2), Qt::IntersectsItemBoundingRect, Qt::AscendingOrder), expected2134);
    child2->stackBefore(child2); // 2134
    QCOMPARE(scene.items(QPointF(2, 2), Qt::IntersectsItemBoundingRect, Qt::AscendingOrder), expected2134);
    child1->setZValue(1); // 2341
    QCOMPARE(scene.items(QPointF(2, 2), Qt::IntersectsItemBoundingRect, Qt::AscendingOrder), expected2341);
    child1->stackBefore(child2); // 2341
    QCOMPARE(scene.items(QPointF(2, 2), Qt::IntersectsItemBoundingRect, Qt::AscendingOrder), expected2341);
    child1->setZValue(0); // 1234
    QCOMPARE(scene.items(QPointF(2, 2), Qt::IntersectsItemBoundingRect, Qt::AscendingOrder), expected1234);
    child4->stackBefore(child1); // 4123
    QCOMPARE(scene.items(QPointF(2, 2), Qt::IntersectsItemBoundingRect, Qt::AscendingOrder), expected4123);
    child4->setZValue(1); // 1234 (4123)
    QCOMPARE(scene.items(QPointF(2, 2), Qt::IntersectsItemBoundingRect, Qt::AscendingOrder), expected1234);
    child3->stackBefore(child1); // 3124 (4312)
    QCOMPARE(scene.items(QPointF(2, 2), Qt::IntersectsItemBoundingRect, Qt::AscendingOrder), expected3124);
    child4->setZValue(0); // 4312
    QCOMPARE(scene.items(QPointF(2, 2), Qt::IntersectsItemBoundingRect, Qt::AscendingOrder), expected4312);
}

void tst_QGraphicsItem::QTBUG_4233_updateCachedWithSceneRect()
{
    if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation))
        QSKIP("Window activation is not supported");

    EventTester *tester = new EventTester;
    tester->setCacheMode(QGraphicsItem::ItemCoordinateCache);

    QGraphicsScene scene;
    scene.addItem(tester);
    scene.setSceneRect(-100, -100, 200, 200); // contains the tester item

    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.show();
    view.window()->activateWindow();
    QVERIFY(QTest::qWaitForWindowExposed(&view));
    QVERIFY(QTest::qWaitForWindowActive(&view));
    QCOMPARE(QApplication::activeWindow(), &view);

    QTRY_COMPARE(tester->repaints, 1);

    scene.update(); // triggers "updateAll" optimization
    QCoreApplication::processEvents();
    QCoreApplication::processEvents(); // in 4.6 only one processEvents is necessary

    QCOMPARE(tester->repaints, 1);

    scene.update(); // triggers "updateAll" optimization
    tester->update();
    QCoreApplication::processEvents();
    QCoreApplication::processEvents(); // in 4.6 only one processEvents is necessary

    QCOMPARE(tester->repaints, 2);
}

void tst_QGraphicsItem::sceneModality()
{
    // 1) Test mouse events (delivery/propagation/redirection)
    // 2) Test hover events (incl. leave on block, enter on unblock)
    // 3) Test cursor stuff (incl. unset on block, set on unblock)
    // 4) Test clickfocus
    // 5) Test grab/ungrab events (possibly ungrab on block, regrab on unblock)
    // 6) ### modality for non-panels is unsupported for now
    QGraphicsScene scene;

    QGraphicsRectItem *bottomItem = scene.addRect(-150, -100, 300, 200);
    bottomItem->setFlag(QGraphicsItem::ItemIsFocusable);
    bottomItem->setBrush(Qt::yellow);

    QGraphicsRectItem *leftParent = scene.addRect(-50, -50, 100, 100);
    leftParent->setFlag(QGraphicsItem::ItemIsPanel);
    leftParent->setBrush(Qt::blue);

    QGraphicsRectItem *leftChild = scene.addRect(-25, -25, 50, 50);
    leftChild->setFlag(QGraphicsItem::ItemIsPanel);
    leftChild->setBrush(Qt::green);
    leftChild->setParentItem(leftParent);

    QGraphicsRectItem *rightParent = scene.addRect(-50, -50, 100, 100);
    rightParent->setFlag(QGraphicsItem::ItemIsPanel);
    rightParent->setBrush(Qt::red);
    QGraphicsRectItem *rightChild = scene.addRect(-25, -25, 50, 50);
    rightChild->setFlag(QGraphicsItem::ItemIsPanel);
    rightChild->setBrush(Qt::gray);
    rightChild->setParentItem(rightParent);

    leftParent->setPos(-75, 0);
    rightParent->setPos(75, 0);

    bottomItem->setData(0, "bottomItem");
    leftParent->setData(0, "leftParent");
    leftChild->setData(0, "leftChild");
    rightParent->setData(0, "rightParent");
    rightChild->setData(0, "rightChild");

    scene.setSceneRect(scene.itemsBoundingRect().adjusted(-50, -50, 50, 50));

    EventSpy2 leftParentSpy(&scene, leftParent);
    EventSpy2 leftChildSpy(&scene, leftChild);
    EventSpy2 rightParentSpy(&scene, rightParent);
    EventSpy2 rightChildSpy(&scene, rightChild);
    EventSpy2 bottomItemSpy(&scene, bottomItem);

    // Scene modality, also test multiple scene modal items
    leftChild->setPanelModality(QGraphicsItem::SceneModal);
    QCOMPARE(leftChildSpy.counts[QEvent::WindowBlocked], 0);
    QCOMPARE(leftParentSpy.counts[QEvent::WindowBlocked], 1);
    QCOMPARE(rightParentSpy.counts[QEvent::WindowBlocked], 1);
    QCOMPARE(rightChildSpy.counts[QEvent::WindowBlocked], 1);
    QCOMPARE(bottomItemSpy.counts[QEvent::WindowBlocked], 0); // not a panel

    // Click inside left child
    sendMouseClick(&scene, leftChild->scenePos(), Qt::LeftButton);
    QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMousePress], 1);
    QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMouseRelease], 0); // no grab
    QCOMPARE(leftParentSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked
    QCOMPARE(rightParentSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked
    QCOMPARE(rightChildSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked
    QCOMPARE(bottomItemSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked

    // Click on left parent, event goes to modal child
    sendMouseClick(&scene, leftParent->sceneBoundingRect().topLeft() + QPointF(5, 5), Qt::LeftButton);
    QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMousePress], 2);
    QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMouseRelease], 0); // no grab
    QCOMPARE(leftParentSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked
    QCOMPARE(rightParentSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked
    QCOMPARE(rightChildSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked
    QCOMPARE(bottomItemSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked

    // Click on all other items and outside the items
    sendMouseClick(&scene, rightParent->sceneBoundingRect().topLeft() + QPointF(5, 5), Qt::LeftButton);
    QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMousePress], 3);
    sendMouseClick(&scene, rightChild->scenePos(), Qt::LeftButton);
    QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMousePress], 4);
    sendMouseClick(&scene, bottomItem->scenePos(), Qt::LeftButton);
    QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMousePress], 5);
    sendMouseClick(&scene, QPointF(10000, 10000), Qt::LeftButton);
    QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMousePress], 6);
    QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMouseRelease], 0); // no grab
    QCOMPARE(leftParentSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked
    QCOMPARE(rightParentSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked
    QCOMPARE(rightChildSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked
    QCOMPARE(bottomItemSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked

    leftChildSpy.counts.clear();
    rightChildSpy.counts.clear();
    leftParentSpy.counts.clear();
    rightParentSpy.counts.clear();
    bottomItemSpy.counts.clear();

    leftChild->setPanelModality(QGraphicsItem::NonModal);
    QCOMPARE(leftChildSpy.counts[QEvent::WindowUnblocked], 0);
    QCOMPARE(rightChildSpy.counts[QEvent::WindowUnblocked], 1);
    QCOMPARE(leftParentSpy.counts[QEvent::WindowUnblocked], 1);
    QCOMPARE(rightParentSpy.counts[QEvent::WindowUnblocked], 1);
    QCOMPARE(bottomItemSpy.counts[QEvent::WindowUnblocked], 0);

    // Left parent enters scene modality.
    leftParent->setPanelModality(QGraphicsItem::SceneModal);
    QCOMPARE(leftChildSpy.counts[QEvent::WindowBlocked], 0);
    QCOMPARE(rightChildSpy.counts[QEvent::WindowBlocked], 1);
    QCOMPARE(leftParentSpy.counts[QEvent::WindowBlocked], 0);
    QCOMPARE(rightParentSpy.counts[QEvent::WindowBlocked], 1);
    QCOMPARE(bottomItemSpy.counts[QEvent::WindowBlocked], 0);

    // Click inside left child.
    sendMouseClick(&scene, leftChild->scenePos(), Qt::LeftButton);
    QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMousePress], 1);
    QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMouseRelease], 0);
    QCOMPARE(leftParentSpy.counts[QEvent::GraphicsSceneMousePress], 0); // panel stops propagation
    QCOMPARE(rightParentSpy.counts[QEvent::GraphicsSceneMousePress], 0);
    QCOMPARE(rightChildSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked
    QCOMPARE(bottomItemSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked

   // Click on left parent.
    sendMouseClick(&scene, leftParent->sceneBoundingRect().topLeft() + QPointF(5, 5), Qt::LeftButton);
    QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMousePress], 1);
    QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMouseRelease], 0);
    QCOMPARE(leftParentSpy.counts[QEvent::GraphicsSceneMousePress], 1);
    QCOMPARE(rightParentSpy.counts[QEvent::GraphicsSceneMousePress], 0);
    QCOMPARE(rightChildSpy.counts[QEvent::GraphicsSceneMousePress], 0);
    QCOMPARE(bottomItemSpy.counts[QEvent::GraphicsSceneMousePress], 0);

    // Click on all other items and outside the items
    sendMouseClick(&scene, rightParent->sceneBoundingRect().topLeft() + QPointF(5, 5), Qt::LeftButton);
    QCOMPARE(leftParentSpy.counts[QEvent::GraphicsSceneMousePress], 2);
    sendMouseClick(&scene, rightChild->scenePos(), Qt::LeftButton);
    QCOMPARE(leftParentSpy.counts[QEvent::GraphicsSceneMousePress], 3);
    sendMouseClick(&scene, bottomItem->scenePos(), Qt::LeftButton);
    QCOMPARE(leftParentSpy.counts[QEvent::GraphicsSceneMousePress], 4);
    sendMouseClick(&scene, QPointF(10000, 10000), Qt::LeftButton);
    QCOMPARE(leftParentSpy.counts[QEvent::GraphicsSceneMousePress], 5);
    QCOMPARE(leftParentSpy.counts[QEvent::GraphicsSceneMouseRelease], 0);
    QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMousePress], 1);
    QCOMPARE(rightParentSpy.counts[QEvent::GraphicsSceneMousePress], 0);
    QCOMPARE(rightChildSpy.counts[QEvent::GraphicsSceneMousePress], 0);
    QCOMPARE(bottomItemSpy.counts[QEvent::GraphicsSceneMousePress], 0);

    leftChildSpy.counts.clear();
    rightChildSpy.counts.clear();
    leftParentSpy.counts.clear();
    rightParentSpy.counts.clear();
    bottomItemSpy.counts.clear();

    // Now both left parent and child are scene modal. Left parent is blocked.
    leftChild->setPanelModality(QGraphicsItem::SceneModal);
    QCOMPARE(leftChildSpy.counts[QEvent::WindowBlocked], 0);
    QCOMPARE(rightChildSpy.counts[QEvent::WindowBlocked], 0);
    QCOMPARE(leftParentSpy.counts[QEvent::WindowBlocked], 1);
    QCOMPARE(rightParentSpy.counts[QEvent::WindowBlocked], 0);
    QCOMPARE(bottomItemSpy.counts[QEvent::WindowBlocked], 0);

    // Click inside left child
    sendMouseClick(&scene, leftChild->scenePos(), Qt::LeftButton);
    QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMousePress], 1);
    QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMouseRelease], 0); // no grab
    QCOMPARE(leftParentSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked
    QCOMPARE(rightParentSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked
    QCOMPARE(rightChildSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked
    QCOMPARE(bottomItemSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked

    // Click on left parent, event goes to modal child
    sendMouseClick(&scene, leftParent->sceneBoundingRect().topLeft() + QPointF(5, 5), Qt::LeftButton);
    QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMousePress], 2);
    QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMouseRelease], 0); // no grab
    QCOMPARE(leftParentSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked
    QCOMPARE(rightParentSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked
    QCOMPARE(rightChildSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked
    QCOMPARE(bottomItemSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked

    // Click on all other items and outside the items
    sendMouseClick(&scene, rightParent->sceneBoundingRect().topLeft() + QPointF(5, 5), Qt::LeftButton);
    QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMousePress], 3);
    sendMouseClick(&scene, rightChild->scenePos(), Qt::LeftButton);
    QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMousePress], 4);
    sendMouseClick(&scene, bottomItem->scenePos(), Qt::LeftButton);
    QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMousePress], 5);
    sendMouseClick(&scene, QPointF(10000, 10000), Qt::LeftButton);
    QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMousePress], 6);
    QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMouseRelease], 0); // no grab
    QCOMPARE(leftParentSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked
    QCOMPARE(rightParentSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked
    QCOMPARE(rightChildSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked
    QCOMPARE(bottomItemSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked

    leftChildSpy.counts.clear();
    rightChildSpy.counts.clear();
    leftParentSpy.counts.clear();
    rightParentSpy.counts.clear();
    bottomItemSpy.counts.clear();

    // Right child enters scene modality, only left child is blocked.
    rightChild->setPanelModality(QGraphicsItem::SceneModal);
    QCOMPARE(leftChildSpy.counts[QEvent::WindowBlocked], 1);
    QCOMPARE(rightChildSpy.counts[QEvent::WindowBlocked], 0);
    QCOMPARE(leftParentSpy.counts[QEvent::WindowBlocked], 0);
    QCOMPARE(rightParentSpy.counts[QEvent::WindowBlocked], 0);
    QCOMPARE(bottomItemSpy.counts[QEvent::WindowBlocked], 0);
}

void tst_QGraphicsItem::panelModality()
{
    // 1) Test mouse events (delivery/propagation/redirection)
    // 2) Test hover events (incl. leave on block, enter on unblock)
    // 3) Test cursor stuff (incl. unset on block, set on unblock)
    // 4) Test clickfocus
    // 5) Test grab/ungrab events (possibly ungrab on block, regrab on unblock)
    // 6) ### modality for non-panels is unsupported for now
    QGraphicsScene scene;

    QGraphicsRectItem *bottomItem = scene.addRect(-150, -100, 300, 200);
    bottomItem->setFlag(QGraphicsItem::ItemIsFocusable);
    bottomItem->setBrush(Qt::yellow);

    QGraphicsRectItem *leftParent = scene.addRect(-50, -50, 100, 100);
    leftParent->setFlag(QGraphicsItem::ItemIsPanel);
    leftParent->setBrush(Qt::blue);

    QGraphicsRectItem *leftChild = scene.addRect(-25, -25, 50, 50);
    leftChild->setFlag(QGraphicsItem::ItemIsPanel);
    leftChild->setBrush(Qt::green);
    leftChild->setParentItem(leftParent);

    QGraphicsRectItem *rightParent = scene.addRect(-50, -50, 100, 100);
    rightParent->setFlag(QGraphicsItem::ItemIsPanel);
    rightParent->setBrush(Qt::red);
    QGraphicsRectItem *rightChild = scene.addRect(-25, -25, 50, 50);
    rightChild->setFlag(QGraphicsItem::ItemIsPanel);
    rightChild->setBrush(Qt::gray);
    rightChild->setParentItem(rightParent);

    leftParent->setPos(-75, 0);
    rightParent->setPos(75, 0);

    bottomItem->setData(0, "bottomItem");
    leftParent->setData(0, "leftParent");
    leftChild->setData(0, "leftChild");
    rightParent->setData(0, "rightParent");
    rightChild->setData(0, "rightChild");

    scene.setSceneRect(scene.itemsBoundingRect().adjusted(-50, -50, 50, 50));

    EventSpy2 leftParentSpy(&scene, leftParent);
    EventSpy2 leftChildSpy(&scene, leftChild);
    EventSpy2 rightParentSpy(&scene, rightParent);
    EventSpy2 rightChildSpy(&scene, rightChild);
    EventSpy2 bottomItemSpy(&scene, bottomItem);

    // Left Child enters panel modality, only left parent is blocked.
    leftChild->setPanelModality(QGraphicsItem::PanelModal);
    QCOMPARE(leftChildSpy.counts[QEvent::WindowBlocked], 0);
    QCOMPARE(rightChildSpy.counts[QEvent::WindowBlocked], 0);
    QCOMPARE(leftParentSpy.counts[QEvent::WindowBlocked], 1);
    QCOMPARE(rightParentSpy.counts[QEvent::WindowBlocked], 0);
    QCOMPARE(bottomItemSpy.counts[QEvent::WindowBlocked], 0);

    leftChild->setPanelModality(QGraphicsItem::NonModal);
    leftChildSpy.counts.clear();
    rightChildSpy.counts.clear();
    leftParentSpy.counts.clear();
    rightParentSpy.counts.clear();
    bottomItemSpy.counts.clear();

    // Left parent enter panel modality, nothing is blocked.
    leftParent->setPanelModality(QGraphicsItem::PanelModal);
    QCOMPARE(leftChildSpy.counts[QEvent::WindowBlocked], 0);
    QCOMPARE(rightChildSpy.counts[QEvent::WindowBlocked], 0);
    QCOMPARE(leftParentSpy.counts[QEvent::WindowBlocked], 0);
    QCOMPARE(rightParentSpy.counts[QEvent::WindowBlocked], 0);
    QCOMPARE(bottomItemSpy.counts[QEvent::WindowBlocked], 0);

    // Left child enters panel modality, left parent is blocked again.
    leftChild->setPanelModality(QGraphicsItem::PanelModal);
    QCOMPARE(leftChildSpy.counts[QEvent::WindowBlocked], 0);
    QCOMPARE(rightChildSpy.counts[QEvent::WindowBlocked], 0);
    QCOMPARE(leftParentSpy.counts[QEvent::WindowBlocked], 1);
    QCOMPARE(rightParentSpy.counts[QEvent::WindowBlocked], 0);
    QCOMPARE(bottomItemSpy.counts[QEvent::WindowBlocked], 0);

    leftChildSpy.counts.clear();
    rightChildSpy.counts.clear();
    leftParentSpy.counts.clear();
    rightParentSpy.counts.clear();
    bottomItemSpy.counts.clear();

    leftChild->setPanelModality(QGraphicsItem::NonModal);
    QCOMPARE(leftParentSpy.counts[QEvent::WindowUnblocked], 1);
    leftParent->setPanelModality(QGraphicsItem::NonModal);
    QCOMPARE(leftChildSpy.counts[QEvent::WindowUnblocked], 0);
    QCOMPARE(rightChildSpy.counts[QEvent::WindowUnblocked], 0);
    QCOMPARE(leftParentSpy.counts[QEvent::WindowUnblocked], 1);
    QCOMPARE(rightParentSpy.counts[QEvent::WindowUnblocked], 0);
    QCOMPARE(bottomItemSpy.counts[QEvent::WindowUnblocked], 0);

    leftChildSpy.counts.clear();
    rightChildSpy.counts.clear();
    leftParentSpy.counts.clear();
    rightParentSpy.counts.clear();
    bottomItemSpy.counts.clear();

    // Left and right child enter panel modality, both parents are blocked.
    rightChild->setPanelModality(QGraphicsItem::PanelModal);
    QCOMPARE(leftParentSpy.counts[QEvent::WindowBlocked], 0);
    QCOMPARE(rightParentSpy.counts[QEvent::WindowBlocked], 1);
    leftChild->setPanelModality(QGraphicsItem::PanelModal);
    QCOMPARE(leftParentSpy.counts[QEvent::WindowBlocked], 1);
    QCOMPARE(rightParentSpy.counts[QEvent::WindowBlocked], 1);
}

void tst_QGraphicsItem::mixedModality()
{
    // 1) Test mouse events (delivery/propagation/redirection)
    // 2) Test hover events (incl. leave on block, enter on unblock)
    // 3) Test cursor stuff (incl. unset on block, set on unblock)
    // 4) Test clickfocus
    // 5) Test grab/ungrab events (possibly ungrab on block, regrab on unblock)
    // 6) ### modality for non-panels is unsupported for now
    QGraphicsScene scene;

    QGraphicsRectItem *bottomItem = scene.addRect(-150, -100, 300, 200);
    bottomItem->setFlag(QGraphicsItem::ItemIsFocusable);
    bottomItem->setBrush(Qt::yellow);

    QGraphicsRectItem *leftParent = scene.addRect(-50, -50, 100, 100);
    leftParent->setFlag(QGraphicsItem::ItemIsPanel);
    leftParent->setBrush(Qt::blue);

    QGraphicsRectItem *leftChild = scene.addRect(-25, -25, 50, 50);
    leftChild->setFlag(QGraphicsItem::ItemIsPanel);
    leftChild->setBrush(Qt::green);
    leftChild->setParentItem(leftParent);

    QGraphicsRectItem *rightParent = scene.addRect(-50, -50, 100, 100);
    rightParent->setFlag(QGraphicsItem::ItemIsPanel);
    rightParent->setBrush(Qt::red);
    QGraphicsRectItem *rightChild = scene.addRect(-25, -25, 50, 50);
    rightChild->setFlag(QGraphicsItem::ItemIsPanel);
    rightChild->setBrush(Qt::gray);
    rightChild->setParentItem(rightParent);

    leftParent->setPos(-75, 0);
    rightParent->setPos(75, 0);

    bottomItem->setData(0, "bottomItem");
    leftParent->setData(0, "leftParent");
    leftChild->setData(0, "leftChild");
    rightParent->setData(0, "rightParent");
    rightChild->setData(0, "rightChild");

    scene.setSceneRect(scene.itemsBoundingRect().adjusted(-50, -50, 50, 50));

    EventSpy2 leftParentSpy(&scene, leftParent);
    EventSpy2 leftChildSpy(&scene, leftChild);
    EventSpy2 rightParentSpy(&scene, rightParent);
    EventSpy2 rightChildSpy(&scene, rightChild);
    EventSpy2 bottomItemSpy(&scene, bottomItem);

    // Left Child enters panel modality, only left parent is blocked.
    leftChild->setPanelModality(QGraphicsItem::PanelModal);
    QCOMPARE(leftChildSpy.counts[QEvent::WindowBlocked], 0);
    QCOMPARE(rightChildSpy.counts[QEvent::WindowBlocked], 0);
    QCOMPARE(leftParentSpy.counts[QEvent::WindowBlocked], 1);
    QCOMPARE(rightParentSpy.counts[QEvent::WindowBlocked], 0);

    // Left parent enters scene modality, which blocks everything except the child.
    leftParent->setPanelModality(QGraphicsItem::SceneModal);
    QCOMPARE(leftChildSpy.counts[QEvent::WindowBlocked], 0);
    QCOMPARE(leftChildSpy.counts[QEvent::WindowUnblocked], 0);
    QCOMPARE(rightChildSpy.counts[QEvent::WindowBlocked], 1);
    QCOMPARE(rightChildSpy.counts[QEvent::WindowUnblocked], 0);
    QCOMPARE(leftParentSpy.counts[QEvent::WindowBlocked], 1);
    QCOMPARE(leftParentSpy.counts[QEvent::WindowUnblocked], 0);
    QCOMPARE(rightParentSpy.counts[QEvent::WindowBlocked], 1);
    QCOMPARE(rightParentSpy.counts[QEvent::WindowUnblocked], 0);

    // Right child enters panel modality (changes nothing).
    rightChild->setPanelModality(QGraphicsItem::PanelModal);
    QCOMPARE(leftChildSpy.counts[QEvent::WindowBlocked], 0);
    QCOMPARE(leftChildSpy.counts[QEvent::WindowUnblocked], 0);
    QCOMPARE(rightChildSpy.counts[QEvent::WindowBlocked], 1);
    QCOMPARE(rightChildSpy.counts[QEvent::WindowUnblocked], 0);
    QCOMPARE(leftParentSpy.counts[QEvent::WindowBlocked], 1);
    QCOMPARE(leftParentSpy.counts[QEvent::WindowUnblocked], 0);
    QCOMPARE(rightParentSpy.counts[QEvent::WindowBlocked], 1);
    QCOMPARE(rightParentSpy.counts[QEvent::WindowUnblocked], 0);

    // Left parent leaves modality. Right child is unblocked.
    leftParent->setPanelModality(QGraphicsItem::NonModal);
    QCOMPARE(leftChildSpy.counts[QEvent::WindowBlocked], 0);
    QCOMPARE(leftChildSpy.counts[QEvent::WindowUnblocked], 0);
    QCOMPARE(rightChildSpy.counts[QEvent::WindowBlocked], 1);
    QCOMPARE(rightChildSpy.counts[QEvent::WindowUnblocked], 1);
    QCOMPARE(leftParentSpy.counts[QEvent::WindowBlocked], 1);
    QCOMPARE(leftParentSpy.counts[QEvent::WindowUnblocked], 0);
    QCOMPARE(rightParentSpy.counts[QEvent::WindowBlocked], 1);
    QCOMPARE(rightParentSpy.counts[QEvent::WindowUnblocked], 0);

    // Right child "upgrades" its modality to scene modal. Left child is blocked.
    // Right parent is unaffected.
    rightChild->setPanelModality(QGraphicsItem::SceneModal);
    QCOMPARE(leftChildSpy.counts[QEvent::WindowBlocked], 1);
    QCOMPARE(leftChildSpy.counts[QEvent::WindowUnblocked], 0);
    QCOMPARE(rightChildSpy.counts[QEvent::WindowBlocked], 1);
    QCOMPARE(rightChildSpy.counts[QEvent::WindowUnblocked], 1);
    QCOMPARE(leftParentSpy.counts[QEvent::WindowBlocked], 1);
    QCOMPARE(leftParentSpy.counts[QEvent::WindowUnblocked], 0);
    QCOMPARE(rightParentSpy.counts[QEvent::WindowBlocked], 1);
    QCOMPARE(rightParentSpy.counts[QEvent::WindowUnblocked], 0);

    // "downgrade" right child back to panel modal, left child is unblocked
    rightChild->setPanelModality(QGraphicsItem::PanelModal);
    QCOMPARE(leftChildSpy.counts[QEvent::WindowBlocked], 1);
    QCOMPARE(leftChildSpy.counts[QEvent::WindowUnblocked], 1);
    QCOMPARE(rightChildSpy.counts[QEvent::WindowBlocked], 1);
    QCOMPARE(rightChildSpy.counts[QEvent::WindowUnblocked], 1);
    QCOMPARE(leftParentSpy.counts[QEvent::WindowBlocked], 1);
    QCOMPARE(leftParentSpy.counts[QEvent::WindowUnblocked], 0);
    QCOMPARE(rightParentSpy.counts[QEvent::WindowBlocked], 1);
    QCOMPARE(rightParentSpy.counts[QEvent::WindowUnblocked], 0);
}

void tst_QGraphicsItem::modality_hover()
{
    QGraphicsScene scene;
    QGraphicsRectItem *rect1 = scene.addRect(-50, -50, 100, 100);
    rect1->setFlag(QGraphicsItem::ItemIsPanel);
    rect1->setAcceptHoverEvents(true);
    rect1->setData(0, "rect1");

    QGraphicsRectItem *rect2 = scene.addRect(-50, -50, 100, 100);
    rect2->setParentItem(rect1);
    rect2->setFlag(QGraphicsItem::ItemIsPanel);
    rect2->setAcceptHoverEvents(true);
    rect2->setPos(50, 50);
    rect2->setPanelModality(QGraphicsItem::SceneModal);
    rect2->setData(0, "rect2");

    EventSpy2 rect1Spy(&scene, rect1);
    EventSpy2 rect2Spy(&scene, rect2);

    sendMouseMove(&scene, QPointF(-25, -25));

    QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverEnter], 0);
    QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverMove], 0);
    QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverLeave], 0);

    sendMouseMove(&scene, QPointF(75, 75));

    QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverEnter], 0);
    QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverMove], 0);
    QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverLeave], 0);
    QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneHoverEnter], 1);
    QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneHoverMove], 1);
    QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneHoverLeave], 0);

    sendMouseMove(&scene, QPointF(-25, -25));

    QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneHoverLeave], 1);
    QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverEnter], 0);
    QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverMove], 0);
    QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverLeave], 0);

    rect2->setPanelModality(QGraphicsItem::NonModal);

    QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverEnter], 1);
    QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverMove], 1);

    sendMouseMove(&scene, QPointF(75, 75));

    QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneHoverEnter], 2);
    QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneHoverMove], 2);

    rect2->setPanelModality(QGraphicsItem::SceneModal);

    QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverLeave], 1);
    QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneHoverEnter], 2);
    // changing modality causes a spurious GraphicsSceneHoveMove, even though the mouse didn't
    // actually move
    QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneHoverMove], 3);

    sendMouseMove(&scene, QPointF(-25, -25));

    QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneHoverLeave], 2);
    QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverEnter], 1);
    QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverMove], 1);
    QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverLeave], 1);

    rect2->setPanelModality(QGraphicsItem::PanelModal);

    QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverEnter], 1);
    QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverMove], 1);
    QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverLeave], 1);
    QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneHoverEnter], 2);
    QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneHoverMove], 3);
    QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneHoverLeave], 2);

    rect2->setPanelModality(QGraphicsItem::NonModal);

    QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverEnter], 2);
    QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverMove], 2);
    QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneHoverEnter], 2);
    QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneHoverMove], 3);
    QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneHoverLeave], 2);
}

void tst_QGraphicsItem::modality_mouseGrabber()
{
    QGraphicsScene scene;
    QGraphicsRectItem *rect1 = scene.addRect(-50, -50, 100, 100);
    rect1->setFlag(QGraphicsItem::ItemIsPanel);
    rect1->setFlag(QGraphicsItem::ItemIsMovable);
    rect1->setData(0, "rect1");

    QGraphicsRectItem *rect2 = scene.addRect(-50, -50, 100, 100);
    rect2->setParentItem(rect1);
    rect2->setFlag(QGraphicsItem::ItemIsPanel);
    rect2->setFlag(QGraphicsItem::ItemIsMovable);
    rect2->setPos(50, 50);
    rect2->setData(0, "rect2");

    EventSpy2 rect1Spy(&scene, rect1);
    EventSpy2 rect2Spy(&scene, rect2);

    {
        // pressing mouse on rect1 starts implicit grab
        sendMousePress(&scene, QPoint(-25, -25));
        QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 1);
        QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneMousePress], 1);
        QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 0);
        QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 0);
        QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 0);
        QCOMPARE(scene.mouseGrabberItem(), rect1);

        // grab lost when rect1 is modally shadowed
        rect2->setPanelModality(QGraphicsItem::SceneModal);
        QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 1);
        QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 1);
        QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 0);
        QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 0);
        QCOMPARE(scene.mouseGrabberItem(), nullptr);

        // releasing goes nowhere
        sendMouseRelease(&scene, QPoint(-25, -25));
        QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 1);
        QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneMouseRelease], 0);
        QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 1);
        QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 0);
        QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneMouseRelease], 0);
        QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 0);
        QCOMPARE(scene.mouseGrabberItem(), nullptr);

        // pressing mouse on rect1 starts implicit grab on rect2 (since it is modal)
        sendMouseClick(&scene, QPoint(-25, -25));
        QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 1);
        QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneMousePress], 1);
        QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneMouseRelease], 0);
        QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 1);
        QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 1);
        QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneMousePress], 1);
        QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneMouseRelease], 1);
        QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 1);

        rect2->setPanelModality(QGraphicsItem::NonModal);

        // pressing mouse on rect1 starts implicit grab
        sendMousePress(&scene, QPoint(-25, -25));
        QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 2);
        QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneMousePress], 2);
        QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 1);
        QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 1);
        QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 1);
        QCOMPARE(scene.mouseGrabberItem(), rect1);

        // grab lost to rect2 when rect1 is modally shadowed
        rect2->setPanelModality(QGraphicsItem::SceneModal);
        QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 2);
        QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 2);
        QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 1);
        QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 1);
        QCOMPARE(scene.mouseGrabberItem(), nullptr);

        // rect1 does *not* re-grab when rect2 is no longer modal
        rect2->setPanelModality(QGraphicsItem::NonModal);
        QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 2);
        QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 2);
        QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 1);
        QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 1);
        QCOMPARE(scene.mouseGrabberItem(), nullptr);

        // release goes nowhere
        sendMouseRelease(&scene, QPoint(-25, -25));
        QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 2);
        QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneMouseRelease], 0);
        QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 2);
        QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 1);
        QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 1);
        QCOMPARE(scene.mouseGrabberItem(), nullptr);
    }
    {
        // repeat the test using PanelModal
        rect2->setPanelModality(QGraphicsItem::NonModal);
        rect1Spy.counts.clear();
        rect2Spy.counts.clear();

        // pressing mouse on rect1 starts implicit grab
        sendMousePress(&scene, QPoint(-25, -25));
        QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 1);
        QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneMousePress], 1);
        QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 0);
        QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 0);
        QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 0);
        QCOMPARE(scene.mouseGrabberItem(), rect1);

        // grab lost when rect1 is modally shadowed
        rect2->setPanelModality(QGraphicsItem::PanelModal);
        QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 1);
        QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 1);
        QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 0);
        QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 0);
        QCOMPARE(scene.mouseGrabberItem(), nullptr);

        // releasing goes nowhere
        sendMouseRelease(&scene, QPoint(-25, -25));
        QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 1);
        QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneMouseRelease], 0);
        QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 1);
        QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 0);
        QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneMouseRelease], 0);
        QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 0);
        QCOMPARE(scene.mouseGrabberItem(), nullptr);

        // pressing mouse on rect1 starts implicit grab on rect2 (since it is modal)
        sendMouseClick(&scene, QPoint(-25, -25));
        QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 1);
        QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneMousePress], 1);
        QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneMouseRelease], 0);
        QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 1);
        QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 1);
        QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneMousePress], 1);
        QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneMouseRelease], 1);
        QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 1);

        rect2->setPanelModality(QGraphicsItem::NonModal);

        // pressing mouse on rect1 starts implicit grab
        sendMousePress(&scene, QPoint(-25, -25));
        QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 2);
        QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneMousePress], 2);
        QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 1);
        QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 1);
        QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 1);
        QCOMPARE(scene.mouseGrabberItem(), rect1);

        // grab lost to rect2 when rect1 is modally shadowed
        rect2->setPanelModality(QGraphicsItem::PanelModal);
        QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 2);
        QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 2);
        QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 1);
        QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 1);
        QCOMPARE(scene.mouseGrabberItem(), nullptr);

        // rect1 does *not* re-grab when rect2 is no longer modal
        rect2->setPanelModality(QGraphicsItem::NonModal);
        QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 2);
        QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 2);
        QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 1);
        QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 1);
        QCOMPARE(scene.mouseGrabberItem(), nullptr);

        // release goes nowhere
        sendMouseRelease(&scene, QPoint(-25, -25));
        QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 2);
        QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneMouseRelease], 0);
        QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 2);
        QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 1);
        QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 1);
        QCOMPARE(scene.mouseGrabberItem(), nullptr);
    }

    {
        // repeat the PanelModal tests, but this time the mouse events will be on a non-modal item,
        // meaning normal grabbing should work
        rect2->setPanelModality(QGraphicsItem::NonModal);
        rect1Spy.counts.clear();
        rect2Spy.counts.clear();

        QGraphicsRectItem *rect3 = scene.addRect(-50, -50, 100, 100);
        rect3->setFlag(QGraphicsItem::ItemIsPanel);
        rect3->setFlag(QGraphicsItem::ItemIsMovable);
        rect3->setPos(150, 50);
        rect3->setData(0, "rect3");

        EventSpy2 rect3Spy(&scene, rect3);

        // pressing mouse on rect3 starts implicit grab
        sendMousePress(&scene, QPoint(150, 50));
        QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 0);
        QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 0);
        QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 0);
        QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 0);
        QCOMPARE(rect3Spy.counts[QEvent::GrabMouse], 1);
        QCOMPARE(rect3Spy.counts[QEvent::GraphicsSceneMousePress], 1);
        QCOMPARE(rect3Spy.counts[QEvent::UngrabMouse], 0);
        QCOMPARE(scene.mouseGrabberItem(), rect3);

        // grab is *not* lost when rect1 is modally shadowed by rect2
        rect2->setPanelModality(QGraphicsItem::PanelModal);
        QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 0);
        QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 0);
        QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 0);
        QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 0);
        QCOMPARE(rect3Spy.counts[QEvent::GrabMouse], 1);
        QCOMPARE(rect3Spy.counts[QEvent::UngrabMouse], 0);
        QCOMPARE(scene.mouseGrabberItem(), rect3);

        // releasing goes to rect3
        sendMouseRelease(&scene, QPoint(150, 50));
        QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 0);
        QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 0);
        QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 0);
        QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 0);
        QCOMPARE(rect3Spy.counts[QEvent::GrabMouse], 1);
        QCOMPARE(rect3Spy.counts[QEvent::GraphicsSceneMouseRelease], 1);
        QCOMPARE(rect3Spy.counts[QEvent::UngrabMouse], 1);
        QCOMPARE(scene.mouseGrabberItem(), nullptr);

        rect2->setPanelModality(QGraphicsItem::NonModal);

        // pressing mouse on rect3 starts implicit grab
        sendMousePress(&scene, QPoint(150, 50));
        QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 0);
        QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 0);
        QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 0);
        QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 0);
        QCOMPARE(rect3Spy.counts[QEvent::GrabMouse], 2);
        QCOMPARE(rect3Spy.counts[QEvent::UngrabMouse], 1);
        QCOMPARE(scene.mouseGrabberItem(), rect3);

        // grab is not lost
        rect2->setPanelModality(QGraphicsItem::PanelModal);
        QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 0);
        QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 0);
        QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 0);
        QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 0);
        QCOMPARE(rect3Spy.counts[QEvent::GrabMouse], 2);
        QCOMPARE(rect3Spy.counts[QEvent::UngrabMouse], 1);
        QCOMPARE(scene.mouseGrabberItem(), rect3);

        // grab stays on rect3
        rect2->setPanelModality(QGraphicsItem::NonModal);
        QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 0);
        QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 0);
        QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 0);
        QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 0);
        QCOMPARE(rect3Spy.counts[QEvent::GrabMouse], 2);
        QCOMPARE(rect3Spy.counts[QEvent::UngrabMouse], 1);
        QCOMPARE(scene.mouseGrabberItem(), rect3);

        // release goes to rect3
        sendMouseRelease(&scene, QPoint(150, 50));
        QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 0);
        QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 0);
        QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 0);
        QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 0);
        QCOMPARE(rect3Spy.counts[QEvent::GrabMouse], 2);
        QCOMPARE(rect3Spy.counts[QEvent::UngrabMouse], 2);
        QCOMPARE(scene.mouseGrabberItem(), nullptr);
    }
}

void tst_QGraphicsItem::modality_clickFocus()
{
    QGraphicsScene scene;
    QGraphicsRectItem *rect1 = scene.addRect(-50, -50, 100, 100);
    rect1->setFlag(QGraphicsItem::ItemIsPanel);
    rect1->setFlag(QGraphicsItem::ItemIsFocusable);
    rect1->setData(0, "rect1");

    QGraphicsRectItem *rect2 = scene.addRect(-50, -50, 100, 100);
    rect2->setParentItem(rect1);
    rect2->setFlag(QGraphicsItem::ItemIsPanel);
    rect2->setFlag(QGraphicsItem::ItemIsFocusable);
    rect2->setPos(50, 50);
    rect2->setData(0, "rect2");

    QEvent windowActivateEvent(QEvent::WindowActivate);
    QCoreApplication::sendEvent(&scene, &windowActivateEvent);

    EventSpy2 rect1Spy(&scene, rect1);
    EventSpy2 rect2Spy(&scene, rect2);

    // activate rect1, it should get focus
    rect1->setActive(true);
    QCOMPARE(scene.focusItem(), rect1);

    // focus stays when rect2 becomes modal
    rect2->setPanelModality(QGraphicsItem::SceneModal);
    QCOMPARE(scene.focusItem(), rect1);
    QCOMPARE(rect1Spy.counts[QEvent::FocusIn], 1);
    QCOMPARE(rect1Spy.counts[QEvent::FocusOut], 0);
    QCOMPARE(rect2Spy.counts[QEvent::FocusIn], 0);
    QCOMPARE(rect2Spy.counts[QEvent::FocusOut], 0);

    // clicking on rect1 should not set it's focus item
    rect1->clearFocus();
    sendMouseClick(&scene, QPointF(-25, -25));
    QCOMPARE(rect1->focusItem(), nullptr);
    QCOMPARE(rect1Spy.counts[QEvent::FocusIn], 1);
    QCOMPARE(rect1Spy.counts[QEvent::FocusOut], 1);
    QCOMPARE(rect2Spy.counts[QEvent::FocusIn], 0);
    QCOMPARE(rect2Spy.counts[QEvent::FocusOut], 0);

    // clicking on rect2 gives it focus
    rect2->setActive(true);
    sendMouseClick(&scene, QPointF(75, 75));
    QCOMPARE(scene.focusItem(), rect2);
    QCOMPARE(rect1Spy.counts[QEvent::FocusIn], 1);
    QCOMPARE(rect1Spy.counts[QEvent::FocusOut], 1);
    QCOMPARE(rect2Spy.counts[QEvent::FocusIn], 1);
    QCOMPARE(rect2Spy.counts[QEvent::FocusOut], 0);

    // clicking on rect1 does *not* give it focus
    rect1->setActive(true);
    rect1->clearFocus();
    sendMouseClick(&scene, QPointF(-25, -25));
    QCOMPARE(scene.focusItem(), nullptr);
    QCOMPARE(rect1Spy.counts[QEvent::FocusIn], 2);
    QCOMPARE(rect1Spy.counts[QEvent::FocusOut], 2);
    QCOMPARE(rect2Spy.counts[QEvent::FocusIn], 1);
    QCOMPARE(rect2Spy.counts[QEvent::FocusOut], 1);

    // focus doesn't change when leaving modality either
    rect2->setPanelModality(QGraphicsItem::NonModal);
    QCOMPARE(scene.focusItem(), nullptr);
    QCOMPARE(rect1Spy.counts[QEvent::FocusIn], 2);
    QCOMPARE(rect1Spy.counts[QEvent::FocusOut], 2);
    QCOMPARE(rect2Spy.counts[QEvent::FocusIn], 1);
    QCOMPARE(rect2Spy.counts[QEvent::FocusOut], 1);

    // click on rect1, it should get focus now
    sendMouseClick(&scene, QPointF(-25, -25));
    QCOMPARE(scene.focusItem(), rect1);
    QCOMPARE(rect1Spy.counts[QEvent::FocusIn], 3);
    QCOMPARE(rect1Spy.counts[QEvent::FocusOut], 2);
    QCOMPARE(rect2Spy.counts[QEvent::FocusIn], 1);
    QCOMPARE(rect2Spy.counts[QEvent::FocusOut], 1);
}

void tst_QGraphicsItem::modality_keyEvents()
{
    QGraphicsScene scene;
    QGraphicsRectItem *rect1 = scene.addRect(-50, -50, 100, 100);
    rect1->setFlag(QGraphicsItem::ItemIsPanel);
    rect1->setFlag(QGraphicsItem::ItemIsFocusable);
    rect1->setData(0, "rect1");

    QGraphicsRectItem *rect1child = scene.addRect(-10, -10, 20, 20);
    rect1child->setParentItem(rect1);
    rect1child->setFlag(QGraphicsItem::ItemIsFocusable);
    rect1child->setData(0, "rect1child1");

    QGraphicsRectItem *rect2 = scene.addRect(-50, -50, 100, 100);
    rect2->setParentItem(rect1);
    rect2->setFlag(QGraphicsItem::ItemIsPanel);
    rect2->setFlag(QGraphicsItem::ItemIsFocusable);
    rect2->setPos(50, 50);
    rect2->setData(0, "rect2");

    QGraphicsRectItem *rect2child = scene.addRect(-10, -10, 20, 20);
    rect2child->setParentItem(rect2);
    rect2child->setFlag(QGraphicsItem::ItemIsFocusable);
    rect2child->setData(0, "rect2child1");

    QEvent windowActivateEvent(QEvent::WindowActivate);
    QCoreApplication::sendEvent(&scene, &windowActivateEvent);

    EventSpy2 rect1Spy(&scene, rect1);
    EventSpy2 rect1childSpy(&scene, rect1child);
    EventSpy2 rect2Spy(&scene, rect2);
    EventSpy2 rect2childSpy(&scene, rect2child);

    // activate rect1 and give it rect1child focus
    rect1->setActive(true);
    rect1child->setFocus();
    QCOMPARE(scene.focusItem(), rect1child);

    // focus stays on rect1child when rect2 becomes modal
    rect2->setPanelModality(QGraphicsItem::SceneModal);
    QCOMPARE(scene.focusItem(), rect1child);

    // but key events to rect1child should be neither delivered nor propagated
    sendKeyClick(&scene, Qt::Key_A);
    sendKeyClick(&scene, Qt::Key_S);
    sendKeyClick(&scene, Qt::Key_D);
    sendKeyClick(&scene, Qt::Key_F);
    QCOMPARE(rect1childSpy.counts[QEvent::KeyPress], 0);
    QCOMPARE(rect1childSpy.counts[QEvent::KeyRelease], 0);
    QCOMPARE(rect1Spy.counts[QEvent::KeyPress], 0);
    QCOMPARE(rect1Spy.counts[QEvent::KeyRelease], 0);

    // change to panel modality, rect1child1 keeps focus
    rect2->setPanelModality(QGraphicsItem::PanelModal);
    QCOMPARE(scene.focusItem(), rect1child);

    // still no key events
    sendKeyClick(&scene, Qt::Key_J);
    sendKeyClick(&scene, Qt::Key_K);
    sendKeyClick(&scene, Qt::Key_L);
    sendKeyClick(&scene, Qt::Key_Semicolon);
    QCOMPARE(rect1childSpy.counts[QEvent::KeyPress], 0);
    QCOMPARE(rect1childSpy.counts[QEvent::KeyRelease], 0);
    QCOMPARE(rect1Spy.counts[QEvent::KeyPress], 0);
    QCOMPARE(rect1Spy.counts[QEvent::KeyRelease], 0);
}

void tst_QGraphicsItem::itemIsInFront()
{
    QGraphicsScene scene;
    QGraphicsRectItem *rect1 = new QGraphicsRectItem;
    rect1->setData(0, "rect1");
    scene.addItem(rect1);

    QGraphicsRectItem *rect1child1 = new QGraphicsRectItem(rect1);
    rect1child1->setZValue(1);
    rect1child1->setData(0, "rect1child1");

    QGraphicsRectItem *rect1child2 = new QGraphicsRectItem(rect1);
    rect1child2->setParentItem(rect1);
    rect1child2->setData(0, "rect1child2");

    QGraphicsRectItem *rect1child1_1 = new QGraphicsRectItem(rect1child1);
    rect1child1_1->setData(0, "rect1child1_1");

    QGraphicsRectItem *rect1child1_2 = new QGraphicsRectItem(rect1child1);
    rect1child1_2->setFlag(QGraphicsItem::ItemStacksBehindParent);
    rect1child1_2->setData(0, "rect1child1_2");

    QGraphicsRectItem *rect2 = new QGraphicsRectItem;
    rect2->setData(0, "rect2");
    scene.addItem(rect2);

    QGraphicsRectItem *rect2child1 = new QGraphicsRectItem(rect2);
    rect2child1->setData(0, "rect2child1");

    QCOMPARE(qt_closestItemFirst(rect1, rect1), false);
    QCOMPARE(qt_closestItemFirst(rect1, rect2), false);
    QCOMPARE(qt_closestItemFirst(rect1child1, rect2child1), false);
    QCOMPARE(qt_closestItemFirst(rect1child1, rect1child2), true);
    QCOMPARE(qt_closestItemFirst(rect1child1_1, rect1child2), true);
    QCOMPARE(qt_closestItemFirst(rect1child1_1, rect1child1), true);
    QCOMPARE(qt_closestItemFirst(rect1child1_2, rect1child2), true);
    QCOMPARE(qt_closestItemFirst(rect1child1_2, rect1child1), false);
    QCOMPARE(qt_closestItemFirst(rect1child1_2, rect1), true);
    QCOMPARE(qt_closestItemFirst(rect1child1_2, rect2), false);
    QCOMPARE(qt_closestItemFirst(rect1child1_2, rect2child1), false);
}

using ScenePosChangeTester = ItemChangeTester;

void tst_QGraphicsItem::scenePosChange()
{
    ScenePosChangeTester* root = new ScenePosChangeTester;
    ScenePosChangeTester* child1 = new ScenePosChangeTester(root);
    const QScopedPointer<ScenePosChangeTester> grandChild1(new ScenePosChangeTester(child1));
    ScenePosChangeTester* child2 = new ScenePosChangeTester(root);
    ScenePosChangeTester* grandChild2 = new ScenePosChangeTester(child2);

    child1->setFlag(QGraphicsItem::ItemSendsScenePositionChanges, true);
    grandChild2->setFlag(QGraphicsItem::ItemSendsScenePositionChanges, true);

    QVERIFY(child1->flags() & QGraphicsItem::ItemSendsScenePositionChanges);
    QVERIFY(grandChild2->flags() & QGraphicsItem::ItemSendsScenePositionChanges);

    QGraphicsScene scene;
    scene.addItem(root);

    // ignore uninteresting changes
    child1->clear();
    child2->clear();
    grandChild1->clear();
    grandChild2->clear();

    // move whole tree
    root->moveBy(1.0, 1.0);
    QCOMPARE(child1->changes.count(QGraphicsItem::ItemScenePositionHasChanged), 1);
    QCOMPARE(grandChild1->changes.count(QGraphicsItem::ItemScenePositionHasChanged), 0);
    QCOMPARE(child2->changes.count(QGraphicsItem::ItemScenePositionHasChanged), 0);
    QCOMPARE(grandChild2->changes.count(QGraphicsItem::ItemScenePositionHasChanged), 1);

    // move subtree
    child2->moveBy(1.0, 1.0);
    QCOMPARE(child1->changes.count(QGraphicsItem::ItemScenePositionHasChanged), 1);
    QCOMPARE(grandChild1->changes.count(QGraphicsItem::ItemScenePositionHasChanged), 0);
    QCOMPARE(child2->changes.count(QGraphicsItem::ItemScenePositionHasChanged), 0);
    QCOMPARE(grandChild2->changes.count(QGraphicsItem::ItemScenePositionHasChanged), 2);

    // reparent
    grandChild2->setParentItem(child1);
    child1->moveBy(1.0, 1.0);
    QCOMPARE(child1->changes.count(QGraphicsItem::ItemScenePositionHasChanged), 2);
    QCOMPARE(grandChild1->changes.count(QGraphicsItem::ItemScenePositionHasChanged), 0);
    QCOMPARE(child2->changes.count(QGraphicsItem::ItemScenePositionHasChanged), 0);
    QCOMPARE(grandChild2->changes.count(QGraphicsItem::ItemScenePositionHasChanged), 3);

    // change flags
    grandChild1->setFlag(QGraphicsItem::ItemSendsScenePositionChanges, true);
    grandChild2->setFlag(QGraphicsItem::ItemSendsScenePositionChanges, false);
    QCoreApplication::processEvents(); // QGraphicsScenePrivate::_q_updateScenePosDescendants()
    child1->moveBy(1.0, 1.0);
    QCOMPARE(child1->changes.count(QGraphicsItem::ItemScenePositionHasChanged), 3);
    QCOMPARE(grandChild1->changes.count(QGraphicsItem::ItemScenePositionHasChanged), 1);
    QCOMPARE(child2->changes.count(QGraphicsItem::ItemScenePositionHasChanged), 0);
    QCOMPARE(grandChild2->changes.count(QGraphicsItem::ItemScenePositionHasChanged), 3);

    // remove
    scene.removeItem(grandChild1.data());
    delete grandChild2; grandChild2 = nullptr;
    QCoreApplication::processEvents(); // QGraphicsScenePrivate::_q_updateScenePosDescendants()
    root->moveBy(1.0, 1.0);
    QCOMPARE(child1->changes.count(QGraphicsItem::ItemScenePositionHasChanged), 4);
    QCOMPARE(grandChild1->changes.count(QGraphicsItem::ItemScenePositionHasChanged), 1);
    QCOMPARE(child2->changes.count(QGraphicsItem::ItemScenePositionHasChanged), 0);

    root->setX(1);
    QCOMPARE(child1->changes.count(QGraphicsItem::ItemScenePositionHasChanged), 5);
    QCOMPARE(grandChild1->changes.count(QGraphicsItem::ItemScenePositionHasChanged), 1);
    QCOMPARE(child2->changes.count(QGraphicsItem::ItemScenePositionHasChanged), 0);

    root->setY(1);
    QCOMPARE(child1->changes.count(QGraphicsItem::ItemScenePositionHasChanged), 6);
    QCOMPARE(grandChild1->changes.count(QGraphicsItem::ItemScenePositionHasChanged), 1);
    QCOMPARE(child2->changes.count(QGraphicsItem::ItemScenePositionHasChanged), 0);
}

#if QT_CONFIG(shortcut)

void tst_QGraphicsItem::textItem_shortcuts()
{
    if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation))
        QSKIP("Window activation is not supported");

    QWidget w;
    w.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    auto l = new QVBoxLayout(&w);
    QGraphicsScene scene;
    QGraphicsView view(&scene);
    l->addWidget(&view);
    QPushButton b("Push Me");
    l->addWidget(&b);

    QGraphicsTextItem *item = scene.addText("Troll Text");
    item->setFlag(QGraphicsItem::ItemIsFocusable);
    item->setTextInteractionFlags(Qt::TextEditorInteraction);
    w.show();
    if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) {
        view.window()->activateWindow();
        QVERIFY(QTest::qWaitForWindowActive(&view));
    }
    QVERIFY(QTest::qWaitForWindowExposed(&w));

    item->setFocus();
    QTRY_VERIFY(item->hasFocus());
    QVERIFY(item->textCursor().selectedText().isEmpty());

    // Shortcut should work (select all)
    QTest::keyClick(&view, Qt::Key_A, Qt::ControlModifier);
    QTRY_COMPARE(item->textCursor().selectedText(), item->toPlainText());
    QTextCursor tc = item->textCursor();
    tc.clearSelection();
    item->setTextCursor(tc);
    QVERIFY(item->textCursor().selectedText().isEmpty());

    // Shortcut should also work if the text item has the focus and another widget
    // has the same shortcut.
    b.setShortcut(QKeySequence("CTRL+A"));
    QTest::keyClick(&view, Qt::Key_A, Qt::ControlModifier);
    QTRY_COMPARE(item->textCursor().selectedText(), item->toPlainText());
}

#endif // QT_CONFIG(shortcut)

void tst_QGraphicsItem::scroll()
{
    // Create two overlapping rectangles in the scene:
    // +-------+
    // |       | <- item1
    // |   +-------+
    // |   |       |
    // +---|       | <- item2
    //     |       |
    //     +-------+

    EventTester *item1 = new EventTester;
    item1->br = QRectF(0, 0, 200, 200);
    item1->brush = Qt::red;
    item1->setFlag(QGraphicsItem::ItemUsesExtendedStyleOption);

    EventTester *item2 = new EventTester;
    item2->br = QRectF(0, 0, 200, 200);
    item2->brush = Qt::blue;
    item2->setFlag(QGraphicsItem::ItemUsesExtendedStyleOption);
    item2->setPos(100, 100);

    QGraphicsScene scene(0, 0, 300, 300);
    scene.addItem(item1);
    scene.addItem(item2);

    MyGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.setFrameStyle(0);
    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));
    if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) {
        view.window()->activateWindow();
        QVERIFY(QTest::qWaitForWindowActive(&view));
    }
    QTRY_VERIFY(view.repaints > 0);

    view.reset();
    item1->reset();
    item2->reset();

    const QRectF item1BoundingRect = item1->boundingRect();
    const QRectF item2BoundingRect = item2->boundingRect();

    // Scroll item1:
    // Item1 should get full exposure
    // Item2 should get exposure for the part that overlaps item1.
    item1->scroll(0, -10);
    QTRY_VERIFY(view.repaints > 0);
    QCOMPARE(item1->lastExposedRect, item1BoundingRect);

    QRectF expectedItem2Expose = item2BoundingRect;
    // NB! Adjusted by 2 pixels for antialiasing
    expectedItem2Expose &= item1->mapRectToItem(item2, item1BoundingRect.adjusted(-2, -2, 2, 2));
    QCOMPARE(item2->lastExposedRect, expectedItem2Expose);

    // Enable ItemCoordinateCache on item1.
    view.reset();
    item1->setCacheMode(QGraphicsItem::ItemCoordinateCache);
    QTRY_VERIFY(view.repaints > 0);
    view.reset();
    item1->reset();
    item2->reset();

    // Scroll item1:
    // Item1 should only get expose for the newly exposed area (accelerated scroll).
    // Item2 should get exposure for the part that overlaps item1.
    item1->scroll(0, -10, QRectF(50, 50, 100, 100));
    QTRY_VERIFY(view.repaints > 0);
    QCOMPARE(item1->lastExposedRect, QRectF(50, 140, 100, 10));

    expectedItem2Expose = item2BoundingRect;
    // NB! Adjusted by 2 pixels for antialiasing
    expectedItem2Expose &= item1->mapRectToItem(item2, QRectF(50, 50, 100, 100).adjusted(-2, -2, 2, 2));
    QCOMPARE(item2->lastExposedRect, expectedItem2Expose);
}

Q_DECLARE_METATYPE(QGraphicsItem::GraphicsItemFlag);

void tst_QGraphicsItem::focusHandling_data()
{
    QTest::addColumn<QGraphicsItem::GraphicsItemFlag>("focusFlag");
    QTest::addColumn<bool>("useStickyFocus");
    QTest::addColumn<int>("expectedFocusItem"); // 0: none, 1: focusableUnder, 2: itemWithFocus

    QTest::newRow("Focus goes through.")
        << static_cast<QGraphicsItem::GraphicsItemFlag>(0x0) << false << 1;

    QTest::newRow("Focus goes through, even with sticky scene.")
        << static_cast<QGraphicsItem::GraphicsItemFlag>(0x0) << true  << 1;

    QTest::newRow("With ItemStopsClickFocusPropagation, we cannot focus the item beneath the flagged one (but can still focus-out).")
        << QGraphicsItem::ItemStopsClickFocusPropagation << false << 0;

    QTest::newRow("With ItemStopsClickFocusPropagation, we cannot focus the item beneath the flagged one (and cannot focus-out if scene is sticky).")
        << QGraphicsItem::ItemStopsClickFocusPropagation << true << 2;

    QTest::newRow("With ItemStopsFocusHandling, focus cannot be changed by presses.")
        << QGraphicsItem::ItemStopsFocusHandling << false << 2;

    QTest::newRow("With ItemStopsFocusHandling, focus cannot be changed by presses (even if scene is sticky).")
        << QGraphicsItem::ItemStopsFocusHandling << true << 2;
}

void tst_QGraphicsItem::focusHandling()
{
    QFETCH(QGraphicsItem::GraphicsItemFlag, focusFlag);
    QFETCH(bool, useStickyFocus);
    QFETCH(int, expectedFocusItem);

    class MyItem : public QGraphicsRectItem
    {
    public:
        MyItem() : QGraphicsRectItem(0, 0, 100, 100) {}
        void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override
        {
            painter->fillRect(boundingRect(), hasFocus() ? QBrush(Qt::red) : brush());
        }
    };

    QGraphicsRectItem *noFocusOnTop = new MyItem;
    noFocusOnTop->setFlag(QGraphicsItem::ItemIsFocusable, false);
    noFocusOnTop->setBrush(Qt::yellow);

    QGraphicsRectItem *focusableUnder = new MyItem;
    focusableUnder->setBrush(Qt::blue);
    focusableUnder->setFlag(QGraphicsItem::ItemIsFocusable);
    focusableUnder->setPos(50, 50);

    QGraphicsRectItem *itemWithFocus = new MyItem;
    itemWithFocus->setBrush(Qt::black);
    itemWithFocus->setFlag(QGraphicsItem::ItemIsFocusable);
    itemWithFocus->setPos(250, 10);

    QGraphicsScene scene(-50, -50, 400, 400);
    scene.addItem(noFocusOnTop);
    scene.addItem(focusableUnder);
    scene.addItem(itemWithFocus);
    scene.setStickyFocus(useStickyFocus);

    noFocusOnTop->setFlag(focusFlag);
    focusableUnder->stackBefore(noFocusOnTop);
    itemWithFocus->setFocus();

    QGraphicsView view(&scene);
    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));

    QApplicationPrivate::setActiveWindow(&view);
    QTRY_COMPARE(QApplication::activeWindow(), static_cast<QWidget *>(&view));
    QVERIFY(itemWithFocus->hasFocus());

    const QPointF mousePressPoint = noFocusOnTop->mapToScene(noFocusOnTop->boundingRect().center());
    const QList<QGraphicsItem *> itemsAtMousePressPosition = scene.items(mousePressPoint);
    QVERIFY(itemsAtMousePressPosition.contains(noFocusOnTop));

    sendMousePress(&scene, mousePressPoint);

    switch (expectedFocusItem) {
    case 0:
        QCOMPARE(scene.focusItem(), nullptr);
        break;
    case 1:
        QCOMPARE(scene.focusItem(), focusableUnder);
        break;
    case 2:
        QCOMPARE(scene.focusItem(), itemWithFocus);
        break;
    }

    // Sanity check - manually setting the focus must work regardless of our
    // focus handling flags:
    focusableUnder->setFocus();
    QCOMPARE(scene.focusItem(), focusableUnder);
}

class TouchEventTestee : public QGraphicsRectItem
{
public:
    using TouchPoints = QList<QEventPoint>;

    TouchEventTestee(const QSizeF &size = QSizeF(100, 100)) :
        QGraphicsRectItem(QRectF(QPointF(), size))
    {
        setAcceptTouchEvents(true);
        setFlag(QGraphicsItem::ItemIsFocusable, false);
    }

    TouchPoints touchBeginPoints() const { return m_touchBeginPoints; }
    int touchBeginEventCount() const { return m_touchBeginPoints.size(); }

    TouchPoints touchUpdatePoints() const { return m_touchUpdatePoints; }
    int touchUpdateEventCount() const { return m_touchUpdatePoints.size(); }

protected:
    bool sceneEvent(QEvent *ev) override
    {
        switch (ev->type()) {
        case QEvent::TouchBegin:
            m_touchBeginPoints.append(static_cast<const QTouchEvent *>(ev)->points().constFirst());
            ev->accept();
            return true;
        case QEvent::TouchUpdate:
            m_touchUpdatePoints.append(static_cast<const QTouchEvent *>(ev)->points().constFirst());
            ev->accept();
            return true;
        default:
            break;
        }

        return QGraphicsRectItem::sceneEvent(ev);
    }

private:
    TouchPoints m_touchBeginPoints;
    TouchPoints m_touchUpdatePoints;
};

static QList<QEventPoint>
    createTouchPoints(const QGraphicsView &view,
                      const QPointF &scenePos,
                      const QSizeF &ellipseDiameters,
                      QEventPoint::State state = QEventPoint::State::Pressed)
{
    const QPointF screenPos = view.viewport()->mapToGlobal(view.mapFromScene(scenePos));
    QEventPoint tp(0, state, scenePos, screenPos);
    QMutableEventPoint::setState(tp, state);
    QMutableEventPoint::setScenePosition(tp, scenePos);
    QMutableEventPoint::setGlobalPosition(tp, screenPos);
    QMutableEventPoint::setGlobalPressPosition(tp, screenPos);
    QMutableEventPoint::setGlobalLastPosition(tp, screenPos);
    QMutableEventPoint::setEllipseDiameters(tp, ellipseDiameters);
    return QList<QEventPoint>() << tp;
}

static bool comparePointF(const QPointF &p1, const QPointF &p2)
{
    return qFuzzyCompare(p1.x(), p2.x()) && qFuzzyCompare(p1.y(), p2.y());
}

static bool compareSizeF(const QSizeF &s1, const QSizeF &s2)
{
    return qFuzzyCompare(s1.width(), s2.width()) && qFuzzyCompare(s1.height(), s2.height());
}

static QByteArray msgPointFComparisonFailed(const QPointF &p1, const QPointF &p2)
{
    return QByteArray::number(p1.x()) + ", " + QByteArray::number(p1.y())
        + " != " + QByteArray::number(p2.x()) + ", " + QByteArray::number(p2.y());
}

static QByteArray msgSizeFComparisonFailed(const QSizeF &s1, const QSizeF &s2)
{
    return QByteArray::number(s1.width()) + 'x' + QByteArray::number(s1.height())
        + " != " + QByteArray::number(s2.width()) + 'x' + QByteArray::number(s2.height());
}

#define COMPARE_POINTF(ACTUAL, EXPECTED) \
    QVERIFY2(comparePointF(ACTUAL, EXPECTED), msgPointFComparisonFailed(ACTUAL, EXPECTED).constData())

#define COMPARE_SIZEF(ACTUAL, EXPECTED) \
    QVERIFY2(compareSizeF(ACTUAL, EXPECTED), msgSizeFComparisonFailed(ACTUAL, EXPECTED).constData())

void tst_QGraphicsItem::touchEventPropagation_data()
{
    QTest::addColumn<QGraphicsItem::GraphicsItemFlag>("flag");
    QTest::addColumn<int>("expectedCount");

    QTest::newRow("ItemIsPanel")
        << QGraphicsItem::ItemIsPanel << 0;
    QTest::newRow("ItemStopsClickFocusPropagation")
        << QGraphicsItem::ItemStopsClickFocusPropagation << 1;
    QTest::newRow("ItemStopsFocusHandling")
        << QGraphicsItem::ItemStopsFocusHandling << 1;
}

void tst_QGraphicsItem::touchEventPropagation()
{
    QFETCH(QGraphicsItem::GraphicsItemFlag, flag);
    QFETCH(int, expectedCount);

    TouchEventTestee *touchEventReceiver = new TouchEventTestee;
    QGraphicsItem *topMost = new QGraphicsRectItem(touchEventReceiver->boundingRect());

    QGraphicsScene scene;
    scene.addItem(topMost);
    scene.addItem(touchEventReceiver);

    topMost->setAcceptTouchEvents(true);
    topMost->setZValue(FLT_MAX);
    topMost->setFlag(QGraphicsItem::ItemIsFocusable, false);
    topMost->setFlag(flag, true);

    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()) + QLatin1String("::")
                        + QLatin1String(QTest::currentDataTag()));
    view.setSceneRect(touchEventReceiver->boundingRect());
    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));
    QInputDevicePrivate::get(m_touchDevice)->setAvailableVirtualGeometry(view.screen()->geometry());

    QCOMPARE(touchEventReceiver->touchBeginEventCount(), 0);

    const QPointF scenePos = view.sceneRect().center();
    sendMousePress(&scene, scenePos);
    QMutableTouchEvent touchBegin(QEvent::TouchBegin, m_touchDevice, Qt::NoModifier,
                                  createTouchPoints(view, scenePos, QSizeF(10, 10)));
    touchBegin.setTarget(view.viewport());

    qApp->sendEvent(&scene, &touchBegin);
    QCOMPARE(touchEventReceiver->touchBeginEventCount(), expectedCount);
}

void tst_QGraphicsItem::touchEventTransformation_data()
{
    QTest::addColumn<QGraphicsItem::GraphicsItemFlag>("flag");
    QTest::addColumn<QTransform>("viewTransform");
    QTest::addColumn<QPointF>("touchScenePos");
    QTest::addColumn<QSizeF>("ellipseDiameters");
    QTest::addColumn<QPointF>("expectedItemPos");

    QTest::newRow("notransform")
        << QGraphicsItem::ItemIsSelectable << QTransform()
        << QPointF(150, 150) << QSizeF(7, 8) << QPointF(50, 50);
    QTest::newRow("scaled")
        << QGraphicsItem::ItemIsSelectable << QTransform::fromScale(0.5, 0.5)
        << QPointF(150, 150) << QSizeF(7, 8) << QPointF(50, 50);
    // QTBUG-66192: When the item ignores the downscaling transformation,
    // it will receive the touch point at 25,25 instead of 50,50.
    QTest::newRow("scaled/ItemIgnoresTransformations")
        << QGraphicsItem::ItemIgnoresTransformations << QTransform::fromScale(0.5, 0.5)
        << QPointF(150, 150) << QSizeF(7, 8) << QPointF(25, 25);
}

void tst_QGraphicsItem::touchEventTransformation()
{
    QFETCH(QGraphicsItem::GraphicsItemFlag, flag);
    QFETCH(QTransform, viewTransform);
    QFETCH(QPointF, touchScenePos);
    QFETCH(QSizeF, ellipseDiameters);
    QFETCH(QPointF, expectedItemPos);

    TouchEventTestee *touchEventReceiver = new TouchEventTestee;

    QGraphicsScene scene;
    scene.addItem(touchEventReceiver);
    const QPointF itemPos(100, 100);

    touchEventReceiver->setPos(itemPos);

    touchEventReceiver->setFlag(flag, true);

    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()) + QLatin1String("::")
                        + QLatin1String(QTest::currentDataTag()));
    view.setSceneRect(QRectF(QPointF(0, 0), QSizeF(300, 300)));
    view.setTransform(viewTransform);
    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));
    QInputDevicePrivate::get(m_touchDevice)->setAvailableVirtualGeometry(view.screen()->geometry());

    QCOMPARE(touchEventReceiver->touchBeginEventCount(), 0);

    QMutableTouchEvent touchBegin(QEvent::TouchBegin, m_touchDevice, Qt::NoModifier,
                                  createTouchPoints(view, touchScenePos, ellipseDiameters));
    touchBegin.setTarget(view.viewport());
    QCoreApplication::sendEvent(&scene, &touchBegin);
    QCOMPARE(touchEventReceiver->touchBeginEventCount(), 1);

    const QEventPoint touchBeginPoint = touchEventReceiver->touchBeginPoints().constFirst();

    COMPARE_POINTF(touchBeginPoint.scenePosition(), touchScenePos);
    COMPARE_POINTF(touchBeginPoint.scenePressPosition(), touchScenePos);
    COMPARE_POINTF(touchBeginPoint.sceneLastPosition(), touchScenePos);
    COMPARE_POINTF(touchBeginPoint.position(), expectedItemPos);
    COMPARE_SIZEF(touchBeginPoint.ellipseDiameters(), ellipseDiameters); // Must remain untransformed

    QMutableTouchEvent touchUpdate(QEvent::TouchUpdate, m_touchDevice, Qt::NoModifier,
                                   createTouchPoints(view, touchScenePos, ellipseDiameters, QEventPoint::State::Updated));
    touchUpdate.setTarget(view.viewport());

    QCoreApplication::sendEvent(&scene, &touchUpdate);
    QCOMPARE(touchEventReceiver->touchUpdateEventCount(), 1);

    const QEventPoint touchUpdatePoint = touchEventReceiver->touchUpdatePoints().constFirst();

    COMPARE_POINTF(touchUpdatePoint.scenePosition(), touchScenePos);
    COMPARE_POINTF(touchBeginPoint.scenePressPosition(), touchScenePos);
    COMPARE_POINTF(touchUpdatePoint.position(), expectedItemPos);
    COMPARE_SIZEF(touchUpdatePoint.ellipseDiameters(), ellipseDiameters); // Must remain untransformed

}

void tst_QGraphicsItem::deviceCoordinateCache_simpleRotations()
{
    // Make sure we don't invalidate the cache when applying simple
    // (90, 180, 270, 360) rotation transforms to the item.
    QGraphicsRectItem *item = new QGraphicsRectItem(0, 0, 300, 200);
    item->setBrush(Qt::red);
    item->setCacheMode(QGraphicsItem::DeviceCoordinateCache);

    QGraphicsScene scene;
    scene.setSceneRect(0, 0, 300, 200);
    scene.addItem(item);

    MyGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));
    QTRY_VERIFY(view.repaints > 0);

    QGraphicsItemCache *itemCache = QGraphicsItemPrivate::get(item)->extraItemCache();
    QVERIFY(itemCache);
    QPixmapCache::Key currentKey = itemCache->deviceData.value(view.viewport()).key;

    // Trigger an update and verify that the cache is unchanged.
    QPixmapCache::Key oldKey = currentKey;
    view.reset();
    view.viewport()->update();
    QTRY_VERIFY(view.repaints > 0);
    currentKey = itemCache->deviceData.value(view.viewport()).key;
    QCOMPARE(currentKey, oldKey);

    // Check 90, 180, 270 and 360 degree rotations.
    for (int angle = 90; angle <= 360; angle += 90) {
        // Rotate item and verify that the cache was invalidated.
        oldKey = currentKey;
        view.reset();
        QTransform transform;
        transform.translate(150, 100);
        transform.rotate(angle);
        transform.translate(-150, -100);
        item->setTransform(transform);
        QTRY_VERIFY(view.repaints > 0);
        currentKey = itemCache->deviceData.value(view.viewport()).key;
        QVERIFY(currentKey != oldKey);

        // IMPORTANT PART:
        // Trigger an update and verify that the cache is unchanged.
        oldKey = currentKey;
        view.reset();
        view.viewport()->update();
        QTRY_VERIFY(view.repaints > 0);
        currentKey = itemCache->deviceData.value(view.viewport()).key;
        QCOMPARE(currentKey, oldKey);
    }

    // 45 degree rotation.
    oldKey = currentKey;
    view.reset();
    QTransform transform;
    transform.translate(150, 100);
    transform.rotate(45);
    transform.translate(-150, -100);
    item->setTransform(transform);
    QTRY_VERIFY(view.repaints > 0);
    currentKey = itemCache->deviceData.value(view.viewport()).key;
    QVERIFY(currentKey != oldKey);

    // Trigger an update and verify that the cache was invalidated.
    // We should always invalidate the cache for non-trivial transforms.
    oldKey = currentKey;
    view.reset();
    view.viewport()->update();
    QTRY_VERIFY(view.repaints > 0);
    currentKey = itemCache->deviceData.value(view.viewport()).key;
    QVERIFY(currentKey != oldKey);
}

void tst_QGraphicsItem::QTBUG_5418_textItemSetDefaultColor()
{
    struct Item : public QGraphicsTextItem
    {
        int painted;
        void paint(QPainter *painter, const QStyleOptionGraphicsItem *opt, QWidget *wid) override
        {
            painted++;
            QGraphicsTextItem::paint(painter, opt, wid);
        }
    };

    Item *i = new Item;
    i->painted = 0;
    i->setPlainText("I AM A TROLL");

    QGraphicsScene scene;
    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));
    scene.addItem(i);
    QApplication::processEvents();
    QTRY_VERIFY(i->painted);
    QApplication::processEvents();

    i->painted = 0;
    QColor col(Qt::red);
    i->setDefaultTextColor(col);
    QApplication::processEvents();
    QTRY_COMPARE(i->painted, 1); //check that changing the color force an update

    i->painted = false;
    QImage image(400, 200, QImage::Format_RGB32);
    image.fill(0);
    QPainter painter(&image);
    scene.render(&painter);
    painter.end();
    QCOMPARE(i->painted, 1);

    int numRedPixel = 0;
    QRgb rgb = col.rgb();
    for (int y = 0; y < image.height(); ++y) {
        for (int x = 0; x < image.width(); ++x) {
            // Because of antialiasing we allow a certain range of errors here.
            QRgb pixel = image.pixel(x, y);
            if (qAbs(int(pixel & 0xff) - int(rgb & 0xff)) +
                qAbs(int((pixel & 0xff00) >> 8) - int((rgb & 0xff00) >> 8)) +
                qAbs(int((pixel & 0xff0000) >> 16) - int((rgb & 0xff0000) >> 16)) <= 50) {
                if (++numRedPixel >= 10) {
                    return;
                }
            }
        }
    }
    QCOMPARE(numRedPixel, -1); //color not found, FAIL!

    i->painted = 0;
    i->setDefaultTextColor(col);
    QApplication::processEvents();
    QCOMPARE(i->painted, 0); //same color as before should not trigger an update (QTBUG-6242)
}

void tst_QGraphicsItem::QTBUG_6738_missingUpdateWithSetParent()
{
    // In all 3 test cases below the reparented item should disappear
    EventTester *parent = new EventTester;
    EventTester *child = new EventTester(parent);
    EventTester *child2 = new EventTester(parent);
    EventTester *child3 = new EventTester(parent);
    EventTester *child4 = new EventTester(parent);

    child->setPos(10, 10);
    child2->setPos(20, 20);
    child3->setPos(30, 30);
    child4->setPos(40, 40);

    QGraphicsScene scene;
    scene.addItem(parent);

    MyGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    view.show();
    if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) {
        view.window()->activateWindow();
        QVERIFY(QTest::qWaitForWindowActive(&view));
    }
    QVERIFY(QTest::qWaitForWindowExposed(&view));
    QCoreApplication::processEvents(); // Process all queued paint events
    QTRY_VERIFY(view.repaints > 0);

    // test case #1
    view.reset();
    child2->setVisible(false);
    child2->setParentItem(child);

    QTRY_COMPARE(view.repaints, 1);

    // test case #2
    view.reset();
    child3->setOpacity(0.0);
    child3->setParentItem(child);

    QTRY_COMPARE(view.repaints, 1);

    // test case #3
    view.reset();
    child4->setParentItem(child);
    child4->setVisible(false);

    QTRY_COMPARE(view.repaints, 1);
}

void tst_QGraphicsItem::QT_2653_fullUpdateDiscardingOpacityUpdate()
{
    QGraphicsScene scene(0, 0, 200, 200);
    MyGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));

    EventTester *parentGreen = new EventTester();
    parentGreen->setGeometry(QRectF(20, 20, 100, 100));
    parentGreen->brush = Qt::green;

    EventTester *childYellow = new EventTester(parentGreen);
    childYellow->setGeometry(QRectF(10, 10, 50, 50));
    childYellow->brush = Qt::yellow;

    scene.addItem(parentGreen);

    childYellow->setOpacity(0.0);
    parentGreen->setOpacity(0.0);

    // set any of the flags below to trigger a fullUpdate to reproduce the bug:
    // ItemIgnoresTransformations, ItemClipsChildrenToShape, ItemIsSelectable
    parentGreen->setFlag(QGraphicsItem::ItemIgnoresTransformations);

    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));
    if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) {
        view.window()->activateWindow();
        QVERIFY(QTest::qWaitForWindowActive(&view));
    }
    QCoreApplication::processEvents(); // Process all queued paint events
    view.reset();

    parentGreen->setOpacity(1.0);

    QTRY_COMPARE(view.repaints, 1);

    view.reset();
    childYellow->repaints = 0;

    childYellow->setOpacity(1.0);

    QTRY_COMPARE(view.repaints, 1);
    QTRY_COMPARE(childYellow->repaints, 1);
}

void tst_QGraphicsItem::QTBUG_7714_fullUpdateDiscardingOpacityUpdate2()
{
    QGraphicsScene scene(0, 0, 200, 200);
    MyGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    MyGraphicsView origView(&scene);

    EventTester *parentGreen = new EventTester();
    parentGreen->setGeometry(QRectF(20, 20, 100, 100));
    parentGreen->brush = Qt::green;

    EventTester *childYellow = new EventTester(parentGreen);
    childYellow->setGeometry(QRectF(10, 10, 50, 50));
    childYellow->brush = Qt::yellow;

    scene.addItem(parentGreen);

    origView.show();
    if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) {
        view.window()->activateWindow();
        QVERIFY(QTest::qWaitForWindowActive(&origView));
    }
    QVERIFY(QTest::qWaitForWindowExposed(&origView));
    QCoreApplication::processEvents(); // Process all queued paint events

    origView.setGeometry(origView.x() + origView.width() + 20, origView.y() + 20,
                         origView.width(), origView.height());

    parentGreen->setFlag(QGraphicsItem::ItemIgnoresTransformations);

    origView.reset();
    childYellow->setOpacity(0.0);

    QTRY_VERIFY(origView.repaints > 0);

    view.show();
    if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) {
        QApplicationPrivate::setActiveWindow(&view);
        QVERIFY(QTest::qWaitForWindowActive(&view));
    }
    QVERIFY(QTest::qWaitForWindowExposed(&view));
    view.reset();
    origView.reset();

    childYellow->setOpacity(1.0);

    QTRY_VERIFY(origView.repaints > 0);
    QTRY_VERIFY(view.repaints > 0);
}

void tst_QGraphicsItem::QT_2649_focusScope()
{
    QGraphicsScene *scene = new QGraphicsScene;

    QGraphicsRectItem *subFocusItem = new QGraphicsRectItem;
    subFocusItem->setFlags(QGraphicsItem::ItemIsFocusable);
    subFocusItem->setFocus();
    QCOMPARE(subFocusItem->focusItem(), subFocusItem);

    QGraphicsRectItem *scope = new QGraphicsRectItem;
    scope->setFlags(QGraphicsItem::ItemIsFocusable | QGraphicsItem::ItemIsFocusScope);
    scope->setFocus();
    subFocusItem->setParentItem(scope);
    QCOMPARE(subFocusItem->focusItem(), subFocusItem);
    QCOMPARE(subFocusItem->focusScopeItem(), nullptr);
    QCOMPARE(scope->focusItem(), subFocusItem);
    QCOMPARE(scope->focusScopeItem(), subFocusItem);

    QGraphicsRectItem *rootItem = new QGraphicsRectItem;
    rootItem->setFlags(QGraphicsItem::ItemIsFocusable);
    scope->setParentItem(rootItem);
    QCOMPARE(rootItem->focusItem(), subFocusItem);
    QCOMPARE(rootItem->focusScopeItem(), nullptr);
    QCOMPARE(subFocusItem->focusItem(), subFocusItem);
    QCOMPARE(subFocusItem->focusScopeItem(), nullptr);
    QCOMPARE(scope->focusItem(), subFocusItem);
    QCOMPARE(scope->focusScopeItem(), subFocusItem);

    scene->addItem(rootItem);

    QEvent windowActivate(QEvent::WindowActivate);
    qApp->sendEvent(scene, &windowActivate);
    scene->setFocus();

    QCOMPARE(rootItem->focusItem(), subFocusItem);
    QCOMPARE(scope->focusItem(), subFocusItem);
    QCOMPARE(subFocusItem->focusItem(), subFocusItem);
    QCOMPARE(rootItem->focusScopeItem(), nullptr);
    QCOMPARE(scope->focusScopeItem(), subFocusItem);
    QCOMPARE(subFocusItem->focusScopeItem(), nullptr);
    QVERIFY(subFocusItem->hasFocus());

    scope->hide();

    QCOMPARE(rootItem->focusItem(), nullptr);
    QCOMPARE(scope->focusItem(), nullptr);
    QCOMPARE(subFocusItem->focusItem(), nullptr);
    QCOMPARE(rootItem->focusScopeItem(), nullptr);
    QCOMPARE(scope->focusScopeItem(), subFocusItem);
    QCOMPARE(subFocusItem->focusScopeItem(), nullptr);
    QVERIFY(!subFocusItem->hasFocus());

    scope->show();

    QCOMPARE(rootItem->focusItem(), subFocusItem);
    QCOMPARE(scope->focusItem(), subFocusItem);
    QCOMPARE(subFocusItem->focusItem(), subFocusItem);
    QCOMPARE(rootItem->focusScopeItem(), nullptr);
    QCOMPARE(scope->focusScopeItem(), subFocusItem);
    QCOMPARE(subFocusItem->focusScopeItem(), nullptr);
    QVERIFY(subFocusItem->hasFocus());

    // This should not crash
    scope->hide();
    delete scene;
}

class MyGraphicsItemWithItemChange : public QGraphicsWidget
{
public:
    using QGraphicsWidget::QGraphicsWidget;

    QVariant itemChange(GraphicsItemChange change, const QVariant &value) override
    {
        if (change == QGraphicsItem::ItemSceneHasChanged) {
            const auto views = scene()->views();
            for (QGraphicsView *view : views) // We trigger a sort of unindexed items in the BSP
                view->sceneRect();
        }
        return QGraphicsWidget::itemChange(change, value);
    }
};

void tst_QGraphicsItem::sortItemsWhileAdding()
{
    QGraphicsScene scene;
    QGraphicsView view(&scene);
    QGraphicsWidget grandGrandParent;
    grandGrandParent.resize(200, 200);
    scene.addItem(&grandGrandParent);
    QGraphicsWidget grandParent;
    grandParent.resize(200, 200);
    QGraphicsWidget parent(&grandParent);
    parent.resize(200, 200);
    MyGraphicsItemWithItemChange item(&parent);
    grandParent.setParentItem(&grandGrandParent);
}

void tst_QGraphicsItem::doNotMarkFullUpdateIfNotInScene()
{
    if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation))
        QSKIP("Window activation is not supported");

    struct Item : public QGraphicsTextItem
    {
        int painted = 0;
        void paint(QPainter *painter, const QStyleOptionGraphicsItem *opt, QWidget *wid) override
        {
            painted++;
            QGraphicsTextItem::paint(painter, opt, wid);
        }
    };
    QGraphicsScene scene;
    MyGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    Item *item = new Item;
    item->setPlainText("Grandparent");
    Item *item2 = new Item;
    item2->setPlainText("parent");
    Item *item3 = new Item;
    item3->setPlainText("child");
    QGraphicsOpacityEffect *effect = new QGraphicsOpacityEffect;
    effect->setOpacity(0.5);
    item2->setGraphicsEffect(effect);
    item3->setParentItem(item2);
    item2->setParentItem(item);
    scene.addItem(item);
    view.show();
    view.window()->activateWindow();
    QVERIFY(QTest::qWaitForWindowExposed(view.windowHandle()));
    QVERIFY(QTest::qWaitForWindowActive(view.windowHandle()));
    QCoreApplication::processEvents(); // Process all queued paint events
    view.activateWindow();
    QTRY_VERIFY(view.isActiveWindow());
    QTRY_VERIFY(view.repaints >= 1);
    int count = view.repaints;
    QTRY_COMPARE(item->painted, count);
    // cached as graphics effects, not painted multiple times
    QTRY_COMPARE(item2->painted, 1);
    QTRY_COMPARE(item3->painted, 1);
    item2->update();
    QApplication::processEvents();
    QTRY_COMPARE(item->painted, count + 1);
    QTRY_COMPARE(item2->painted, 2);
    QTRY_COMPARE(item3->painted, 2);
    item2->update();
    QApplication::processEvents();
    QTRY_COMPARE(item->painted, count + 2);
    QTRY_COMPARE(item2->painted, 3);
    QTRY_COMPARE(item3->painted, 3);
}

void tst_QGraphicsItem::itemDiesDuringDraggingOperation()
{
    QGraphicsScene scene;
    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    QGraphicsRectItem *item = new QGraphicsRectItem(QRectF(0, 0, 100, 100));
    item->setFlag(QGraphicsItem::ItemIsMovable);
    item->setAcceptDrops(true);
    scene.addItem(item);
    view.show();
    if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) {
        view.window()->activateWindow();
        QVERIFY(QTest::qWaitForWindowActive(&view));
        QCOMPARE(QApplication::activeWindow(), &view);
    }
    QVERIFY(QTest::qWaitForWindowExposed(&view));
    QGraphicsSceneDragDropEvent dragEnter(QEvent::GraphicsSceneDragEnter);
    dragEnter.setScenePos(item->boundingRect().center());
    QCoreApplication::sendEvent(&scene, &dragEnter);
    QGraphicsSceneDragDropEvent event(QEvent::GraphicsSceneDragMove);
    event.setScenePos(item->boundingRect().center());
    QCoreApplication::sendEvent(&scene, &event);
    QCOMPARE(QGraphicsScenePrivate::get(&scene)->dragDropItem, item);
    delete item;
    QVERIFY(!QGraphicsScenePrivate::get(&scene)->dragDropItem);
}

void tst_QGraphicsItem::QTBUG_12112_focusItem()
{
    QGraphicsScene scene;
    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    QGraphicsRectItem *item1 = new QGraphicsRectItem(0, 0, 20, 20);
    item1->setFlag(QGraphicsItem::ItemIsFocusable);
    QGraphicsRectItem *item2 = new QGraphicsRectItem(20, 20, 20, 20);
    item2->setFlag(QGraphicsItem::ItemIsFocusable);
    item1->setFocus();
    scene.addItem(item2);
    scene.addItem(item1);

    view.show();
    if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) {
        view.window()->activateWindow();
        QVERIFY(QTest::qWaitForWindowActive(&view));
        QCOMPARE(QApplication::activeWindow(), &view);
    }
    QVERIFY(QTest::qWaitForWindowExposed(&view));

    QVERIFY(item1->focusItem());
    QVERIFY(!item2->focusItem());

    item2->setFocus();
    QVERIFY(!item1->focusItem());
    QVERIFY(item2->focusItem());
}

void tst_QGraphicsItem::QTBUG_13473_sceneposchange()
{
    ScenePosChangeTester* parent = new ScenePosChangeTester;
    ScenePosChangeTester* child = new ScenePosChangeTester(parent);

    // parent's disabled ItemSendsGeometryChanges flag must not affect
    // child's scene pos change notifications
    parent->setFlag(QGraphicsItem::ItemSendsGeometryChanges, false);
    child->setFlag(QGraphicsItem::ItemSendsScenePositionChanges, true);

    QGraphicsScene scene;
    scene.addItem(parent);

    // ignore uninteresting changes
    parent->clear();
    child->clear();

    // move
    parent->moveBy(1.0, 1.0);
    QCOMPARE(child->changes.count(QGraphicsItem::ItemScenePositionHasChanged), 1);

    // transform
    parent->setTransform(QTransform::fromScale(0.5, 0.5));
    QCOMPARE(child->changes.count(QGraphicsItem::ItemScenePositionHasChanged), 2);
}

class MyGraphicsWidget : public QGraphicsWidget
{
    Q_OBJECT
public:
    MyGraphicsWidget() : QGraphicsWidget(nullptr)
    {
        QGraphicsLinearLayout *lay = new QGraphicsLinearLayout(Qt::Vertical);
        QLatin1String wiseWords("AZ BUKI VEDI");
        QString sentence(wiseWords);
        QStringList words = sentence.split(QLatin1Char(' '), Qt::SkipEmptyParts);
        for (int i = 0; i < words.size(); ++i) {
            QGraphicsProxyWidget *proxy = new QGraphicsProxyWidget(this);
            QLabel *label = new QLabel(words.at(i));
            proxy->setWidget(label);
            proxy->setFocusPolicy(Qt::StrongFocus);
            proxy->setFlag(QGraphicsItem::ItemAcceptsInputMethod, true);
            if (i % 2 == 0)
                proxy->setVisible(false);
            proxy->setFocus();
            lay->addItem(proxy);
        }
        setLayout(lay);
    }
};

class MyWidgetWindow : public QGraphicsWidget
{
public:
    MyWidgetWindow() : QGraphicsWidget(nullptr, Qt::Window)
    {
        QGraphicsLinearLayout *lay = new QGraphicsLinearLayout(Qt::Vertical);
        MyGraphicsWidget *widget = new MyGraphicsWidget();
        lay->addItem(widget);
        setLayout(lay);
    }
};

void tst_QGraphicsItem::QTBUG_16374_crashInDestructor()
{
    QGraphicsScene scene;
    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));

    MyWidgetWindow win;
    scene.addItem(&win);

    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));
}

void tst_QGraphicsItem::QTBUG_20699_focusScopeCrash()
{
    QGraphicsScene scene;
    QGraphicsView view(&scene);
    view.setWindowTitle(QLatin1String(QTest::currentTestFunction()));
    QGraphicsPixmapItem fs;
    fs.setFlags(QGraphicsItem::ItemIsFocusScope | QGraphicsItem::ItemIsFocusable);
    scene.addItem(&fs);
    QGraphicsPixmapItem* fs2 = new QGraphicsPixmapItem(&fs);
    fs2->setFlags(QGraphicsItem::ItemIsFocusScope | QGraphicsItem::ItemIsFocusable);
    QGraphicsPixmapItem* fi2 = new QGraphicsPixmapItem(&fs);
    fi2->setFlags(QGraphicsItem::ItemIsFocusable);
    QGraphicsPixmapItem* fi = new QGraphicsPixmapItem(fs2);
    fi->setFlags(QGraphicsItem::ItemIsFocusable);
    fs.setFocus();
    fi->setFocus();

    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));

    fi->setParentItem(fi2);
    fi->setFocus();
    fs.setFocus();
    fi->setParentItem(fs2);
    fi->setFocus();
    fs2->setFocus();
    fs.setFocus();
    fi->setParentItem(fi2);
    fi->setFocus();
    fs.setFocus();
}

void tst_QGraphicsItem::QTBUG_30990_rightClickSelection()
{
    QGraphicsScene scene;
    QGraphicsItem *item1 = scene.addRect(10, 10, 10, 10);
    item1->setFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsMovable);
    QGraphicsItem *item2 = scene.addRect(100, 100, 10, 10);
    item2->setFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsMovable);

    // right mouse press & release over an item should not make it selected
    sendMousePress(&scene, item1->boundingRect().center(), Qt::RightButton);
    QVERIFY(!item1->isSelected());
    sendMouseRelease(&scene, item1->boundingRect().center(), Qt::RightButton);
    QVERIFY(!item1->isSelected());

    // right mouse press over one item, moving over another item,
    // and then releasing should make neither of the items selected
    sendMousePress(&scene, item1->boundingRect().center(), Qt::RightButton);
    QVERIFY(!item1->isSelected());
    QVERIFY(!item2->isSelected());
    sendMouseMove(&scene, item2->boundingRect().center(), Qt::RightButton);
    QVERIFY(!item1->isSelected());
    QVERIFY(!item2->isSelected());
    sendMouseRelease(&scene, item2->boundingRect().center(), Qt::RightButton);
    QVERIFY(!item1->isSelected());
    QVERIFY(!item2->isSelected());
}

void tst_QGraphicsItem::QTBUG_21618_untransformable_sceneTransform()
{
    QGraphicsScene scene(0, 0, 150, 150);
    scene.addRect(-2, -2, 4, 4);

    QGraphicsItem *item1 = scene.addRect(0, 0, 100, 100, QPen(), Qt::red);
    item1->setPos(50, 50);
    item1->setTransform(QTransform::fromTranslate(50, 50), true);
    item1->setTransform(QTransform().rotate(90), true);
    QGraphicsItem *item2 = scene.addRect(0, 0, 100, 100, QPen(), Qt::green);
    item2->setPos(50, 50);
    item2->setTransform(QTransform::fromTranslate(50, 50), true);
    item2->setTransform(QTransform().rotate(90), true);
    item2->setFlags(QGraphicsItem::ItemIgnoresTransformations);

    QGraphicsRectItem *item1_topleft = new QGraphicsRectItem(QRectF(-2, -2, 4, 4));
    item1_topleft->setParentItem(item1);
    item1_topleft->setBrush(Qt::black);
    QGraphicsRectItem *item1_bottomright = new QGraphicsRectItem(QRectF(-2, -2, 4, 4));
    item1_bottomright->setParentItem(item1);
    item1_bottomright->setPos(100, 100);
    item1_bottomright->setBrush(Qt::yellow);

    QGraphicsRectItem *item2_topleft = new QGraphicsRectItem(QRectF(-2, -2, 4, 4));
    item2_topleft->setParentItem(item2);
    item2_topleft->setBrush(Qt::black);
    QGraphicsRectItem *item2_bottomright = new QGraphicsRectItem(QRectF(-2, -2, 4, 4));
    item2_bottomright->setParentItem(item2);
    item2_bottomright->setPos(100, 100);
    item2_bottomright->setBrush(Qt::yellow);

    QCOMPARE(item1->sceneTransform(), item2->sceneTransform());
    QCOMPARE(item1_topleft->sceneTransform(), item2_topleft->sceneTransform());
    QCOMPARE(item1_bottomright->sceneTransform(), item2_bottomright->sceneTransform());
    QCOMPARE(item1->deviceTransform(QTransform()), item2->deviceTransform(QTransform()));
    QCOMPARE(item1->deviceTransform(QTransform()).map(QPointF()), QPointF(100, 100));
    QCOMPARE(item2->deviceTransform(QTransform()).map(QPointF()), QPointF(100, 100));
    QCOMPARE(item1->deviceTransform(QTransform()).map(QPointF(100, 100)), QPointF(0, 200));
    QCOMPARE(item2->deviceTransform(QTransform()).map(QPointF(100, 100)), QPointF(0, 200));
    QCOMPARE(item1_topleft->deviceTransform(QTransform()).map(QPointF()), QPointF(100, 100));
    QCOMPARE(item2_topleft->deviceTransform(QTransform()).map(QPointF()), QPointF(100, 100));
    QCOMPARE(item1_bottomright->deviceTransform(QTransform()).map(QPointF()), QPointF(0, 200));
    QCOMPARE(item2_bottomright->deviceTransform(QTransform()).map(QPointF()), QPointF(0, 200));

    item2->setParentItem(item1);

    QCOMPARE(item2->deviceTransform(QTransform()).map(QPointF()), QPointF(100, 200));
    QCOMPARE(item2->deviceTransform(QTransform()).map(QPointF(100, 100)), QPointF(0, 300));
    QCOMPARE(item2_topleft->deviceTransform(QTransform()).map(QPointF()), QPointF(100, 200));
    QCOMPARE(item2_bottomright->deviceTransform(QTransform()).map(QPointF()), QPointF(0, 300));

    QTransform tx = QTransform::fromTranslate(100, 0);
    QCOMPARE(item1->deviceTransform(tx).map(QPointF()), QPointF(200, 100));
    QCOMPARE(item1->deviceTransform(tx).map(QPointF(100, 100)), QPointF(100, 200));
    QCOMPARE(item2->deviceTransform(tx).map(QPointF()), QPointF(200, 200));
    QCOMPARE(item2->deviceTransform(tx).map(QPointF(100, 100)), QPointF(100, 300));
    QCOMPARE(item2_topleft->deviceTransform(tx).map(QPointF()), QPointF(200, 200));
    QCOMPARE(item2_bottomright->deviceTransform(tx).map(QPointF()), QPointF(100, 300));
}

void tst_QGraphicsItem::resolvePaletteForItemChildren()
{
    QGraphicsScene scene;
    QGraphicsRectItem item(0, 0, 50, -150);
    scene.addItem(&item);
    QGraphicsWidget widget;
    widget.setParentItem(&item);

    QColor green(Qt::green);
    QPalette paletteForScene = scene.palette();
    paletteForScene.setColor(QPalette::Active, QPalette::Window, green);
    scene.setPalette(paletteForScene);

    QCOMPARE(widget.palette().color(QPalette::Active, QPalette::Window), green);
}

QTEST_MAIN(tst_QGraphicsItem)
#include "tst_qgraphicsitem.moc"