QCocoaMenu: Attach menu items when updating the menubar

Instead of waiting for the menu delegate to update each item,
we can attach an NSMenu to its NSMenuItem as soon as we update
the current window's menubar. This is safe to do because we
know that this is going to be the main menubar right after, so
we're not orphaning any NSMenuItem from its NSMenu at the wrong
moment.

By doing this, we also ensure that all menus from the active
menubar are reachable by the key-equivalent dispatching logic,
even before we display the actual menu.

This was shown in BigMenuCreator where, under the menubar's ASP
and SAP menus, all A*S submenus would be disabled. Furthermore,
on the same menus, SAP would show the same issue.

Added test in Menurama as well.

Change-Id: If6e7311072e6b53ad1cbced73623d1832aa0df8e
Task-number: QTBUG-57076
Task-number: QTBUG-63712
Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
bb10
Gabriel de Dietrich 2017-10-20 17:39:08 +07:00
parent b35a27676b
commit 385589ef45
6 changed files with 33 additions and 4 deletions

View File

@ -98,6 +98,8 @@ public:
void timerEvent(QTimerEvent *e) Q_DECL_OVERRIDE;
void syncMenuItem_helper(QPlatformMenuItem *menuItem, bool menubarUpdate);
private:
QCocoaMenuItem *itemOrNull(int index) const;
void insertNative(QCocoaMenuItem *item, QCocoaMenuItem *beforeItem);

View File

@ -434,6 +434,11 @@ void QCocoaMenu::timerEvent(QTimerEvent *e)
}
void QCocoaMenu::syncMenuItem(QPlatformMenuItem *menuItem)
{
syncMenuItem_helper(menuItem, false /*menubarUpdate*/);
}
void QCocoaMenu::syncMenuItem_helper(QPlatformMenuItem *menuItem, bool menubarUpdate)
{
QMacAutoReleasePool pool;
QCocoaMenuItem *cocoaItem = static_cast<QCocoaMenuItem *>(menuItem);
@ -444,8 +449,9 @@ void QCocoaMenu::syncMenuItem(QPlatformMenuItem *menuItem)
const bool wasMerged = cocoaItem->isMerged();
NSMenuItem *oldItem = cocoaItem->nsItem();
NSMenuItem *syncedItem = cocoaItem->sync();
if (cocoaItem->sync() != oldItem) {
if (syncedItem != oldItem) {
// native item was changed for some reason
if (oldItem) {
if (wasMerged) {
@ -463,6 +469,14 @@ void QCocoaMenu::syncMenuItem(QPlatformMenuItem *menuItem)
// when an item's enabled state changes after menuWillOpen:
scheduleUpdate();
}
// This may be a good moment to attach this item's eventual submenu to the
// synced item, but only on the condition we're all currently hooked to the
// menunbar. A good indicator of this being the right moment is knowing that
// we got called from QCocoaMenuBar::updateMenuBarImmediately().
if (menubarUpdate)
if (QCocoaMenu *submenu = cocoaItem->menu())
submenu->setAttachedItem(syncedItem);
}
void QCocoaMenu::syncSeparatorsCollapsible(bool enable)

View File

@ -72,6 +72,8 @@ public:
QList<QCocoaMenuItem*> merged() const;
NSMenuItem *itemForRole(QPlatformMenuItem::MenuRole r);
void syncMenu_helper(QPlatformMenu *menu, bool menubarUpdate);
private:
static QCocoaWindow *findWindowForMenubar();
static QCocoaMenuBar *findGlobalMenubar();

View File

@ -155,7 +155,7 @@ void QCocoaMenuBar::insertMenu(QPlatformMenu *platformMenu, QPlatformMenu *befor
}
}
syncMenu(menu);
syncMenu_helper(menu, false /*internaCall*/);
if (needsImmediateUpdate())
updateMenuBarImmediately();
@ -182,12 +182,17 @@ void QCocoaMenuBar::removeMenu(QPlatformMenu *platformMenu)
}
void QCocoaMenuBar::syncMenu(QPlatformMenu *menu)
{
syncMenu_helper(menu, false /*internaCall*/);
}
void QCocoaMenuBar::syncMenu_helper(QPlatformMenu *menu, bool menubarUpdate)
{
QMacAutoReleasePool pool;
QCocoaMenu *cocoaMenu = static_cast<QCocoaMenu *>(menu);
Q_FOREACH (QCocoaMenuItem *item, cocoaMenu->items())
cocoaMenu->syncMenuItem(item);
cocoaMenu->syncMenuItem_helper(item, menubarUpdate);
BOOL shouldHide = YES;
if (cocoaMenu->isVisible()) {
@ -357,7 +362,7 @@ void QCocoaMenuBar::updateMenuBarImmediately()
menu->setAttachedItem(item);
menu->setMenuParent(mb);
// force a sync?
mb->syncMenu(menu);
mb->syncMenu_helper(menu, true /*menubarUpdate*/);
menu->propagateEnabledState(!disableForModal);
}

View File

@ -37,6 +37,11 @@ MainWindow::MainWindow(QWidget *parent) :
{
ui->setupUi(this);
auto *a = ui->menuStuff->addAction("Enabled Submenu (QTBUG-63172)");
auto *qtbug63172_Menu = new QMenu;
qtbug63172_Menu->addAction("We're Good!");
a->setMenu(qtbug63172_Menu);
startTimer(1000);
connect(ui->menuAfter_aboutToShow, &QMenu::aboutToShow, [=] {

View File

@ -131,6 +131,7 @@ Click on &quot;Dynamic Stuff&quot; then move left and right to other menus. Disa
<addaction name="menuSubmenu"/>
<addaction name="actionDisabled_Item"/>
<addaction name="menuDisabled_Submenu"/>
<addaction name="separator"/>
</widget>
<widget class="QMenu" name="menuDisabled_Stuff">
<property name="enabled">