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:
parent
527b5f6ff4
commit
0b76a0b322
@ -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()
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user