QCocoaMenus: Use mix-in pattern to implement menu hierarchy

To solve menu item roles from their text, we need to find
its depth in the menubar hierarchy. Unfortunately, there's
no trivial way to access that hierarchy from the QPA side,
so we need to come with an ad-hoc solution.

Previously, we were using a dynamic proprety to keep track
of the 'parent' object. However, as the life span of the
different objects has changed since 09acf326db,
we need a way to keep track of the parent's existence.

This is what we do in this patch by having both QCocoaMenu
and QCocoaMenuItem also inherit QCocoaMenuObject. This class'
sole role is to store the menu hierarchy's parent object and
wrap it in a QPointer.

Change-Id: Ia18d95171af76d26f6325eef04c77b40d99c4285
Reviewed-by: Morten Johan Sørvig <morten.sorvig@theqtcompany.com>
Reviewed-by: J-P Nurmi <jpnurmi@theqtcompany.com>
Reviewed-by: Timur Pocheptsov <timur.pocheptsov@theqtcompany.com>
bb10
Gabriel de Dietrich 2016-03-18 13:22:19 -07:00 committed by Jani Heikkinen
parent c228e01e12
commit 56384a854a
5 changed files with 34 additions and 18 deletions

View File

@ -43,7 +43,7 @@ QT_BEGIN_NAMESPACE
class QCocoaMenuBar;
class QCocoaMenu : public QPlatformMenu
class QCocoaMenu : public QPlatformMenu, public QCocoaMenuObject
{
public:
QCocoaMenu();

View File

@ -265,8 +265,8 @@ QCocoaMenu::QCocoaMenu() :
QCocoaMenu::~QCocoaMenu()
{
foreach (QCocoaMenuItem *item, m_menuItems) {
if (COCOA_MENU_ANCESTOR(item) == this)
SET_COCOA_MENU_ANCESTOR(item, 0);
if (item->menuParent() == this)
item->setMenuParent(0);
}
QMacAutoReleasePool pool;
@ -344,7 +344,7 @@ void QCocoaMenu::insertNative(QCocoaMenuItem *item, QCocoaMenuItem *beforeItem)
} else {
[m_nativeMenu addItem: item->nsItem()];
}
SET_COCOA_MENU_ANCESTOR(item, this);
item->setMenuParent(this);
}
void QCocoaMenu::removeMenuItem(QPlatformMenuItem *menuItem)
@ -356,8 +356,8 @@ void QCocoaMenu::removeMenuItem(QPlatformMenuItem *menuItem)
return;
}
if (COCOA_MENU_ANCESTOR(menuItem) == this)
SET_COCOA_MENU_ANCESTOR(menuItem, 0);
if (cocoaItem->menuParent() == this)
cocoaItem->setMenuParent(0);
m_menuItems.removeOne(cocoaItem);
if (!cocoaItem->isMerged()) {

View File

@ -314,7 +314,7 @@ void QCocoaMenuBar::updateMenuBarImmediately()
continue;
NSMenuItem *item = mb->nativeItemForMenu(menu);
menu->setAttachedItem(item);
SET_COCOA_MENU_ANCESTOR(menu, mb);
menu->setMenuParent(mb);
// force a sync?
mb->syncMenu(menu);
menu->syncModalState(disableForModal);

View File

@ -49,7 +49,24 @@ QT_BEGIN_NAMESPACE
class QCocoaMenu;
class QCocoaMenuItem : public QPlatformMenuItem
class QCocoaMenuObject
{
public:
void setMenuParent(QObject *o)
{
parent = o;
}
QObject *menuParent() const
{
return parent;
}
private:
QPointer<QObject> parent;
};
class QCocoaMenuItem : public QPlatformMenuItem, public QCocoaMenuObject
{
public:
QCocoaMenuItem();
@ -112,9 +129,6 @@ private:
int m_iconSize;
};
#define COCOA_MENU_ANCESTOR(m) ((m)->property("_qCocoaMenuAncestor").value<QObject *>())
#define SET_COCOA_MENU_ANCESTOR(m, ancestor) (m)->setProperty("_qCocoaMenuAncestor", QVariant::fromValue<QObject *>(ancestor))
QT_END_NAMESPACE
#endif

View File

@ -105,8 +105,8 @@ QCocoaMenuItem::~QCocoaMenuItem()
{
QMacAutoReleasePool pool;
if (m_menu && COCOA_MENU_ANCESTOR(m_menu) == this)
SET_COCOA_MENU_ANCESTOR(m_menu, 0);
if (m_menu && m_menu->menuParent() == this)
m_menu->setMenuParent(0);
if (m_merged) {
[m_native setHidden:YES];
} else {
@ -132,14 +132,14 @@ void QCocoaMenuItem::setMenu(QPlatformMenu *menu)
return;
if (m_menu) {
if (COCOA_MENU_ANCESTOR(m_menu) == this)
SET_COCOA_MENU_ANCESTOR(m_menu, 0);
if (m_menu->menuParent() == this)
m_menu->setMenuParent(0);
}
QMacAutoReleasePool pool;
m_menu = static_cast<QCocoaMenu *>(menu);
if (m_menu) {
SET_COCOA_MENU_ANCESTOR(m_menu, this);
m_menu->setMenuParent(this);
} else {
// we previously had a menu, but no longer
// clear out our item so the nexy sync() call builds a new one
@ -229,12 +229,14 @@ NSMenuItem *QCocoaMenuItem::sync()
mergeItem = [loader preferencesMenuItem];
break;
case TextHeuristicRole: {
QObject *p = COCOA_MENU_ANCESTOR(this);
QObject *p = menuParent();
int depth = 1;
QCocoaMenuBar *menubar = 0;
while (depth < 3 && p && !(menubar = qobject_cast<QCocoaMenuBar *>(p))) {
++depth;
p = COCOA_MENU_ANCESTOR(p);
QCocoaMenuObject *menuObject = dynamic_cast<QCocoaMenuObject *>(p);
Q_ASSERT(menuObject);
p = menuObject->menuParent();
}
if (depth == 3 || !menubar)
break; // Menu item too deep in the hierarchy, or not connected to any menubar