QMenu: don't force platform instance creation on construction

There's a conflict between QGtk3Menu and QDbusPlatformMenuBar. The
problem is that on Unity the type of the platform menu instance must
be different depending on whether the menu is in the global menubar or
a standalone context menu.

Since QMenu creates a platform menu instance at construction time, it
does not yet know whether it will be added into a menubar. QMenuBar
checks that the QMenu already has a platform menu instance, and passes
it to the platform menubar. As a result, a QGtk3Menu instance is passed
to QDbusPlatformMenuBar.

Currently, a standalone QMenu does not use the native platform menu
instance. Only menus that are added to a QMenuBar do. Therefore we
don't need to create the platform instance when QMenu is constructed,
but only after it is added to QMenuBar. The platform menu instance
creation is implemented in QMenuBarPrivate::getPlatformMenu(), and
QMenu::setPlatformMenu() calls syncPlatformMenu() to take care of
syncing the QMenu properties and actions to the new platform menu
instance.

The macOS-specific methods QMenu::toNSMenu() and QMenu::setAsDockMenu()
rely on the platform menu instance, and must therefore create it on
demand.

This is a hot fix for the release blocker, not a long term solution.
In the future, if standalone QMenus are made to use native platform
menu instances, the instance must be created lazily when the menu is
about to be made visible.

Task-number: QTBUG-56526
Change-Id: I044933cabb1639406fe47908dfc4b1903af214d1
Reviewed-by: Dmitry Shachnev <mitya57@gmail.com>
Reviewed-by: Lars Knoll <lars.knoll@qt.io>
bb10
J-P Nurmi 2016-10-14 16:16:02 +02:00
parent 0cc2a75ad8
commit 063997f44f
5 changed files with 38 additions and 3 deletions

View File

@ -153,12 +153,19 @@ void QMenuPrivate::init()
scroll->scrollFlags = QMenuPrivate::QMenuScroller::ScrollNone;
}
setPlatformMenu(QGuiApplicationPrivate::platformTheme()->createPlatformMenu());
sloppyState.initialize(q);
delayState.initialize(q);
mousePopupDelay = q->style()->styleHint(QStyle::SH_Menu_SubMenuPopupDelay, 0, q);
}
QPlatformMenu *QMenuPrivate::createPlatformMenu()
{
Q_Q(QMenu);
if (platformMenu.isNull())
q->setPlatformMenu(QGuiApplicationPrivate::platformTheme()->createPlatformMenu());
return platformMenu.data();
}
void QMenuPrivate::setPlatformMenu(QPlatformMenu *menu)
{
Q_Q(QMenu);

View File

@ -80,11 +80,12 @@ inline QPlatformNativeInterface::NativeResourceForIntegrationFunction resolvePla
*/
NSMenu *QMenu::toNSMenu()
{
Q_D(QMenu);
// Call into the cocoa platform plugin: qMenuToNSMenu(platformMenu())
QPlatformNativeInterface::NativeResourceForIntegrationFunction function = resolvePlatformFunction("qmenutonsmenu");
if (function) {
typedef void* (*QMenuToNSMenuFunction)(QPlatformMenu *platformMenu);
return reinterpret_cast<NSMenu *>(reinterpret_cast<QMenuToNSMenuFunction>(function)(platformMenu()));
return reinterpret_cast<NSMenu *>(reinterpret_cast<QMenuToNSMenuFunction>(function)(d->createPlatformMenu()));
}
return nil;
}
@ -98,11 +99,12 @@ NSMenu *QMenu::toNSMenu()
*/
void QMenu::setAsDockMenu()
{
Q_D(QMenu);
// Call into the cocoa platform plugin: setDockMenu(platformMenu())
QPlatformNativeInterface::NativeResourceForIntegrationFunction function = resolvePlatformFunction("setdockmenu");
if (function) {
typedef void (*SetDockMenuFunction)(QPlatformMenu *platformMenu);
reinterpret_cast<SetDockMenuFunction>(function)(platformMenu());
reinterpret_cast<SetDockMenuFunction>(function)(d->createPlatformMenu());
}
}

View File

@ -286,6 +286,7 @@ public:
delete platformMenu.data();
}
void init();
QPlatformMenu *createPlatformMenu();
void setPlatformMenu(QPlatformMenu *menu);
void syncPlatformMenu();
#ifdef Q_OS_OSX

View File

@ -1192,6 +1192,10 @@ QPlatformMenu *QMenuBarPrivate::getPlatformMenu(QAction *action)
QPlatformMenu *platformMenu = action->menu()->platformMenu();
if (!platformMenu && platformMenuBar) {
platformMenu = platformMenuBar->createMenu();
// QPlatformMenuBar::createMenu() was introduced in Qt 5.7. Not all third party
// platform themes are using it, so fallback to QPlatformTheme::createPlatformMenu().
if (!platformMenu)
platformMenu = QGuiApplicationPrivate::platformTheme()->createPlatformMenu();
if (platformMenu)
action->menu()->setPlatformMenu(platformMenu);
}

View File

@ -131,6 +131,8 @@ private slots:
void cornerWidgets();
void taskQTBUG53205_crashReparentNested();
void platformMenu();
protected slots:
void onSimpleActivated( QAction*);
void onComplexActionTriggered();
@ -1487,6 +1489,25 @@ void tst_QMenuBar::taskQTBUG53205_crashReparentNested()
testMenus.actions[0]->trigger();
}
// QTBUG-56526
void tst_QMenuBar::platformMenu()
{
QMenuBar menuBar;
QPlatformMenuBar *platformMenuBar = menuBar.platformMenuBar();
if (!platformMenuBar)
QSKIP("No platform menubar implementation available on this platform.");
// QMenu must not create a platform menu instance at creation time, because
// on Unity the type of the platform menu instance must be different (QGtk3Menu
// vs. QDbusPlatformMenu) depending on whether the menu is in the global menubar
// or a standalone context menu.
QMenu *menu = new QMenu(&menuBar);
QVERIFY(!menu->platformMenu());
menuBar.addMenu(menu);
QVERIFY(menu->platformMenu());
}
void tst_QMenuBar::slotForTaskQTBUG53205()
{
QWidget *parent = taskQTBUG53205MenuBar->parentWidget();