tests/auto/qwidget/tst_qwidget.cpp
changeset 33 3e2da88830cd
parent 30 5dc02b23752f
child 37 758a864f9613
--- a/tests/auto/qwidget/tst_qwidget.cpp	Tue Jul 06 15:10:48 2010 +0300
+++ b/tests/auto/qwidget/tst_qwidget.cpp	Wed Aug 18 10:37:55 2010 +0300
@@ -151,16 +151,6 @@
 #undef Bool
 #endif
 
-// Will try to wait for the condition while allowing event processing
-// for a maximum of 2 seconds.
-#define WAIT_FOR_CONDITION(expr, expected) \
-    do { \
-        const int step = 100; \
-        for (int i = 0; i < 2000 && expr != expected; i+=step) { \
-            QTest::qWait(step); \
-        } \
-    } while(0)
-
 //TESTED_CLASS=
 //TESTED_FILES=
 
@@ -385,6 +375,7 @@
     void setGraphicsEffect();
 
     void destroyBackingStore();
+    void destroyBackingStoreWhenHidden();
 
     void activateWindow();
 
@@ -403,6 +394,13 @@
 
     void taskQTBUG_7532_tabOrderWithFocusProxy();
     void movedAndResizedAttributes();
+    void childAt();
+#ifdef Q_WS_MAC
+    void childAt_unifiedToolBar();
+#ifdef QT_MAC_USE_COCOA
+    void taskQTBUG_11373();
+#endif // QT_MAC_USE_COCOA
+#endif
 
 private:
     bool ensureScreenSize(int width, int height);
@@ -605,10 +603,14 @@
     obj1.setAttribute(Qt::WA_InputMethodEnabled);
     obj1.setInputContext(var13);
     QCOMPARE(static_cast<QInputContext *>(var13), obj1.inputContext());
+    // QWidget takes ownership, so check parent
+    QCOMPARE(var13->parent(), static_cast<QObject *>(&obj1));
+    // Check self assignment
+    obj1.setInputContext(obj1.inputContext());
+    QCOMPARE(static_cast<QInputContext *>(var13), obj1.inputContext());
     obj1.setInputContext((QInputContext *)0);
     QCOMPARE(qApp->inputContext(), obj1.inputContext());
     QVERIFY(qApp->inputContext() != var13);
-    //delete var13; // No delete, since QWidget takes ownership
 
     // bool QWidget::autoFillBackground()
     // void QWidget::setAutoFillBackground(bool)
@@ -1656,13 +1658,11 @@
     child->setFocus();
     qApp->processEvents();
 
-    WAIT_FOR_CONDITION(child->hasFocus(), true);
-    QCOMPARE(child->hasFocus(), true);
+    QTRY_COMPARE(child->hasFocus(), true);
     child->hide();
     qApp->processEvents();
 
-    WAIT_FOR_CONDITION(parent->hasFocus(), true);
-    QCOMPARE(parent->hasFocus(), true);
+    QTRY_COMPARE(parent->hasFocus(), true);
     QCOMPARE(parent, qApp->focusWidget());
 
     delete parent;
@@ -9232,7 +9232,8 @@
     QCOMPARE(grandChild->numLeaveEvents, 0);
     QCOMPARE(child1->numLeaveEvents, 0);
 
-    QCOMPARE(window.numEnterEvents, 1);
+    // This event arrives asynchronously
+    QTRY_COMPARE(window.numEnterEvents, 1);
     QCOMPARE(child2->numEnterEvents, 1);
     QCOMPARE(grandChild->numEnterEvents, 1);
     QCOMPARE(child1->numEnterEvents, 0);
@@ -9323,7 +9324,7 @@
      child.show();
 
      // Make sure the child gets enter event and no mouse move event.
-     QCOMPARE(child.numEnterEvents, 1);
+     QTRY_COMPARE(child.numEnterEvents, 1);
      QCOMPARE(child.numMouseMoveEvents, 0);
 
      child.hide();
@@ -9334,7 +9335,7 @@
      // Make sure the child gets enter event and mouse move event.
      // Note that we verify event->button() and event->buttons()
      // in SELChild::mouseMoveEvent().
-     QCOMPARE(child.numEnterEvents, 1);
+     QTRY_COMPARE(child.numEnterEvents, 1);
      QCOMPARE(child.numMouseMoveEvents, 1);
 
      // Sending synthetic enter/leave trough the parent's mousePressEvent handler.
