[Ktutorial-commits] SF.net SVN: ktutorial:[280] trunk/ktutorial/ktutorial-library
Status: Alpha
Brought to you by:
danxuliu
From: <dan...@us...> - 2010-10-29 19:45:23
|
Revision: 280 http://ktutorial.svn.sourceforge.net/ktutorial/?rev=280&view=rev Author: danxuliu Date: 2010-10-29 19:45:17 +0000 (Fri, 29 Oct 2010) Log Message: ----------- Fix several bugs in WindowOnTopEnforcer behaviour. Modified Paths: -------------- trunk/ktutorial/ktutorial-library/src/view/WindowOnTopEnforcer.cpp trunk/ktutorial/ktutorial-library/src/view/WindowOnTopEnforcer.h trunk/ktutorial/ktutorial-library/tests/view/WindowOnTopEnforcerTest.cpp Modified: trunk/ktutorial/ktutorial-library/src/view/WindowOnTopEnforcer.cpp =================================================================== --- trunk/ktutorial/ktutorial-library/src/view/WindowOnTopEnforcer.cpp 2010-10-29 19:41:20 UTC (rev 279) +++ trunk/ktutorial/ktutorial-library/src/view/WindowOnTopEnforcer.cpp 2010-10-29 19:45:17 UTC (rev 280) @@ -46,40 +46,72 @@ this, SLOT(handleWindowHidden(QWidget*))); } +//private: + +bool WindowOnTopEnforcer::isAncestorOf(QObject* object, + QObject* childObject) const { + if (childObject->parent() == 0) { + return false; + } + + if (childObject->parent() == object) { + return true; + } + + return isAncestorOf(object, childObject->parent()); +} + +void WindowOnTopEnforcer::reparentWindowTo(QWidget* window, + QWidget* parent) const { + //When a widget is reparented it is hidden and its window flags are cleared, + //so they must be restored and the widget shown again + Qt::WindowFlags flags = window->windowFlags(); + window->setParent(parent); + window->setWindowFlags(flags); + window->show(); +} + //private slots: void WindowOnTopEnforcer::handleWindowShown(QWidget* window) { Q_ASSERT(window); - if (!window->isModal()) { + if (!window->isModal() || mParentStack.contains(window)) { return; } + //If the modal window shown is ancestor of any of the modal windows already + //shown just insert it at the appropriate place in the parent stack. + for (int i=1; i<mParentStack.size(); ++i) { + QWidget* widgetInStack = mParentStack[i]; + + if (!isAncestorOf(widgetInStack, window) && + isAncestorOf(window, widgetInStack)) { + mParentStack.insert(i, window); + return; + } + } + mParentStack.push(window); - //When a widget is reparented it is hidden and its window flags are cleared, - //so they must be restored and the widget shown again - Qt::WindowFlags flags = mWidgetToKeepOnTop->windowFlags(); - mWidgetToKeepOnTop->setParent(window); - mWidgetToKeepOnTop->setWindowFlags(flags); - mWidgetToKeepOnTop->show(); + reparentWindowTo(mWidgetToKeepOnTop, window); } void WindowOnTopEnforcer::handleWindowHidden(QWidget* window) { Q_ASSERT(window); - if (!window->isModal()) { + if (!window->isModal() || !mParentStack.contains(window)) { return; } + if (window != mParentStack.top()) { + mParentStack.remove(mParentStack.indexOf(window)); + return; + } + mParentStack.pop(); - //When a widget is reparented it is hidden and its window flags are cleared - //so they must be restored and the widget shown again - Qt::WindowFlags flags = mWidgetToKeepOnTop->windowFlags(); - mWidgetToKeepOnTop->setParent(mParentStack.top()); - mWidgetToKeepOnTop->setWindowFlags(flags); - mWidgetToKeepOnTop->show(); + reparentWindowTo(mWidgetToKeepOnTop, mParentStack.top()); } } Modified: trunk/ktutorial/ktutorial-library/src/view/WindowOnTopEnforcer.h =================================================================== --- trunk/ktutorial/ktutorial-library/src/view/WindowOnTopEnforcer.h 2010-10-29 19:41:20 UTC (rev 279) +++ trunk/ktutorial/ktutorial-library/src/view/WindowOnTopEnforcer.h 2010-10-29 19:45:17 UTC (rev 280) @@ -29,10 +29,10 @@ namespace view { /** - * Utility class to avoid windows being blocked by modal dialogs. - * When a modal dialog is shown, the widget to keep on top is reparented to the - * shown dialog. When the modal dialog is hidden, the widget is reparented to - * its previous parent. + * Utility class to avoid windows being blocked by modal widgets. + * When a modal widget is shown, the widget to keep on top is reparented to the + * shown widget (if necessary). When the modal widget is hidden, the widget is + * reparented to its first previous ancestor still visible. */ class WindowOnTopEnforcer: public QObject { Q_OBJECT @@ -64,14 +64,36 @@ /** * A stack with the parents of the widget to keep on top. * It is used to restore the previous parent when the latest one is hidden. + * It always contains, at least, the base window as its first item. */ QStack<QWidget*> mParentStack; + /** + * Checks whether the given object is ancestor of the given child object. + * + * @param object The object to check if it is the ancestor. + * @param childObject The child object. + * @return True if it is ancestor, false otherwise. + */ + bool isAncestorOf(QObject* object, QObject* childObject) const; + + /** + * Reparents the window to the given parent, preserving its window flags (as + * they are cleared when a new parent is set). + * + * @param window The window to reparent. + * @param parent The new parent. + */ + void reparentWindowTo(QWidget* window, QWidget* parent) const; + private Q_SLOTS: /** - * Reparents the widget to keep on top to the window if it is a modal - * dialog. + * Reparents the widget to keep on top to the window if it is the top most + * modal widget. + * If the window is modal but is parent of the top most modal widget the + * window is just inserted at the appropriate place in the stack. + * If the window is modal but it is already in the stack nothing is done. * * @param window The window that has been shown. */ @@ -79,7 +101,10 @@ /** * Reparents the widget to keep on top to its previous parent if the hidden - * window is a modal dialog. + * window is the top most modal widget. + * If the window is modal but is parent of the top most modal widget the + * window is just removed from the stack. + * If the window is modal but it is not part of the stack nothing is done. * * @param window The window that has been hidden. */ Modified: trunk/ktutorial/ktutorial-library/tests/view/WindowOnTopEnforcerTest.cpp =================================================================== --- trunk/ktutorial/ktutorial-library/tests/view/WindowOnTopEnforcerTest.cpp 2010-10-29 19:41:20 UTC (rev 279) +++ trunk/ktutorial/ktutorial-library/tests/view/WindowOnTopEnforcerTest.cpp 2010-10-29 19:45:17 UTC (rev 280) @@ -43,6 +43,22 @@ void testNestedModalDialogOnChildWindow(); + void testModalWidget(); + + void testModalWidgetSiblingOfParent(); + + void testShowModalWidgetTwice(); + + void testHideNestedModalWidgetsInOrder(); + + void testHideNestedModalWidgetsInReverseOrder(); + + void testShowNestedModalWidgetsInOrder(); + + void testShowNestedModalWidgetsInReverseOrder(); + + void testShowNestedModalWidgetsInMixedOrder(); + private: void queueAssertParent(QWidget* widget, QWidget* parent, int timeToWait); @@ -72,6 +88,14 @@ assertWindow(windowToKeepOnTop, window); + window->hide(); + + assertWindow(windowToKeepOnTop, window); + + window->show(); + + assertWindow(windowToKeepOnTop, window); + delete window; QVERIFY(!windowToKeepOnTop); @@ -94,6 +118,14 @@ assertWindow(windowToKeepOnTop, window); + childWindow->hide(); + + assertWindow(windowToKeepOnTop, window); + + childWindow->show(); + + assertWindow(windowToKeepOnTop, window); + delete childWindow; assertWindow(windowToKeepOnTop, window); @@ -316,6 +348,429 @@ QVERIFY(!windowToKeepOnTop); } +void WindowOnTopEnforcerTest::testModalWidget() { + QWidget* window = new QWidget(); + window->show(); + + QPointer<QWidget> windowToKeepOnTop = new QWidget(window); + windowToKeepOnTop->setWindowFlags(Qt::Window); + windowToKeepOnTop->show(); + + WindowOnTopEnforcer* enforcer = new WindowOnTopEnforcer(windowToKeepOnTop); + enforcer->setBaseWindow(window); + + QWidget* modalWidget = new QWidget(window); + modalWidget->setWindowFlags(Qt::Window); + modalWidget->setWindowModality(Qt::ApplicationModal); + modalWidget->show(); + + assertWindow(windowToKeepOnTop, modalWidget); + + modalWidget->hide(); + + assertWindow(windowToKeepOnTop, window); + + modalWidget->show(); + + assertWindow(windowToKeepOnTop, modalWidget); + + delete modalWidget; + + assertWindow(windowToKeepOnTop, window); + + delete window; + + QVERIFY(!windowToKeepOnTop); +} + +//This test may cause next test to fail (with a segmentation fault) when the +//modal widget in the next test is shown. +//The crash seems to be random, but has nothing to do (checked by isolating the +//code that crashes) with KTutorial classes. +//Under some unknown circumstances (I have yet to see the crash when GDB is +//recording the execution to analyze it properly :( ), "delete window" in +//testModalWidgetSiblingOfParent method makes qt_last_mouse_receiver (declared +//in qapplication_x11.cpp) to get the address of the window (or one of its child +//windows) when it is deleted. +//As the address is no longer valid, when that variable is used internally in +//the next modalWidget->show() ugly things happen. +//Hopefully I'll get enough information about this weird behavior to fill a bug. +void WindowOnTopEnforcerTest::testModalWidgetSiblingOfParent() { + QWidget* window = new QWidget(); + window->show(); + + QPointer<QWidget> windowToKeepOnTop = new QWidget(window); + windowToKeepOnTop->setWindowFlags(Qt::Window); + windowToKeepOnTop->show(); + + WindowOnTopEnforcer* enforcer = new WindowOnTopEnforcer(windowToKeepOnTop); + enforcer->setBaseWindow(window); + + QWidget* siblingModalWidget = new QWidget(window); + siblingModalWidget->setWindowFlags(Qt::Window); + siblingModalWidget->setWindowModality(Qt::ApplicationModal); + siblingModalWidget->show(); + + QWidget* modalWidget = new QWidget(window); + modalWidget->setWindowFlags(Qt::Window); + modalWidget->setWindowModality(Qt::ApplicationModal); + modalWidget->show(); + + assertWindow(windowToKeepOnTop, modalWidget); + + modalWidget->hide(); + + assertWindow(windowToKeepOnTop, siblingModalWidget); + + modalWidget->show(); + + assertWindow(windowToKeepOnTop, modalWidget); + + delete siblingModalWidget; + + assertWindow(windowToKeepOnTop, modalWidget); + + delete modalWidget; + + assertWindow(windowToKeepOnTop, window); + + delete window; + + QVERIFY(!windowToKeepOnTop); +} + +//If a step contains a WaitForWindow and the step it changes to adds two +//WaitForWindows or more in its setup, the WindowOnTopEnforcer will get the Show +//event for the new window twice. +//This is a pretty strange and complex phenomenon that involves some Qt +//internals (http://bugreports.qt.nokia.com/browse/QTBUG-14651): +//Each object contains an internal list with its event filters. When a new +//filter is installed, it is prepended to the list. This list is traversed when +//the events for the object are being filtered; the first filter in the list is +//executed, then the second filter, then the third... and so on until there are +//no more filters in the list (or some filter stopped the event from being +//handled further). +//If a new filter is installed in an object while an event for that object is +//being filtered, the new filter will be prepended, the list will be modified, +//and the filter being executed will now take the next position in the list. +//When the next filter to be executed is fetch it will be the same filter that +//has just been executed. +//When the window that WaitForWindow is waiting for is shown, WaitForWindow will +//change to the next step in the tutorial. When this new step is set up, it adds +//two new WaitForWindows. The WaitForWindows will install a filter for each +//widget, which includes the window that has been shown. So, although it happens +//deep in the call stack, two event filters are installed while an event filter +//for that object is being executed. +//But, why has it to be two and not just one? And why does it affect +//WindowOnTopEnforcer if they are installed in a WaitForWindow? +//The reason is that the filter of the WaitForWindow that changes to the next +//step is the last filter executed, just after the filter of the +//WindowOnTopEnforcer. When the tutorial changes to the next step, it deletes +//the WaitForWindow of the deactivated step, thus removing its filter. Then it +//activates the next step, which causes the two WaitForWindow to be setup, and +//thus two filters are prepended to the filter list. So, as the filter after +//the WindowOnTopEnforcer filter is removed and two new ones are prepended, the +//WindowOnTopEnforcer filter ends one place after the one that was being +//executed. When the execution of that one ends, the end of the list was not +//reached yet, so the loop executes the next filter, and thus causes the +//WindowOnTopEnforcer filter to be executed again. +//And that's all. Strange? I know, I was the one who had to debug it :P +//This test is a synthesized version of all the things explained above. +void WindowOnTopEnforcerTest::testShowModalWidgetTwice() { + QWidget* window = new QWidget(); + window->show(); + + QPointer<QWidget> windowToKeepOnTop = new QWidget(window); + windowToKeepOnTop->setWindowFlags(Qt::Window); + windowToKeepOnTop->show(); + + WindowOnTopEnforcer* enforcer = new WindowOnTopEnforcer(windowToKeepOnTop); + enforcer->setBaseWindow(window); + + QWidget* modalWidget = new QWidget(window); + modalWidget->setWindowFlags(Qt::Window); + modalWidget->setWindowModality(Qt::ApplicationModal); + modalWidget->show(); + + QShowEvent showEvent; + QCoreApplication::sendEvent(modalWidget, &showEvent); + + assertWindow(windowToKeepOnTop, modalWidget); + + modalWidget->hide(); + + assertWindow(windowToKeepOnTop, window); + + delete window; + + QVERIFY(!windowToKeepOnTop); +} + +void WindowOnTopEnforcerTest::testHideNestedModalWidgetsInOrder() { + QWidget* window = new QWidget(); + window->show(); + + QPointer<QWidget> windowToKeepOnTop = new QWidget(window); + windowToKeepOnTop->setWindowFlags(Qt::Window); + windowToKeepOnTop->show(); + + WindowOnTopEnforcer* enforcer = new WindowOnTopEnforcer(windowToKeepOnTop); + enforcer->setBaseWindow(window); + + QWidget* modalWidget = new QWidget(window); + modalWidget->setWindowFlags(Qt::Window); + modalWidget->setWindowModality(Qt::ApplicationModal); + modalWidget->show(); + + QWidget* nestedModalWidget = new QWidget(modalWidget); + nestedModalWidget->setWindowFlags(Qt::Window); + nestedModalWidget->setWindowModality(Qt::ApplicationModal); + nestedModalWidget->show(); + + assertWindow(windowToKeepOnTop, nestedModalWidget); + + nestedModalWidget->hide(); + + assertWindow(windowToKeepOnTop, modalWidget); + + modalWidget->hide(); + + assertWindow(windowToKeepOnTop, window); + + window->hide(); + + assertWindow(windowToKeepOnTop, window); + + delete window; + + QVERIFY(!windowToKeepOnTop); +} + +void WindowOnTopEnforcerTest::testHideNestedModalWidgetsInReverseOrder() { + QWidget* window = new QWidget(); + window->show(); + + QPointer<QWidget> windowToKeepOnTop = new QWidget(window); + windowToKeepOnTop->setWindowFlags(Qt::Window); + windowToKeepOnTop->show(); + + WindowOnTopEnforcer* enforcer = new WindowOnTopEnforcer(windowToKeepOnTop); + enforcer->setBaseWindow(window); + + QWidget* modalWidget = new QWidget(window); + modalWidget->setWindowFlags(Qt::Window); + modalWidget->setWindowModality(Qt::ApplicationModal); + modalWidget->show(); + + QWidget* nestedModalWidget = new QWidget(modalWidget); + nestedModalWidget->setWindowFlags(Qt::Window); + nestedModalWidget->setWindowModality(Qt::ApplicationModal); + nestedModalWidget->show(); + + assertWindow(windowToKeepOnTop, nestedModalWidget); + + window->hide(); + + assertWindow(windowToKeepOnTop, nestedModalWidget); + + modalWidget->hide(); + + assertWindow(windowToKeepOnTop, nestedModalWidget); + + nestedModalWidget->hide(); + + assertWindow(windowToKeepOnTop, window); + + delete window; + + QVERIFY(!windowToKeepOnTop); +} + +void WindowOnTopEnforcerTest::testShowNestedModalWidgetsInOrder() { + QWidget* window = new QWidget(); + window->show(); + + QPointer<QWidget> windowToKeepOnTop = new QWidget(window); + windowToKeepOnTop->setWindowFlags(Qt::Window); + windowToKeepOnTop->show(); + + WindowOnTopEnforcer* enforcer = new WindowOnTopEnforcer(windowToKeepOnTop); + enforcer->setBaseWindow(window); + + QWidget* modalWidget = new QWidget(window); + modalWidget->setWindowFlags(Qt::Window); + modalWidget->setWindowModality(Qt::ApplicationModal); + modalWidget->show(); + + QWidget* nestedModalWidget = new QWidget(modalWidget); + nestedModalWidget->setWindowFlags(Qt::Window); + nestedModalWidget->setWindowModality(Qt::ApplicationModal); + nestedModalWidget->show(); + + assertWindow(windowToKeepOnTop, nestedModalWidget); + + nestedModalWidget->hide(); + modalWidget->hide(); + window->hide(); + + window->show(); + + assertWindow(windowToKeepOnTop, window); + + modalWidget->show(); + + assertWindow(windowToKeepOnTop, modalWidget); + + nestedModalWidget->show(); + + assertWindow(windowToKeepOnTop, nestedModalWidget); + + delete nestedModalWidget; + + assertWindow(windowToKeepOnTop, modalWidget); + + delete modalWidget; + + assertWindow(windowToKeepOnTop, window); + + delete window; + + QVERIFY(!windowToKeepOnTop); +} + +void WindowOnTopEnforcerTest::testShowNestedModalWidgetsInReverseOrder() { + QWidget* window = new QWidget(); + window->show(); + + QPointer<QWidget> windowToKeepOnTop = new QWidget(window); + windowToKeepOnTop->setWindowFlags(Qt::Window); + windowToKeepOnTop->show(); + + WindowOnTopEnforcer* enforcer = new WindowOnTopEnforcer(windowToKeepOnTop); + enforcer->setBaseWindow(window); + + QWidget* modalWidget = new QWidget(window); + modalWidget->setWindowFlags(Qt::Window); + modalWidget->setWindowModality(Qt::ApplicationModal); + modalWidget->show(); + + QWidget* nestedModalWidget = new QWidget(modalWidget); + nestedModalWidget->setWindowFlags(Qt::Window); + nestedModalWidget->setWindowModality(Qt::ApplicationModal); + nestedModalWidget->show(); + + assertWindow(windowToKeepOnTop, nestedModalWidget); + + window->hide(); + modalWidget->hide(); + nestedModalWidget->hide(); + + nestedModalWidget->show(); + + assertWindow(windowToKeepOnTop, nestedModalWidget); + + modalWidget->show(); + + assertWindow(windowToKeepOnTop, nestedModalWidget); + + window->show(); + + assertWindow(windowToKeepOnTop, nestedModalWidget); + + delete nestedModalWidget; + + assertWindow(windowToKeepOnTop, modalWidget); + + delete modalWidget; + + assertWindow(windowToKeepOnTop, window); + + delete window; + + QVERIFY(!windowToKeepOnTop); +} + +void WindowOnTopEnforcerTest::testShowNestedModalWidgetsInMixedOrder() { + QWidget* window = new QWidget(); + window->show(); + + QPointer<QWidget> windowToKeepOnTop = new QWidget(window); + windowToKeepOnTop->setWindowFlags(Qt::Window); + windowToKeepOnTop->show(); + + WindowOnTopEnforcer* enforcer = new WindowOnTopEnforcer(windowToKeepOnTop); + enforcer->setBaseWindow(window); + + QWidget* modalWidget = new QWidget(window); + modalWidget->setWindowFlags(Qt::Window); + modalWidget->setWindowModality(Qt::ApplicationModal); + modalWidget->show(); + + QWidget* nestedModalWidget = new QWidget(modalWidget); + nestedModalWidget->setWindowFlags(Qt::Window); + nestedModalWidget->setWindowModality(Qt::ApplicationModal); + nestedModalWidget->show(); + + QWidget* nestedNestedModalWidget = new QWidget(nestedModalWidget); + nestedNestedModalWidget->setWindowFlags(Qt::Window); + nestedNestedModalWidget->setWindowModality(Qt::ApplicationModal); + nestedNestedModalWidget->show(); + + QWidget* nestedNestedNestedModalWidget = + new QWidget(nestedNestedModalWidget); + nestedNestedNestedModalWidget->setWindowFlags(Qt::Window); + nestedNestedNestedModalWidget->setWindowModality(Qt::ApplicationModal); + nestedNestedNestedModalWidget->show(); + + assertWindow(windowToKeepOnTop, nestedNestedNestedModalWidget); + + window->hide(); + modalWidget->hide(); + nestedModalWidget->hide(); + nestedNestedModalWidget->hide(); + nestedNestedNestedModalWidget->hide(); + + nestedNestedModalWidget->show(); + + assertWindow(windowToKeepOnTop, nestedNestedModalWidget); + + modalWidget->show(); + + assertWindow(windowToKeepOnTop, nestedNestedModalWidget); + + nestedNestedNestedModalWidget->show(); + + assertWindow(windowToKeepOnTop, nestedNestedNestedModalWidget); + + window->show(); + + assertWindow(windowToKeepOnTop, nestedNestedNestedModalWidget); + + nestedModalWidget->show(); + + assertWindow(windowToKeepOnTop, nestedNestedNestedModalWidget); + + delete nestedNestedNestedModalWidget; + + assertWindow(windowToKeepOnTop, nestedNestedModalWidget); + + delete nestedNestedModalWidget; + + assertWindow(windowToKeepOnTop, nestedModalWidget); + + delete nestedModalWidget; + + assertWindow(windowToKeepOnTop, modalWidget); + + delete modalWidget; + + assertWindow(windowToKeepOnTop, window); + + delete window; + + QVERIFY(!windowToKeepOnTop); +} + /////////////////////////////////// Helpers //////////////////////////////////// //The dialogs are modal, so they won't return to the test code until they are This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |