From b627dd2c24c5962c1e8f9d72bcbe9ebdfe6dfceb Mon Sep 17 00:00:00 2001 From: Felix Bourbonnais <thor400.75@gmail.com> Date: Tue, 10 May 2016 10:06:53 -0400 Subject: [PATCH] QMenuBar: nested parenting fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit QMenuBar now receives a parent changed event for each of its parent, grand-parent, ... This fixes a crash caused by an invalid QWidget pointer and makes sure the keyboard shortcuts events are relayed to the menu bar in all parenting/re-parenting cases by installing an event filter on each parent Task-number: QTBUG-53205 Change-Id: I419e6cbc52e28a67fb08a848a7161b4cb8ae4ae5 Reviewed-by: Gabriel de Dietrich <gabriel.dedietrich@qt.io> Reviewed-by: Błażej Szczygieł <spaz16@wp.pl> --- src/widgets/widgets/qmenubar.cpp | 55 +++++++++-------- src/widgets/widgets/qmenubar_p.h | 3 +- .../widgets/widgets/qmenubar/tst_qmenubar.cpp | 59 +++++++++++++++++++ 3 files changed, 92 insertions(+), 25 deletions(-) diff --git a/src/widgets/widgets/qmenubar.cpp b/src/widgets/widgets/qmenubar.cpp index cca0853a8fd..3f42a07f396 100644 --- a/src/widgets/widgets/qmenubar.cpp +++ b/src/widgets/widgets/qmenubar.cpp @@ -706,7 +706,6 @@ void QMenuBarPrivate::init() } #endif q->setBackgroundRole(QPalette::Button); - oldWindow = oldParent = 0; handleReparent(); q->setMouseTracking(q->style()->styleHint(QStyle::SH_MenuBar_MouseTracking, 0, q)); @@ -1330,30 +1329,41 @@ void QMenuBarPrivate::handleReparent() { Q_Q(QMenuBar); QWidget *newParent = q->parentWidget(); - //Note: if parent is reparented, then window may change even if parent doesn't - // we need to install an event filter on parent, and remove the old one - - if (oldParent != newParent) { - if (oldParent) - oldParent->removeEventFilter(q); - if (newParent) - newParent->installEventFilter(q); + //Note: if parent is reparented, then window may change even if parent doesn't. + // We need to install an avent filter on each parent up to the parent that is + // also a window (for shortcuts) + QWidget *newWindow = newParent ? newParent->window() : Q_NULLPTR; + + QVector<QPointer<QWidget> > newParents; + // Remove event filters on ex-parents, keep them on still-parents + // The parents are always ordered in the vector + foreach (const QPointer<QWidget> &w, oldParents) { + if (w) { + if (newParent == w) { + newParents.append(w); + if (newParent != newWindow) //stop at the window + newParent = newParent->parentWidget(); + } else { + w->removeEventFilter(q); + } + } } - //we also need event filter on top-level (for shortcuts) - QWidget *newWindow = newParent ? newParent->window() : 0; - - if (oldWindow != newWindow) { - if (oldParent && oldParent != oldWindow) - oldWindow->removeEventFilter(q); - - if (newParent && newParent != newWindow) - newWindow->installEventFilter(q); + // At this point, newParent is the next one to be added to newParents + while (newParent && newParent != newWindow) { + //install event filters all the way up to (excluding) the window + newParents.append(newParent); + newParent->installEventFilter(q); + newParent = newParent->parentWidget(); } - oldParent = newParent; - oldWindow = newWindow; + if (newParent && newWindow) { + // Install the event filter on the window + newParents.append(newParent); + newParent->installEventFilter(q); + } + oldParents = newParents; if (platformMenuBar) { if (newWindow) { @@ -1465,10 +1475,9 @@ bool QMenuBar::event(QEvent *e) bool QMenuBar::eventFilter(QObject *object, QEvent *event) { Q_D(QMenuBar); - if (object == parent() && object) { - if (event->type() == QEvent::ParentChange) //GrandparentChange + if (object && (event->type() == QEvent::ParentChange)) //GrandparentChange d->handleReparent(); - } + if (object == d->leftWidget || object == d->rightWidget) { switch (event->type()) { case QEvent::ShowToParent: diff --git a/src/widgets/widgets/qmenubar_p.h b/src/widgets/widgets/qmenubar_p.h index ee615e71f34..7ef696f50ec 100644 --- a/src/widgets/widgets/qmenubar_p.h +++ b/src/widgets/widgets/qmenubar_p.h @@ -128,8 +128,7 @@ public: // reparenting void handleReparent(); - QWidget *oldParent; - QWidget *oldWindow; + QVector<QPointer<QWidget> > oldParents; QList<QAction*> hiddenActions; //default action diff --git a/tests/auto/widgets/widgets/qmenubar/tst_qmenubar.cpp b/tests/auto/widgets/widgets/qmenubar/tst_qmenubar.cpp index f787d73a027..b2d15fffef4 100644 --- a/tests/auto/widgets/widgets/qmenubar/tst_qmenubar.cpp +++ b/tests/auto/widgets/widgets/qmenubar/tst_qmenubar.cpp @@ -132,10 +132,12 @@ private slots: void closeOnSecondClickAndOpenOnThirdClick(); void cornerWidgets_data(); void cornerWidgets(); + void taskQTBUG53205_crashReparentNested(); protected slots: void onSimpleActivated( QAction*); void onComplexActionTriggered(); + void slotForTaskQTBUG53205(); private: TestMenu initSimpleMenuBar(QMenuBar *mb); @@ -148,6 +150,7 @@ private: QAction* m_lastSimpleAcceleratorId; int m_simpleActivatedCount; int m_complexTriggerCount[int('k')]; + QMenuBar* taskQTBUG53205MenuBar; }; // Testing get/set functions @@ -1438,5 +1441,61 @@ void tst_QMenuBar::cornerWidgets() delete cornerLabel; } + +void tst_QMenuBar::taskQTBUG53205_crashReparentNested() +{ + // This test was largely inspired by the test case submitted for the bug + QMainWindow mainWindow; + mainWindow.resize(300, 200); + centerOnScreen(&mainWindow); + const TestMenu testMenus = initWindowWithComplexMenuBar(mainWindow); + QApplication::setActiveWindow(&mainWindow); + + // they can't be windows + QWidget hiddenParent(&mainWindow, 0); + //this one is going to be moved around + QWidget movingParent(&hiddenParent, 0); + + //set up the container widget + QWidget containerWidget(&movingParent,0); + + //set the new parent, a window + QScopedPointer<QWidget> windowedParent; + windowedParent.reset(new QWidget(Q_NULLPTR, Qt::WindowFlags())); + windowedParent->setGeometry(400, 10, 300, 300); + + windowedParent->show(); + QVERIFY(QTest::qWaitForWindowExposed(windowedParent.data())); + + //set the "container", can't be a window + QWidget containedWidget(&containerWidget, 0); + + taskQTBUG53205MenuBar = new QMenuBar(&containedWidget); + + connect(testMenus.actions[0], &QAction::triggered, this, &tst_QMenuBar::slotForTaskQTBUG53205); + //now, move things around + //from : QMainWindow<-hiddenParent<-movingParent<-containerWidget<-containedWidget<-menuBar + //to windowedParent<-movingParent<-containerWidget<-containedWidget<-menuBar + movingParent.setParent(windowedParent.data(),0); + // this resets the parenting and the menu bar's window + taskQTBUG53205MenuBar->setParent(Q_NULLPTR); + taskQTBUG53205MenuBar->setParent(&containedWidget); + //from windowedParent<-movingParent<-containerWidget<-containedWidget<-menuBar + //to : QMainWindow<-hiddenParent<-movingParent<-containerWidget<-containedWidget<-menuBar + movingParent.setParent(&hiddenParent,0); + windowedParent.reset(); //make the old window invalid + // trigger the aciton, reset the menu bar's window, this used to crash here. + testMenus.actions[0]->trigger(); +} + +void tst_QMenuBar::slotForTaskQTBUG53205() +{ + QWidget *parent = taskQTBUG53205MenuBar->parentWidget(); + taskQTBUG53205MenuBar->setParent(Q_NULLPTR); + taskQTBUG53205MenuBar->setParent(parent); +} + + + QTEST_MAIN(tst_QMenuBar) #include "tst_qmenubar.moc" -- GitLab