@@ -9345,7 +9346,7 @@
      QTest::mouseClick(&parent, Qt::LeftButton);
 
      // Make sure the child gets enter event and one mouse move event.
-     QCOMPARE(child.numEnterEvents, 1);
+     QTRY_COMPARE(child.numEnterEvents, 1);
      QCOMPARE(child.numMouseMoveEvents, 1);
 
      child.hide();
@@ -9354,7 +9355,7 @@
      QTest::mouseClick(&parent, Qt::LeftButton);
 
      // Make sure the child gets enter event and no mouse move event.
-     QCOMPARE(child.numEnterEvents, 1);
+     QTRY_COMPARE(child.numEnterEvents, 1);
      QCOMPARE(child.numMouseMoveEvents, 0);
  }
 #endif
@@ -9499,9 +9500,7 @@
     QTRY_VERIFY(w.numPaintEvents > 0);
     w.reset();
     w.update();
-    delete qt_widget_private(&w)->topData()->backingStore;
-    qt_widget_private(&w)->topData()->backingStore = 0;
-    qt_widget_private(&w)->topData()->backingStore = new QWidgetBackingStore(&w);
+    qt_widget_private(&w)->topData()->backingStore.create(&w);
 
     w.update();
     QApplication::processEvents();
@@ -9519,6 +9518,252 @@
 #endif
 }
 
