Allow QObjects to opt in to receiving ParentAboutToChange/ParentChange

QWidget already handles this, but it might be useful for non-Widget
object hierarchies as well, such as in Qt Quick.

The flag is opt in, and as QWidget already handles these events by
itself (without checking any flags), we assert that we don't end up
in this code path, instead of enabling it for QWidget. The latter
would mean refactoring the QWidget code, with possible regressions.

Docs and header comments have been updated to reflect that this
event is not widget specific. (This is an issue with other events
as well, that are documented to say "widget", since they came
from a time when there was only QWidget, but nowadays apply to
e.g. QWindow as well. That's something for another fix though).

Change-Id: Ib71962131d6011c17dcce8c01bd8adcdaa58d798
Reviewed-by: Axel Spoerl <axel.spoerl@qt.io>
bb10
Tor Arne Vestbø 2023-08-24 12:36:28 +02:00
parent bb2f551b32
commit 4ba7a76985
5 changed files with 95 additions and 4 deletions

View File

@ -159,8 +159,10 @@ Q_TRACE_POINT(qtcore, QEvent_dtor, QEvent *event, QEvent::Type type);
\value OrientationChange The screens orientation has changes (QScreenOrientationChangeEvent).
\value Paint Screen update necessary (QPaintEvent).
\value PaletteChange Palette of the widget changed.
\value ParentAboutToChange The widget parent is about to change.
\value ParentChange The widget parent has changed.
\value ParentAboutToChange The object parent is about to change.
Only sent to some object types, such as QWidget.
\value ParentChange The object parent has changed.
Only sent to some object types, such as QWidget.
\value PlatformPanel A platform specific panel has been requested.
\value PlatformSurface A native platform surface has been created or is about to be destroyed (QPlatformSurfaceEvent).
\omitvalue Pointer

View File

@ -77,7 +77,7 @@ public:
Hide = 18, // widget is hidden
Close = 19, // request to close widget
Quit = 20, // request to quit application
ParentChange = 21, // widget has been reparented
ParentChange = 21, // object has been reparented
ParentAboutToChange = 131, // sent just before the parent change is done
ThreadChange = 22, // object has changed threads
WindowActivate = 24, // window was activated

View File

@ -179,6 +179,7 @@ QObjectPrivate::QObjectPrivate(int version)
isQuickItem = false;
willBeWidget = false;
wasWidget = false;
receiveParentEvents = false; // If object wants ParentAboutToChange and ParentChange
}
QObjectPrivate::~QObjectPrivate()
@ -2249,7 +2250,15 @@ void QObjectPrivate::setParent_helper(QObject *o)
}
}
}
if (receiveParentEvents) {
Q_ASSERT(!isWidget); // Handled in QWidget
QEvent e(QEvent::ParentAboutToChange);
QCoreApplication::sendEvent(q, &e);
}
parent = o;
if (parent) {
// object hierarchies are constrained to a single thread
if (threadData.loadRelaxed() != parent->d_func()->threadData.loadRelaxed()) {
@ -2265,6 +2274,12 @@ void QObjectPrivate::setParent_helper(QObject *o)
}
}
}
if (receiveParentEvents) {
Q_ASSERT(!isWidget); // Handled in QWidget
QEvent e(QEvent::ParentChange);
QCoreApplication::sendEvent(q, &e);
}
}
/*!

View File

@ -72,7 +72,8 @@ public:
uint isQuickItem : 1;
uint willBeWidget : 1; // for handling widget-specific bits in QObject's ctor
uint wasWidget : 1; // for properly cleaning up in QObject's dtor
uint unused : 21;
uint receiveParentEvents: 1;
uint unused : 20;
QAtomicInt postedEvents;
QDynamicMetaObjectData *metaObject;
QBindingStorage bindingStorage;

View File

@ -80,6 +80,7 @@ private slots:
void signalBlocking();
void blockingQueuedConnection();
void childEvents();
void parentEvents();
void installEventFilter();
void deleteSelfInSlot();
void disconnectSelfInSlotAndDeleteAfterEmit();
@ -3200,6 +3201,78 @@ void tst_QObject::childEvents()
}
}
void tst_QObject::parentEvents()
{
#ifdef QT_BUILD_INTERNAL
EventSpy::EventList expected;
{
// Parent events not enabled
QObject parent;
QObject child;
EventSpy spy;
child.installEventFilter(&spy);
QCoreApplication::postEvent(&child, new QEvent(QEvent::Type(QEvent::User + 1)));
child.setParent(&parent);
QCoreApplication::postEvent(&child, new QEvent(QEvent::Type(QEvent::User + 2)));
expected =
EventSpy::EventList();
QCOMPARE(spy.eventList(), expected);
spy.clear();
QCoreApplication::processEvents();
expected =
EventSpy::EventList()
<< qMakePair(&child, QEvent::Type(QEvent::User + 1))
<< qMakePair(&child, QEvent::Type(QEvent::User + 2));
QCOMPARE(spy.eventList(), expected);
}
{
// Parent events enabled
QObject parent;
QObject child;
auto *childPrivate = QObjectPrivate::get(&child);
childPrivate->receiveParentEvents = true;
EventSpy spy;
child.installEventFilter(&spy);
QCoreApplication::postEvent(&child, new QEvent(QEvent::Type(QEvent::User + 1)));
child.setParent(&parent);
child.setParent(nullptr);
QCoreApplication::postEvent(&child, new QEvent(QEvent::Type(QEvent::User + 2)));
expected =
EventSpy::EventList()
<< qMakePair(&child, QEvent::ParentAboutToChange)
<< qMakePair(&child, QEvent::ParentChange)
<< qMakePair(&child, QEvent::ParentAboutToChange)
<< qMakePair(&child, QEvent::ParentChange);
QCOMPARE(spy.eventList(), expected);
spy.clear();
QCoreApplication::processEvents();
expected =
EventSpy::EventList()
<< qMakePair(&child, QEvent::Type(QEvent::User + 1))
<< qMakePair(&child, QEvent::Type(QEvent::User + 2));
QCOMPARE(spy.eventList(), expected);
}
#else
QSKIP("Needs QT_BUILD_INTERNAL");
#endif
}
void tst_QObject::installEventFilter()
{
QEvent event(QEvent::User);