Add logging, clarifications, and tests for QWidget show/hide behavior

Task-number: QTBUG-121398
Change-Id: I94b4c90c3bd515279417c88497d7b9bd5a362eae
Reviewed-by: Axel Spoerl <axel.spoerl@qt.io>
(cherry picked from commit b393b26c70cc40c37173cba8c22d66484041087c)
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
This commit is contained in:
Tor Arne Vestbø 2024-01-18 16:53:32 +01:00
parent 527b5f6ff4
commit 0b76a0b322
4 changed files with 180 additions and 10 deletions

View File

@ -82,6 +82,14 @@ using namespace QNativeInterface::Private;
using namespace Qt::StringLiterals; using namespace Qt::StringLiterals;
Q_LOGGING_CATEGORY(lcWidgetPainting, "qt.widgets.painting", QtWarningMsg); Q_LOGGING_CATEGORY(lcWidgetPainting, "qt.widgets.painting", QtWarningMsg);
Q_LOGGING_CATEGORY(lcWidgetShowHide, "qt.widgets.showhide", QtWarningMsg);
#ifndef QT_NO_DEBUG_STREAM
namespace {
struct WidgetAttributes { const QWidget *widget; };
QDebug operator<<(QDebug debug, const WidgetAttributes &attributes);
}
#endif
static inline bool qRectIntersects(const QRect &r1, const QRect &r2) static inline bool qRectIntersects(const QRect &r1, const QRect &r2)
{ {
@ -8319,13 +8327,17 @@ void QWidgetPrivate::hide_sys()
void QWidget::setVisible(bool visible) void QWidget::setVisible(bool visible)
{ {
Q_D(QWidget);
qCDebug(lcWidgetShowHide) << "Setting visibility of" << this
<< "with attributes" << WidgetAttributes{this}
<< "to" << visible << "via QWidget";
if (testAttribute(Qt::WA_WState_ExplicitShowHide) && testAttribute(Qt::WA_WState_Hidden) == !visible) if (testAttribute(Qt::WA_WState_ExplicitShowHide) && testAttribute(Qt::WA_WState_Hidden) == !visible)
return; return;
// Remember that setVisible was called explicitly // Remember that setVisible was called explicitly
setAttribute(Qt::WA_WState_ExplicitShowHide); setAttribute(Qt::WA_WState_ExplicitShowHide);
Q_D(QWidget);
d->setVisible(visible); d->setVisible(visible);
} }
@ -8335,6 +8347,10 @@ void QWidget::setVisible(bool visible)
void QWidgetPrivate::setVisible(bool visible) void QWidgetPrivate::setVisible(bool visible)
{ {
Q_Q(QWidget); Q_Q(QWidget);
qCDebug(lcWidgetShowHide) << "Setting visibility of" << q
<< "with attributes" << WidgetAttributes{q}
<< "to" << visible << "via QWidgetPrivate";
if (visible) { // show if (visible) { // show
// Designer uses a trick to make grabWidget work without showing // Designer uses a trick to make grabWidget work without showing
if (!q->isWindow() && q->parentWidget() && q->parentWidget()->isVisible() if (!q->isWindow() && q->parentWidget() && q->parentWidget()->isVisible()
@ -8454,14 +8470,20 @@ void QWidgetPrivate::_q_showIfNotHidden()
void QWidgetPrivate::showChildren(bool spontaneous) void QWidgetPrivate::showChildren(bool spontaneous)
{ {
Q_Q(QWidget);
qCDebug(lcWidgetShowHide) << "Showing children of" << q
<< "spontaneously" << spontaneous;
QList<QObject*> childList = children; QList<QObject*> childList = children;
for (int i = 0; i < childList.size(); ++i) { for (int i = 0; i < childList.size(); ++i) {
QWidget *widget = qobject_cast<QWidget*>(childList.at(i)); QWidget *widget = qobject_cast<QWidget*>(childList.at(i));
if (widget && widget->windowHandle() && !widget->testAttribute(Qt::WA_WState_ExplicitShowHide)) if (!widget)
continue;
qCDebug(lcWidgetShowHide) << "Considering" << widget
<< "with attributes" << WidgetAttributes{widget};
if (widget->windowHandle() && !widget->testAttribute(Qt::WA_WState_ExplicitShowHide))
widget->setAttribute(Qt::WA_WState_Hidden, false); widget->setAttribute(Qt::WA_WState_Hidden, false);
if (!widget if (widget->isWindow() || widget->testAttribute(Qt::WA_WState_Hidden))
|| widget->isWindow()
|| widget->testAttribute(Qt::WA_WState_Hidden))
continue; continue;
if (spontaneous) { if (spontaneous) {
widget->setAttribute(Qt::WA_Mapped); widget->setAttribute(Qt::WA_Mapped);
@ -8480,10 +8502,17 @@ void QWidgetPrivate::showChildren(bool spontaneous)
void QWidgetPrivate::hideChildren(bool spontaneous) void QWidgetPrivate::hideChildren(bool spontaneous)
{ {
Q_Q(QWidget); Q_Q(QWidget);
qCDebug(lcWidgetShowHide) << "Hiding children of" << q
<< "spontaneously" << spontaneous;
QList<QObject*> childList = children; QList<QObject*> childList = children;
for (int i = 0; i < childList.size(); ++i) { for (int i = 0; i < childList.size(); ++i) {
QWidget *widget = qobject_cast<QWidget*>(childList.at(i)); QWidget *widget = qobject_cast<QWidget*>(childList.at(i));
if (!widget || widget->isWindow() || widget->testAttribute(Qt::WA_WState_Hidden)) if (!widget)
continue;
qCDebug(lcWidgetShowHide) << "Considering" << widget
<< "with attributes" << WidgetAttributes{widget};
if (widget->isWindow() || widget->testAttribute(Qt::WA_WState_Hidden))
continue; continue;
if (spontaneous) if (spontaneous)
@ -8541,13 +8570,15 @@ void QWidgetPrivate::hideChildren(bool spontaneous)
*/ */
bool QWidgetPrivate::handleClose(CloseMode mode) bool QWidgetPrivate::handleClose(CloseMode mode)
{ {
Q_Q(QWidget);
qCDebug(lcWidgetShowHide) << "Handling close event for" << q;
if (data.is_closing) if (data.is_closing)
return true; return true;
// We might not have initiated the close, so update the state now that we know // We might not have initiated the close, so update the state now that we know
data.is_closing = true; data.is_closing = true;
Q_Q(QWidget);
QPointer<QWidget> that = q; QPointer<QWidget> that = q;
if (data.in_destructor) if (data.in_destructor)
@ -10714,7 +10745,20 @@ void QWidget::setParent(QWidget *parent, Qt::WindowFlags f)
if (wasCreated) { if (wasCreated) {
if (!testAttribute(Qt::WA_WState_Hidden)) { if (!testAttribute(Qt::WA_WState_Hidden)) {
// Hiding the widget will set WA_WState_Hidden as well, which would
// normally require the widget to be explicitly shown again to become
// visible, even as a child widget. But we refine this value later in
// setParent_sys(), applying WA_WState_Hidden based on whether the
// widget is a top level or not.
hide(); hide();
// We reset WA_WState_ExplicitShowHide here, likely as a remnant of
// when we only had QWidget::setVisible(), which is treated as an
// explicit show/hide. Nowadays we have QWidgetPrivate::setVisible(),
// that allows us to hide a widget without affecting ExplicitShowHide.
// Though it can be argued that ExplicitShowHide should reflect the
// last update of the widget's state, so if we hide the widget as a
// side effect of changing parent, perhaps we _should_ reset it?
setAttribute(Qt::WA_WState_ExplicitShowHide, false); setAttribute(Qt::WA_WState_ExplicitShowHide, false);
} }
if (newParent) { if (newParent) {
@ -13165,11 +13209,15 @@ void QWidgetPrivate::setNetWmWindowTypes(bool skipIfMissing)
#ifndef QT_NO_DEBUG_STREAM #ifndef QT_NO_DEBUG_STREAM
static inline void formatWidgetAttributes(QDebug debug, const QWidget *widget) namespace {
QDebug operator<<(QDebug debug, const WidgetAttributes &attributes)
{ {
const QDebugStateSaver saver(debug);
debug.nospace();
const QWidget *widget = attributes.widget;
const QMetaObject *qtMo = qt_getEnumMetaObject(Qt::WA_AttributeCount); const QMetaObject *qtMo = qt_getEnumMetaObject(Qt::WA_AttributeCount);
const QMetaEnum me = qtMo->enumerator(qtMo->indexOfEnumerator("WidgetAttribute")); const QMetaEnum me = qtMo->enumerator(qtMo->indexOfEnumerator("WidgetAttribute"));
debug << ", attributes=["; debug << '[';
int count = 0; int count = 0;
for (int a = 0; a < Qt::WA_AttributeCount; ++a) { for (int a = 0; a < Qt::WA_AttributeCount; ++a) {
if (widget->testAttribute(static_cast<Qt::WidgetAttribute>(a))) { if (widget->testAttribute(static_cast<Qt::WidgetAttribute>(a))) {
@ -13179,6 +13227,8 @@ static inline void formatWidgetAttributes(QDebug debug, const QWidget *widget)
} }
} }
debug << ']'; debug << ']';
return debug;
}
} }
QDebug operator<<(QDebug debug, const QWidget *widget) QDebug operator<<(QDebug debug, const QWidget *widget)
@ -13198,7 +13248,7 @@ QDebug operator<<(QDebug debug, const QWidget *widget)
debug << ", disabled"; debug << ", disabled";
debug << ", states=" << widget->windowState() debug << ", states=" << widget->windowState()
<< ", type=" << widget->windowType() << ", flags=" << widget->windowFlags(); << ", type=" << widget->windowType() << ", flags=" << widget->windowFlags();
formatWidgetAttributes(debug, widget); debug << ", attributes=" << WidgetAttributes{widget};
if (widget->isWindow()) if (widget->isWindow())
debug << ", window"; debug << ", window";
debug << ", " << geometry.width() << 'x' << geometry.height() debug << ", " << geometry.width() << 'x' << geometry.height()

View File

@ -50,6 +50,7 @@
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(lcWidgetPainting); Q_DECLARE_LOGGING_CATEGORY(lcWidgetPainting);
Q_DECLARE_LOGGING_CATEGORY(lcWidgetShowHide);
// Extra QWidget data // Extra QWidget data
// - to minimize memory usage for members that are seldom used. // - to minimize memory usage for members that are seldom used.

View File

@ -39,6 +39,8 @@ public:
void setVisible(bool visible) override void setVisible(bool visible) override
{ {
Q_Q(QWidgetWindow); Q_Q(QWidgetWindow);
qCDebug(lcWidgetShowHide) << "Setting visibility of" << q->widget()
<< "to" << visible << "via QWidgetWindowPrivate";
if (QWidget *widget = q->widget()) { if (QWidget *widget = q->widget()) {
// Check if the widget was already hidden, as this indicates it was done // Check if the widget was already hidden, as this indicates it was done
// explicitly and not because the parent window in this case made it hidden. // explicitly and not because the parent window in this case made it hidden.
@ -196,6 +198,9 @@ QObject *QWidgetWindow::focusObject() const
void QWidgetWindow::setNativeWindowVisibility(bool visible) void QWidgetWindow::setNativeWindowVisibility(bool visible)
{ {
Q_D(QWidgetWindow); Q_D(QWidgetWindow);
qCDebug(lcWidgetShowHide) << "Setting visibility of" << this
<< "to" << visible << "via QWidgetWindow::setNativeWindowVisibility";
// Call base class setVisible() implementation to run the QWindow // Call base class setVisible() implementation to run the QWindow
// visibility logic. Don't call QWidgetWindowPrivate::setVisible() // visibility logic. Don't call QWidgetWindowPrivate::setVisible()
// since that will recurse back into QWidget code. // since that will recurse back into QWidget code.

View File

@ -437,6 +437,8 @@ private slots:
void showFullscreenAndroid(); void showFullscreenAndroid();
#endif #endif
void explicitShowHide();
void dragEnterLeaveSymmetry(); void dragEnterLeaveSymmetry();
void setVisibleDuringDestruction(); void setVisibleDuringDestruction();
@ -13435,6 +13437,118 @@ void tst_QWidget::showFullscreenAndroid()
} }
#endif // Q_OS_ANDROID #endif // Q_OS_ANDROID
void tst_QWidget::explicitShowHide()
{
{
QWidget parent;
parent.setObjectName("Parent");
QWidget child(&parent);
child.setObjectName("Child");
QCOMPARE(parent.testAttribute(Qt::WA_WState_ExplicitShowHide), false);
QCOMPARE(parent.testAttribute(Qt::WA_WState_Hidden), true);
QCOMPARE(child.testAttribute(Qt::WA_WState_ExplicitShowHide), false);
QCOMPARE(child.testAttribute(Qt::WA_WState_Hidden), false);
parent.show();
QCOMPARE(parent.testAttribute(Qt::WA_WState_ExplicitShowHide), true);
QCOMPARE(parent.testAttribute(Qt::WA_WState_Hidden), false);
QCOMPARE(child.testAttribute(Qt::WA_WState_ExplicitShowHide), false);
QCOMPARE(child.testAttribute(Qt::WA_WState_Hidden), false);
// Fix up earlier expected failure
child.setAttribute(Qt::WA_WState_ExplicitShowHide, false);
parent.hide();
QCOMPARE(parent.testAttribute(Qt::WA_WState_ExplicitShowHide), true);
QCOMPARE(parent.testAttribute(Qt::WA_WState_Hidden), true);
QCOMPARE(child.testAttribute(Qt::WA_WState_ExplicitShowHide), false);
QCOMPARE(child.testAttribute(Qt::WA_WState_Hidden), false);
}
{
// Test what happens when a child is reparented after showing it
QWidget parent;
parent.setObjectName("Parent");
QWidget child;
child.setObjectName("Child");
child.show();
QCOMPARE(child.isVisible(), true);
QCOMPARE(child.testAttribute(Qt::WA_WState_ExplicitShowHide), true);
QCOMPARE(child.testAttribute(Qt::WA_WState_Hidden), false);
child.setParent(&parent);
// As documented, a widget becomes invisible as part of changing
// its parent, even if it was previously visible. The user must call
// show() to make the widget visible again.
QCOMPARE(child.isVisible(), false);
// However, the widget does not end up with Qt::WA_WState_Hidden,
// as QWidget::setParent treats it as a child, which normally will
// not get Qt::WA_WState_Hidden out of the box.
QCOMPARE(child.testAttribute(Qt::WA_WState_Hidden), false);
// For some reason we reset WA_WState_ExplicitShowHide, and it's
// not clear whether this is correct or not See QWidget::setParent()
// for a comment with more details.
QEXPECT_FAIL("", "We reset WA_WState_ExplicitShowHide on widget re-parent", Continue);
QCOMPARE(child.testAttribute(Qt::WA_WState_ExplicitShowHide), true);
// The fact that the child doesn't have Qt::WA_WState_Hidden means
// it's sufficient to show the parent widget. We don't need to
// explicitly show the child.
parent.show();
QCOMPARE(child.isVisible(), true);
QCOMPARE(child.testAttribute(Qt::WA_WState_Hidden), false);
}
{
QWidget parent;
parent.setObjectName("Parent");
QWidget child(&parent);
child.setObjectName("Child");
parent.show();
// If a non-native child ends up being closed, we will hide the
// widget, but do so via QWidget::hide(), which marks the widget
// as explicitly hidden.
child.setAttribute(Qt::WA_WState_ExplicitShowHide, false);
QCOMPARE(child.close(), true);
QEXPECT_FAIL("", "Closing a non-native child is treated as an explicit hide", Continue);
QCOMPARE(child.testAttribute(Qt::WA_WState_ExplicitShowHide), false);
QCOMPARE(child.testAttribute(Qt::WA_WState_Hidden), true);
child.show();
child.setAttribute(Qt::WA_NativeWindow);
child.setAttribute(Qt::WA_WState_ExplicitShowHide, false);
QCOMPARE(child.close(), true);
QCOMPARE(child.testAttribute(Qt::WA_WState_ExplicitShowHide), true);
QCOMPARE(child.testAttribute(Qt::WA_WState_Hidden), true);
child.show();
child.setAttribute(Qt::WA_WState_ExplicitShowHide, false);
QCOMPARE(child.windowHandle()->close(), false); // Can't close non-top level QWindows
QCOMPARE(child.testAttribute(Qt::WA_WState_ExplicitShowHide), false);
QCOMPARE(child.testAttribute(Qt::WA_WState_Hidden), false);
// If we end up in QWidgetPrivate::handleClose via QWidgetWindow::closeEvent,
// either through QWindow::close(), or via QWSI::handleCloseEvent, we'll still
// do the explicit hide.
child.show();
child.setAttribute(Qt::WA_WState_ExplicitShowHide, false);
QCOMPARE(QWindowSystemInterface::handleCloseEvent<QWindowSystemInterface::SynchronousDelivery>(
child.windowHandle()), true);
QEXPECT_FAIL("", "Closing a native child via QWSI is treated as an explicit hide", Continue);
QCOMPARE(child.testAttribute(Qt::WA_WState_ExplicitShowHide), false);
QCOMPARE(child.testAttribute(Qt::WA_WState_Hidden), true);
}
}
/*! /*!
Verify that we deliver DragEnter/Leave events symmetrically, even if the Verify that we deliver DragEnter/Leave events symmetrically, even if the
widget entered didn't accept the DragEnter event. widget entered didn't accept the DragEnter event.