+// Helper function
+QWidgetBackingStore* backingStore(QWidget &widget)
+{
+    QWidgetBackingStore *backingStore = 0;
+#ifdef QT_BUILD_INTERNAL
+    if (QTLWExtra *topExtra = qt_widget_private(&widget)->maybeTopData())
+        backingStore = topExtra->backingStore.data();
+#endif
+    return backingStore;
+}
+
+// Wait for a condition to be true, timing out after 1 second
+// This is used following calls to QWidget::show() and QWidget::hide(), which are
+// expected to asynchronously trigger native window visibility events.
+#define WAIT_AND_VERIFY(condition)                                              \
+    do {                                                                        \
+        QTime start = QTime::currentTime();                                     \
+        while (!(condition) && (start.elapsed() < 1000)) {                      \
+            qApp->processEvents();                                              \
+            QTest::qWait(50);                                                   \
+        }                                                                       \
+        if (!QTest::qVerify((condition), #condition, "", __FILE__, __LINE__))   \
+            return;                                                             \
+    } while (0)
+
+void tst_QWidget::destroyBackingStoreWhenHidden()
+{
+#ifndef QT_BUILD_INTERNAL
+    QSKIP("Test step requires access to Q_AUTOTEST_EXPORT", SkipAll);
+#endif
+
+#ifndef Q_OS_SYMBIAN
+    QSKIP("Only Symbian destroys backing store when native window becomes invisible", SkipAll);
+#endif
+
+    testWidget->hide();
+    QTest::qWait(1000);
+
+    // 1. Single top-level QWidget
+    {
+    QWidget w;
+    w.setAutoFillBackground(true);
+    w.setPalette(Qt::yellow);
+    w.setGeometry(0, 0, 100, 100);
+    w.show();
+    QTest::qWaitForWindowShown(&w);
+    QVERIFY(0 != backingStore(w));
+
+    w.hide();
+    WAIT_AND_VERIFY(0 == backingStore(w));
+
+    w.show();
+    QTest::qWaitForWindowShown(&w);
+    QVERIFY(0 != backingStore(w));
+    }
+
+    // 2. Two top-level widgets
+    {
+    QWidget w1;
+    w1.setGeometry(0, 0, 100, 100);
+    w1.setAutoFillBackground(true);
+    w1.setPalette(Qt::red);
+    w1.show();
+    QTest::qWaitForWindowShown(&w1);
+    QVERIFY(0 != backingStore(w1));
+
+    QWidget w2;
+    w2.setGeometry(w1.geometry());
+    w1.setAutoFillBackground(true);
+    w1.setPalette(Qt::blue);
+    w2.show();
+    QTest::qWaitForWindowShown(&w2);
+    QVERIFY(0 != backingStore(w2));
+
+    // Check that w1 deleted its backing store when obscured by w2
+    QVERIFY(0 == backingStore(w1));
+
+    w2.move(w2.pos() + QPoint(10, 10));
+
+    // Check that w1 recreates its backing store when partially revealed
+    WAIT_AND_VERIFY(0 != backingStore(w1));
+    }
+
+    // 3. Native child widget
+    {
+    QWidget parent;
+    parent.setGeometry(0, 0, 100, 100);
+    parent.setAutoFillBackground(true);
+    parent.setPalette(Qt::yellow);
+
+    QWidget child(&parent);
+    child.setAutoFillBackground(true);
+    child.setPalette(Qt::green);
+
+    QVBoxLayout layout(&parent);
+    layout.setContentsMargins(10, 10, 10, 10);
+    layout.addWidget(&child);
+    parent.setLayout(&layout);
+
+    child.winId();
+
+    parent.show();
+    QTest::qWaitForWindowShown(&parent);
+
+    // Check that child window does not obscure parent window
+    QVERIFY(!parent.visibleRegion().subtracted(child.visibleRegion()).isEmpty());
+
+    // Native child widget should share parent's backing store
+    QWidgetBackingStore *const parentBs = backingStore(parent);
+    QVERIFY(0 != parentBs);
+    QVERIFY(0 == backingStore(child));
+
+    // Set margins to zero so that child widget totally obscures parent
+    layout.setContentsMargins(0, 0, 0, 0);
+
+    WAIT_AND_VERIFY(parent.visibleRegion().subtracted(child.visibleRegion()).isEmpty());
+
+    // Backing store should remain unchanged despite child window obscuring
+    // parent window
+    QVERIFY(parentBs == backingStore(parent));
+    QVERIFY(0 == backingStore(child));
+    }
+
+    // 4. Alien child widget which is made full-screen
+    {
+    QWidget parent;
+    parent.setGeometry(0, 0, 100, 100);
+    parent.setAutoFillBackground(true);
+    parent.setPalette(Qt::red);
+
+    QWidget child(&parent);
+    child.setAutoFillBackground(true);
+    child.setPalette(Qt::blue);
+
+    QVBoxLayout layout(&parent);
+    layout.setContentsMargins(10, 10, 10, 10);
+    layout.addWidget(&child);
+    parent.setLayout(&layout);
+
+    parent.show();
+    QTest::qWaitForWindowShown(&parent);
+
+    // Check that child window does not obscure parent window
+    QVERIFY(!parent.visibleRegion().subtracted(child.visibleRegion()).isEmpty());
+
+    // Native child widget should share parent's backing store
+    QVERIFY(0 != backingStore(parent));
+    QVERIFY(0 == backingStore(child));
+
+    // Make child widget full screen
+    child.setWindowFlags((child.windowFlags() | Qt::Window) ^ Qt::SubWindow);
+    child.setWindowState(child.windowState() | Qt::WindowFullScreen);
+    child.show();
+    QTest::qWaitForWindowShown(&child);
+
+    // Check that child window obscures parent window
+    QVERIFY(parent.visibleRegion().subtracted(child.visibleRegion()).isEmpty());
+
+    // Now that extent of child widget goes beyond parent's extent,
+    // a new backing store should be created for the child widget.
+    QVERIFY(0 != backingStore(child));
+
+    // Parent is obscured, therefore its backing store should be destroyed
+    QVERIFY(0 == backingStore(parent));
+
+    // Disable full screen
+    child.setWindowFlags(child.windowFlags() ^ (Qt::Window | Qt::SubWindow));
+    child.setWindowState(child.windowState() ^ Qt::WindowFullScreen);
+    child.show();
+    QTest::qWaitForWindowShown(&child);
+
+    // Check that parent is now visible again
+    QVERIFY(!parent.visibleRegion().subtracted(child.visibleRegion()).isEmpty());
+
+    // Native child widget should once again share parent's backing store
+    QVERIFY(0 != backingStore(parent));
+    QEXPECT_FAIL("", "QTBUG-10643", Continue);
+    QVERIFY(0 == backingStore(child));
+    }
+
+    // 5. Native child widget which is made full-screen
+    {
+    QWidget parent;
+    parent.setGeometry(0, 0, 100, 100);
+    parent.setAutoFillBackground(true);
+    parent.setPalette(Qt::red);
+
+    QWidget child(&parent);
+    child.setAutoFillBackground(true);
+    child.setPalette(Qt::blue);
+
+    QVBoxLayout layout(&parent);
+    layout.setContentsMargins(10, 10, 10, 10);
+    layout.addWidget(&child);
+    parent.setLayout(&layout);
+
+    child.winId();
+
+    parent.show();
+    QTest::qWaitForWindowShown(&parent);
+
+    // Check that child window does not obscure parent window
+    QVERIFY(!parent.visibleRegion().subtracted(child.visibleRegion()).isEmpty());
+
+    // Native child widget should share parent's backing store
+    QVERIFY(0 != backingStore(parent));
+    QVERIFY(0 == backingStore(child));
+
+    // Make child widget full screen
+    child.setWindowFlags((child.windowFlags() | Qt::Window) ^ Qt::SubWindow);
+    child.setWindowState(child.windowState() | Qt::WindowFullScreen);
+    child.show();
+    QTest::qWaitForWindowShown(&child);
+
+    // Ensure that 'window hidden' event is received by parent
+    qApp->processEvents();
+
+    // Check that child window obscures parent window
+    QVERIFY(parent.visibleRegion().subtracted(child.visibleRegion()).isEmpty());
+
+    // Now that extent of child widget goes beyond parent's extent,
+    // a new backing store should be created for the child widget.
+    QVERIFY(0 != backingStore(child));
+
+    // Parent is obscured, therefore its backing store should be destroyed
+    QEXPECT_FAIL("", "QTBUG-10643", Continue);
+    QVERIFY(0 == backingStore(parent));
+
+    // Disable full screen
+    child.setWindowFlags(child.windowFlags() ^ (Qt::Window | Qt::SubWindow));
+    child.setWindowState(child.windowState() ^ Qt::WindowFullScreen);
+    child.show();
+    QTest::qWaitForWindowShown(&child);
+
+    // Check that parent is now visible again
+    QVERIFY(!parent.visibleRegion().subtracted(child.visibleRegion()).isEmpty());
+
+    // Native child widget should once again share parent's backing store
+    QVERIFY(0 != backingStore(parent));
+    QEXPECT_FAIL("", "QTBUG-10643", Continue);
+    QVERIFY(0 == backingStore(child));
+    }
+}
+
+#undef WAIT_AND_VERIFY
+
 void tst_QWidget::rectOutsideCoordinatesLimit_task144779()
 {
 #ifdef Q_OS_WINCE_WM
@@ -10000,15 +10245,12 @@
 public:
     void deleteBackingStore()
     {
-        if (static_cast<QWidgetPrivate*>(d_ptr.data())->maybeBackingStore()) {
-            delete static_cast<QWidgetPrivate*>(d_ptr.data())->topData()->backingStore;
-            static_cast<QWidgetPrivate*>(d_ptr.data())->topData()->backingStore = 0;
-        }
+        static_cast<QWidgetPrivate*>(d_ptr.data())->topData()->backingStore.destroy();
     }
     void enableBackingStore()
     {
         if (!static_cast<QWidgetPrivate*>(d_ptr.data())->maybeBackingStore()) {
-            static_cast<QWidgetPrivate*>(d_ptr.data())->topData()->backingStore = new QWidgetBackingStore(this);
+            static_cast<QWidgetPrivate*>(d_ptr.data())->topData()->backingStore.create(this);
             static_cast<QWidgetPrivate*>(d_ptr.data())->invalidateBuffer(this->rect());
             repaint();
         }
@@ -10105,5 +10347,118 @@
 #endif
 }
 
+void tst_QWidget::childAt()
+{
+    QWidget parent(0, Qt::FramelessWindowHint);
+    parent.resize(200, 200);
+
+    QWidget *child = new QWidget(&parent);
+    child->setPalette(Qt::red);
+    child->setAutoFillBackground(true);
+    child->setGeometry(20, 20, 160, 160);
+
+    QWidget *grandChild = new QWidget(child);
+    grandChild->setPalette(Qt::blue);
+    grandChild->setAutoFillBackground(true);
+    grandChild->setGeometry(-20, -20, 220, 220);
+
+    QVERIFY(!parent.childAt(19, 19));
+    QVERIFY(!parent.childAt(180, 180));
+    QCOMPARE(parent.childAt(20, 20), grandChild);
+    QCOMPARE(parent.childAt(179, 179), grandChild);
+
+    grandChild->setAttribute(Qt::WA_TransparentForMouseEvents);
+    QCOMPARE(parent.childAt(20, 20), child);
+    QCOMPARE(parent.childAt(179, 179), child);
+    grandChild->setAttribute(Qt::WA_TransparentForMouseEvents, false);
+
+    child->setMask(QRect(50, 50, 60, 60));
+
+    QVERIFY(!parent.childAt(69, 69));
+    QVERIFY(!parent.childAt(130, 130));
+    QCOMPARE(parent.childAt(70, 70), grandChild);
+    QCOMPARE(parent.childAt(129, 129), grandChild);
+
+    child->setAttribute(Qt::WA_MouseNoMask);
+    QCOMPARE(parent.childAt(69, 69), grandChild);
+    QCOMPARE(parent.childAt(130, 130), grandChild);
+    child->setAttribute(Qt::WA_MouseNoMask, false);
+
+    grandChild->setAttribute(Qt::WA_TransparentForMouseEvents);
+    QCOMPARE(parent.childAt(70, 70), child);
+    QCOMPARE(parent.childAt(129, 129), child);
+    grandChild->setAttribute(Qt::WA_TransparentForMouseEvents, false);
+
+    grandChild->setMask(QRect(80, 80, 40, 40));
+
+    QCOMPARE(parent.childAt(79, 79), child);
+    QCOMPARE(parent.childAt(120, 120), child);
+    QCOMPARE(parent.childAt(80, 80), grandChild);
+    QCOMPARE(parent.childAt(119, 119), grandChild);
+
+    grandChild->setAttribute(Qt::WA_MouseNoMask);
+
+    QCOMPARE(parent.childAt(79, 79), grandChild);
+    QCOMPARE(parent.childAt(120, 120), grandChild);
+}
+
+#ifdef Q_WS_MAC
+void tst_QWidget::childAt_unifiedToolBar()
+{
+    QLabel *label = new QLabel(QLatin1String("foo"));
+    QToolBar *toolBar = new QToolBar;
+    toolBar->addWidget(new QLabel("dummy"));
+    toolBar->addWidget(label);
+
+    QMainWindow mainWindow;
+    mainWindow.addToolBar(toolBar);
+    mainWindow.show();
+
+    // Calculate the top-left corner of the tool bar and the label (in mainWindow's coordinates).
+    QPoint labelTopLeft = label->mapTo(&mainWindow, QPoint());
+    QPoint toolBarTopLeft = toolBar->mapTo(&mainWindow, QPoint());
+
+    QCOMPARE(mainWindow.childAt(toolBarTopLeft), static_cast<QWidget *>(toolBar));
+    QCOMPARE(mainWindow.childAt(labelTopLeft), static_cast<QWidget *>(label));
+
+    // Enable unified tool bars.
+    mainWindow.setUnifiedTitleAndToolBarOnMac(true);
+    QTest::qWait(50);
+
+    // The tool bar is now in the "non-client" area of QMainWindow, i.e.
+    // outside the mainWindow's rect(), and since mapTo et al. doesn't work
+    // in that case (see commit 35667fd45ada49269a5987c235fdedfc43e92bb8),
+    // we use mapToGlobal/mapFromGlobal to re-calculate the corners.
+    QPoint oldToolBarTopLeft = toolBarTopLeft;
+    toolBarTopLeft = mainWindow.mapFromGlobal(toolBar->mapToGlobal(QPoint()));
+    QVERIFY(toolBarTopLeft != oldToolBarTopLeft);
+    QVERIFY(toolBarTopLeft.y() < 0);
+    labelTopLeft = mainWindow.mapFromGlobal(label->mapToGlobal(QPoint()));
+
+    QCOMPARE(mainWindow.childAt(toolBarTopLeft), static_cast<QWidget *>(toolBar));
+    QCOMPARE(mainWindow.childAt(labelTopLeft), static_cast<QWidget *>(label));
+}
+
+#ifdef QT_MAC_USE_COCOA
+void tst_QWidget::taskQTBUG_11373()
+{
+    QMainWindow * myWindow = new QMainWindow();
+    QWidget * center = new QWidget();
+    myWindow -> setCentralWidget(center);
+    QWidget * drawer = new QWidget(myWindow, Qt::Drawer);
+    drawer -> hide();
+    QCOMPARE(drawer->isVisible(), false);
+    myWindow -> show();
+    myWindow -> raise();
+    // The drawer shouldn't be visible now.
+    QCOMPARE(drawer->isVisible(), false);
+    myWindow -> setWindowState(Qt::WindowFullScreen);
+    myWindow -> setWindowState(Qt::WindowNoState);
+    // The drawer should still not be visible, since we haven't shown it.
+    QCOMPARE(drawer->isVisible(), false);
+}
+#endif // QT_MAC_USE_COCOA
+#endif
+
 QTEST_MAIN(tst_QWidget)
 #include "tst_qwidget.moc"