QMenuBar: nested parenting fix

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>
bb10
Felix Bourbonnais 2016-05-10 10:06:53 -04:00 committed by Félix Bourbonnais
parent f095ff85c5
commit b627dd2c24
3 changed files with 91 additions and 24 deletions

View File

@ -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
//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;
if (oldParent != newParent) {
if (oldParent)
oldParent->removeEventFilter(q);
if (newParent)
newParent->installEventFilter(q);
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:

View File

@ -128,8 +128,7 @@ public:
// reparenting
void handleReparent();
QWidget *oldParent;
QWidget *oldWindow;
QVector<QPointer<QWidget> > oldParents;
QList<QAction*> hiddenActions;
//default action

View File

@ -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"