1 /****************************************************************************
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the QtGui module of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qmenu_mac.h"
44 #include <Cocoa/Cocoa.h>
49 #include "qapplication.h"
54 #include "qwidgetaction.h"
56 #include <private/qmenu_p.h>
57 #include <private/qmenubar_p.h>
58 #include <private/qguiapplication_p.h>
60 #include "qcocoahelpers.h"
61 #include "qcocoaapplication.h"
62 #include "qcocoamenuloader.h"
63 #include "qcocoamenu.h"
64 #include "qcocoahelpers.h"
65 #include "qcocoaautoreleasepool.h"
69 /*****************************************************************************
70 QMenu debug facilities
71 *****************************************************************************/
73 /*****************************************************************************
75 *****************************************************************************/
76 bool qt_mac_no_menubar_merge = false;
77 bool qt_mac_quit_menu_item_enabled = true;
78 int qt_mac_menus_open_count = 0;
80 static OSMenuRef qt_mac_create_menu(QWidget *w);
83 QPointer<QMenuBar> qmenubar;
85 } qt_mac_current_menubar = { 0, false };
90 /*****************************************************************************
92 *****************************************************************************/
93 extern OSViewRef qt_mac_hiview_for(const QWidget *w); //qwidget_mac.cpp
94 extern IconRef qt_mac_create_iconref(const QPixmap &px); //qpixmap_mac.cpp
95 extern QWidget * mac_keyboard_grabber; //qwidget_mac.cpp
96 extern bool qt_sendSpontaneousEvent(QObject*, QEvent*); //qapplication_xxx.cpp
97 RgnHandle qt_mac_get_rgn(); //qregion_mac.cpp
98 void qt_mac_dispose_rgn(RgnHandle r); //qregion_mac.cpp
100 /*****************************************************************************
101 QMenu utility functions
102 *****************************************************************************/
103 bool qt_mac_watchingAboutToShow(QMenu *menu)
105 return menu; /* && menu->receivers(SIGNAL(aboutToShow()));*/
108 static int qt_mac_CountMenuItems(OSMenuRef menu)
111 return [menu numberOfItems];
116 void qt_mac_menu_collapseSeparators(NSMenu * theMenu, bool collapse)
118 QCocoaAutoReleasePool pool;
119 OSMenuRef menu = static_cast<OSMenuRef>(theMenu);
121 bool previousIsSeparator = true; // setting to true kills all the separators placed at the top.
122 NSMenuItem *previousItem = nil;
124 NSArray *itemArray = [menu itemArray];
125 for (unsigned int i = 0; i < [itemArray count]; ++i) {
126 NSMenuItem *item = reinterpret_cast<NSMenuItem *>([itemArray objectAtIndex:i]);
127 if ([item isSeparatorItem]) {
128 [item setHidden:previousIsSeparator];
131 if (![item isHidden]) {
133 previousIsSeparator = ([previousItem isSeparatorItem]);
137 // We now need to check the final item since we don't want any separators at the end of the list.
138 if (previousItem && previousIsSeparator)
139 [previousItem setHidden:YES];
141 NSArray *itemArray = [menu itemArray];
142 for (unsigned int i = 0; i < [itemArray count]; ++i) {
143 NSMenuItem *item = reinterpret_cast<NSMenuItem *>([itemArray objectAtIndex:i]);
144 if (QAction *action = reinterpret_cast<QAction *>([item tag]))
145 [item setHidden:!action->isVisible()];
150 #ifndef QT_NO_TRANSLATION
151 static const char *application_menu_strings[] = {
152 QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","Services"),
153 QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","Hide %1"),
154 QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","Hide Others"),
155 QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","Show All"),
156 QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","Preferences..."),
157 QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","Quit %1"),
158 QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","About %1")
161 QString qt_mac_applicationmenu_string(int type)
163 QString menuString = QString::fromLatin1(application_menu_strings[type]);
164 QString translated = qApp->translate("QMenuBar", application_menu_strings[type]);
165 if (translated != menuString)
168 return qApp->translate("MAC_APPLICATION_MENU",
169 application_menu_strings[type]);
174 static quint32 constructModifierMask(quint32 accel_key)
177 const bool dontSwap = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta);
178 if ((accel_key & Qt::CTRL) == Qt::CTRL)
179 ret |= (dontSwap ? NSControlKeyMask : NSCommandKeyMask);
180 if ((accel_key & Qt::META) == Qt::META)
181 ret |= (dontSwap ? NSCommandKeyMask : NSControlKeyMask);
182 if ((accel_key & Qt::ALT) == Qt::ALT)
183 ret |= NSAlternateKeyMask;
184 if ((accel_key & Qt::SHIFT) == Qt::SHIFT)
185 ret |= NSShiftKeyMask;
189 static void cancelAllMenuTracking()
191 QCocoaAutoReleasePool pool;
192 NSMenu *mainMenu = [NSApp mainMenu];
193 [mainMenu cancelTracking];
194 for (NSMenuItem *item in [mainMenu itemArray]) {
195 if ([item submenu]) {
196 [[item submenu] cancelTracking];
201 static bool actualMenuItemVisibility(const QCocoaMenuBar *mbp,
202 const QCocoaMenuAction *action)
204 bool visible = action->action->isVisible();
205 if (visible && action->action->text() == QString(QChar(0x14)))
208 if (visible && action->action->menu() && !action->action->menu()->actions().isEmpty() &&
209 /* ### !qt_mac_CountMenuItems(cocoaMenu->macMenu(mbp->apple_menu)) &&*/
210 !qt_mac_watchingAboutToShow(action->action->menu())) {
216 static inline void syncNSMenuItemVisiblity(NSMenuItem *menuItem, bool actionVisibility)
218 [menuItem setHidden:NO];
219 [menuItem setHidden:YES];
220 [menuItem setHidden:!actionVisibility];
223 static inline void syncNSMenuItemEnabled(NSMenuItem *menuItem, bool enabled)
225 [menuItem setEnabled:NO];
226 [menuItem setEnabled:YES];
227 [menuItem setEnabled:enabled];
230 static inline void syncMenuBarItemsVisiblity(const QCocoaMenuBar *mac_menubar)
232 const QList<QCocoaMenuAction *> &menubarActions = mac_menubar->actionItems;
233 for (int i = 0; i < menubarActions.size(); ++i) {
234 const QCocoaMenuAction *action = menubarActions.at(i);
235 syncNSMenuItemVisiblity(action->menuItem, actualMenuItemVisibility(mac_menubar, action));
239 static inline QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *getMenuLoader()
241 return [NSApp QT_MANGLE_NAMESPACE(qt_qcocoamenuLoader)];
244 static NSMenuItem *createNSMenuItem(const QString &title)
246 NSMenuItem *item = [[NSMenuItem alloc]
247 initWithTitle:qt_mac_QStringToNSString(title)
248 action:@selector(qtDispatcherToQAction:) keyEquivalent:@""];
249 [item setTarget:nil];
253 // helper that recurses into a menu structure and en/dis-ables them
254 void qt_mac_set_modal_state_helper_recursive(OSMenuRef menu, OSMenuRef merge, bool on)
256 bool modalWindowOnScreen = qApp->activeModalWidget() != 0;
257 for (NSMenuItem *item in [menu itemArray]) {
258 OSMenuRef submenu = [item submenu];
259 if (submenu != merge) {
261 qt_mac_set_modal_state_helper_recursive(submenu, merge, on);
263 // The item should follow what the QAction has.
265 QAction *action = reinterpret_cast<QAction *>([item tag]);
266 syncNSMenuItemEnabled(item, action->isEnabled());
268 syncNSMenuItemEnabled(item, YES);
270 // We sneak in some extra code here to handle a menu problem:
271 // If there is no window on screen, we cannot set 'nil' as
272 // menu item target, because then cocoa will disable the item
273 // (guess it assumes that there will be no first responder to
274 // catch the trigger anyway?) OTOH, If we have a modal window,
275 // then setting the menu loader as target will make cocoa not
276 // deliver the trigger because the loader is then seen as modally
277 // shaddowed). So either way there are shortcomings. Instead, we
278 // decide the target as late as possible:
279 [item setTarget:modalWindowOnScreen ? nil : getMenuLoader()];
281 syncNSMenuItemEnabled(item, NO);
287 //toggling of modal state
288 static void qt_mac_set_modal_state(OSMenuRef menu, bool on)
290 OSMenuRef merge = QCocoaMenu::mergeMenuHash.value(menu);
291 qt_mac_set_modal_state_helper_recursive(menu, merge, on);
292 // I'm ignoring the special items now, since they should get handled via a syncAction()
295 bool qt_mac_menubar_is_open()
297 return qt_mac_menus_open_count > 0;
300 QCocoaMenuAction::~QCocoaMenuAction()
303 // Update the menu item if this action still owns it. For some items
304 // (like 'Quit') ownership will be transferred between all menu bars...
305 if (action && action.data() == reinterpret_cast<QAction *>([menuItem tag])) {
306 QAction::MenuRole role = action->menuRole();
307 // Check if the item is owned by Qt, and should be hidden to keep it from causing
308 // problems. Do it for everything but the quit menu item since that should always
310 if (role > QAction::ApplicationSpecificRole && role < QAction::QuitRole) {
311 [menuItem setHidden:YES];
312 } else if (role == QAction::TextHeuristicRole
313 && menuItem != [getMenuLoader() quitMenuItem]) {
314 [menuItem setHidden:YES];
316 [menuItem setTag:nil];
321 static NSMenuItem *qt_mac_menu_merge_action(OSMenuRef merge, QCocoaMenuAction *action)
323 if (qt_mac_no_menubar_merge || action->action->menu() || action->action->isSeparator()
324 || action->action->menuRole() == QAction::NoRole)
327 QString t = qt_mac_removeMnemonics(action->action->text().toLower());
328 int st = t.lastIndexOf(QLatin1Char('\t'));
330 t.remove(st, t.length()-st);
331 t.replace(QRegExp(QString::fromLatin1("\\.*$")), QLatin1String("")); //no ellipses
334 QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
336 switch (action->action->menuRole()) {
337 case QAction::NoRole:
340 case QAction::ApplicationSpecificRole:
341 ret = [loader appSpecificMenuItem];
343 case QAction::AboutRole:
344 ret = [loader aboutMenuItem];
346 case QAction::AboutQtRole:
347 ret = [loader aboutQtMenuItem];
349 case QAction::QuitRole:
350 ret = [loader quitMenuItem];
352 case QAction::PreferencesRole:
353 ret = [loader preferencesMenuItem];
355 case QAction::TextHeuristicRole: {
356 QString aboutString = QMenuBar::tr("About").toLower();
357 if (t.startsWith(aboutString) || t.endsWith(aboutString)) {
358 if (t.indexOf(QRegExp(QString::fromLatin1("qt$"), Qt::CaseInsensitive)) == -1) {
359 ret = [loader aboutMenuItem];
361 ret = [loader aboutQtMenuItem];
363 } else if (t.startsWith(QMenuBar::tr("Config").toLower())
364 || t.startsWith(QMenuBar::tr("Preference").toLower())
365 || t.startsWith(QMenuBar::tr("Options").toLower())
366 || t.startsWith(QMenuBar::tr("Setting").toLower())
367 || t.startsWith(QMenuBar::tr("Setup").toLower())) {
368 ret = [loader preferencesMenuItem];
369 } else if (t.startsWith(QMenuBar::tr("Quit").toLower())
370 || t.startsWith(QMenuBar::tr("Exit").toLower())) {
371 ret = [loader quitMenuItem];
377 if (QMenuMergeList *list = QCocoaMenu::mergeMenuItemsHash.value(merge)) {
378 for(int i = 0; i < list->size(); ++i) {
379 const QMenuMergeItem &item = list->at(i);
380 if (item.menuItem == ret && item.action)
388 static QString qt_mac_menu_merge_text(QCocoaMenuAction *action)
391 extern QString qt_mac_applicationmenu_string(int type);
392 QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
393 if (action->action->menuRole() == QAction::ApplicationSpecificRole)
394 ret = action->action->text();
395 else if (action->menuItem == [loader aboutMenuItem]) {
396 ret = qt_mac_applicationmenu_string(6).arg(qt_mac_applicationName());
397 } else if (action->menuItem == [loader aboutQtMenuItem]) {
398 if (action->action->text() == QString("About Qt"))
399 ret = QMenuBar::tr("About Qt");
401 ret = action->action->text();
402 } else if (action->menuItem == [loader preferencesMenuItem]) {
403 ret = qt_mac_applicationmenu_string(4);
404 } else if (action->menuItem == [loader quitMenuItem]) {
405 ret = qt_mac_applicationmenu_string(5).arg(qt_mac_applicationName());
410 static QKeySequence qt_mac_menu_merge_accel(QCocoaMenuAction *action)
413 QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
414 if (action->action->menuRole() == QAction::ApplicationSpecificRole)
415 ret = action->action->shortcut();
416 else if (action->menuItem == [loader preferencesMenuItem])
417 ret = QKeySequence(QKeySequence::Preferences);
418 else if (action->menuItem == [loader quitMenuItem])
419 ret = QKeySequence(QKeySequence::Quit);
423 void Q_WIDGETS_EXPORT qt_mac_set_menubar_icons(bool b)
424 { QApplication::instance()->setAttribute(Qt::AA_DontShowIconsInMenus, !b); }
425 void Q_WIDGETS_EXPORT qt_mac_set_native_menubar(bool b)
426 { QApplication::instance()->setAttribute(Qt::AA_DontUseNativeMenuBar, !b); }
427 void Q_WIDGETS_EXPORT qt_mac_set_menubar_merge(bool b) { qt_mac_no_menubar_merge = !b; }
429 /*****************************************************************************
431 *****************************************************************************/
433 QCocoaMenuAction::QCocoaMenuAction()
435 , ignore_accel(0), merged(0), menu(0)
440 QCocoaMenu::QCocoaMenu(QMenu *a_qtMenu) : menu(0), qtMenu(a_qtMenu)
444 QCocoaMenu::~QCocoaMenu()
446 QCocoaAutoReleasePool pool;
447 while (actionItems.size()) {
448 QCocoaMenuAction *action = static_cast<QCocoaMenuAction *>(actionItems.takeFirst());
449 if (QMenuMergeList *list = mergeMenuItemsHash.value(action->menu)) {
451 while (i < list->size()) {
452 const QMenuMergeItem &item = list->at(i);
453 if (item.action == action)
461 mergeMenuHash.remove(menu);
462 mergeMenuItemsHash.remove(menu);
467 void QCocoaMenu::addAction(QAction *a, QAction *before)
469 QCocoaMenuAction *action = new QCocoaMenuAction;
471 action->ignore_accel = 0;
475 QCocoaMenuAction *cocoaBefore = findAction(before);
476 addAction(action, cocoaBefore);
480 void QCocoaMenu::addAction(QCocoaMenuAction *action, QCocoaMenuAction *before)
482 QCocoaAutoReleasePool pool;
485 int before_index = actionItems.indexOf(before);
486 if (before_index < 0) {
488 before_index = actionItems.size();
490 actionItems.insert(before_index, action);
493 [action->menu release];
496 /* When the action is considered a mergable action it
497 will stay that way, until removed.. */
498 if (!qt_mac_no_menubar_merge) {
499 OSMenuRef merge = QCocoaMenu::mergeMenuHash.value(menu);
501 if (NSMenuItem *cmd = qt_mac_menu_merge_action(merge, action)) {
504 [action->menu release];
505 action->menu = merge;
507 [cmd setAction:@selector(qtDispatcherToQAction:)];
509 [action->menuItem release];
510 action->menuItem = cmd;
511 QMenuMergeList *list = QCocoaMenu::mergeMenuItemsHash.value(merge);
513 list = new QMenuMergeList;
514 QCocoaMenu::mergeMenuItemsHash.insert(merge, list);
516 list->append(QMenuMergeItem(cmd, action));
521 NSMenuItem *newItem = action->menuItem;
523 newItem = createNSMenuItem(action->action->text());
524 action->menuItem = newItem;
526 [menu insertItem:newItem atIndex:qMax(before_index, 0)];
528 [menu addItem:newItem];
531 [newItem setEnabled:YES];
533 //[newItem setEnabled:!QApplicationPrivate::modalState()];
536 [newItem setTag:long(static_cast<QAction *>(action->action))];
540 void QCocoaMenu::syncAction(QAction *a)
542 syncAction(findAction(a));
545 void QCocoaMenu::removeAction(QAction *a)
547 removeAction(findAction(a));
550 QCocoaMenuAction *QCocoaMenu::findAction(QAction *action) const
552 for (int i = 0; i < actionItems.size(); i++) {
553 QCocoaMenuAction *act = actionItems[i];
554 if (action == act->action)
561 // return an autoreleased string given a QKeySequence (currently only looks at the first one).
562 NSString *keySequenceToKeyEqivalent(const QKeySequence &accel)
564 quint32 accel_key = (accel[0] & ~(Qt::MODIFIER_MASK | Qt::UNICODE_ACCEL));
565 QChar cocoa_key = qt_mac_qtKey2CocoaKey(Qt::Key(accel_key));
566 if (cocoa_key.isNull())
567 cocoa_key = QChar(accel_key).toLower().unicode();
568 return [NSString stringWithCharacters:&cocoa_key.unicode() length:1];
571 // return the cocoa modifier mask for the QKeySequence (currently only looks at the first one).
572 NSUInteger keySequenceModifierMask(const QKeySequence &accel)
574 return constructModifierMask(accel[0]);
577 void QCocoaMenu::syncAction(QCocoaMenuAction *action)
582 NSMenuItem *item = action->menuItem;
586 QCocoaAutoReleasePool pool;
587 NSMenu *menu = [item menu];
588 bool actionVisible = action->action->isVisible();
589 [item setHidden:!actionVisible];
593 int itemIndex = [menu indexOfItem:item];
594 Q_ASSERT(itemIndex != -1);
595 if (action->action->isSeparator()) {
596 action->menuItem = [NSMenuItem separatorItem];
597 [action->menuItem retain];
598 [menu insertItem: action->menuItem atIndex:itemIndex];
599 [menu removeItem:item];
601 item = action->menuItem;
603 } else if ([item isSeparatorItem]) {
604 // I'm no longer a separator...
605 action->menuItem = createNSMenuItem(action->action->text());
606 [menu insertItem:action->menuItem atIndex:itemIndex];
607 [menu removeItem:item];
609 item = action->menuItem;
612 //find text (and accel)
613 action->ignore_accel = 0;
614 QString text = action->action->text();
615 QKeySequence accel = action->action->shortcut();
617 int st = text.lastIndexOf(QLatin1Char('\t'));
619 action->ignore_accel = 1;
620 accel = QKeySequence(text.right(text.length()-(st+1)));
621 text.remove(st, text.length()-st);
625 QString cmd_text = qt_mac_menu_merge_text(action);
626 if (!cmd_text.isEmpty()) {
628 accel = qt_mac_menu_merge_accel(action);
631 // Show multiple key sequences as part of the menu text.
632 if (accel.count() > 1)
633 text += QLatin1String(" (") + accel.toString(QKeySequence::NativeText) + QLatin1String(")");
636 QString finalString = qt_mac_removeMnemonics(text);
638 QString finalString = qt_mac_removeMnemonics(text);
640 // Cocoa Font and title
641 if (action->action->font().resolve()) {
642 const QFont &actionFont = action->action->font();
643 NSFont *customMenuFont = [NSFont fontWithName:qt_mac_QStringToNSString(actionFont.family())
644 size:actionFont.pointSize()];
645 NSArray *keys = [NSArray arrayWithObjects:NSFontAttributeName, nil];
646 NSArray *objects = [NSArray arrayWithObjects:customMenuFont, nil];
647 NSDictionary *attributes = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
648 NSAttributedString *str = [[[NSAttributedString alloc] initWithString:qt_mac_QStringToNSString(finalString)
649 attributes:attributes] autorelease];
650 [item setAttributedTitle: str];
652 [item setTitle: qt_mac_QStringToNSString(finalString)];
655 if (action->action->menuRole() == QAction::AboutRole || action->action->menuRole() == QAction::QuitRole)
656 [item setTitle:qt_mac_QStringToNSString(text)];
658 [item setTitle:qt_mac_QStringToNSString(qt_mac_removeMnemonics(text))];
661 [item setEnabled: action->action->isEnabled()];
664 NSImage *nsimage = 0;
665 if (!action->action->icon().isNull() && action->action->isIconVisibleInMenu()) {
666 nsimage = static_cast<NSImage *>(qt_mac_create_nsimage(action->action->icon().pixmap(16, QIcon::Normal)));
668 [item setImage:nsimage];
671 if (action->action->menu()) { //submenu
672 QCocoaMenu *cocoaMenu = static_cast<QCocoaMenu *>(action->action->menu()->platformMenu());
673 NSMenu *subMenu = cocoaMenu->macMenu();
674 if ([subMenu supermenu] && [subMenu supermenu] != [item menu]) {
675 // The menu is already a sub-menu of another one. Cocoa will throw an exception,
676 // in such cases. For the time being, a new QMenu with same set of actions is the
678 action->action->setEnabled(false);
680 [item setSubmenu:subMenu];
682 } else { //respect some other items
684 // No key equivalent set for multiple key QKeySequence.
685 if (accel.count() == 1) {
686 [item setKeyEquivalent:keySequenceToKeyEqivalent(accel)];
687 [item setKeyEquivalentModifierMask:keySequenceModifierMask(accel)];
689 [item setKeyEquivalent:@""];
690 [item setKeyEquivalentModifierMask:NSCommandKeyMask];
694 [item setState:action->action->isChecked() ? NSOnState : NSOffState];
697 void QCocoaMenu::removeAction(QCocoaMenuAction *action)
701 QCocoaAutoReleasePool pool;
702 if (action->merged) {
703 if (reinterpret_cast<QAction *>([action->menuItem tag]) == action->action) {
704 QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
705 [action->menuItem setEnabled:false];
706 if (action->menuItem != [loader quitMenuItem]
707 && action->menuItem != [loader preferencesMenuItem]) {
708 [[action->menuItem menu] removeItem:action->menuItem];
712 [[action->menuItem menu] removeItem:action->menuItem];
714 actionItems.removeAll(action);
717 OSMenuRef QCocoaMenu::macMenu(OSMenuRef merge)
721 menu = qt_mac_create_menu(qtMenu);
723 mergeMenuHash.insert(menu, merge);
725 QList<QAction*> items = qtMenu->actions();
726 for(int i = 0; i < items.count(); i++)
727 addAction(items[i], 0);
728 syncSeparatorsCollapsible(qtMenu->separatorsCollapsible());
736 QCocoaMenu::syncSeparatorsCollapsible(bool collapse)
738 qt_mac_menu_collapseSeparators(menu, collapse);
744 void QCocoaMenu::setMenuEnabled(bool enable)
746 QCocoaAutoReleasePool pool;
748 for (int i = 0; i < actionItems.count(); ++i) {
749 QCocoaMenuAction *menuItem = static_cast<QCocoaMenuAction *>(actionItems.at(i));
750 if (menuItem && menuItem->action && menuItem->action->isEnabled()) {
751 [menuItem->menuItem setEnabled:true];
756 for (NSMenuItem *item in [menu itemArray]) {
757 [item setEnabled:false];
765 This function will return the OSMenuRef used to create the native menu bar
768 If Qt is built against Carbon, the OSMenuRef is a MenuRef that can be used
769 with Carbon's Menu Manager API.
771 If Qt is built against Cocoa, the OSMenuRef is a NSMenu pointer.
773 \warning This function is not portable.
775 \sa QMenuBar::macMenu()
777 /// OSMenuRef QMenu::macMenu(OSMenuRef merge) { return d_func()->macMenu(merge); }
779 /*****************************************************************************
781 *****************************************************************************/
782 typedef QHash<QWidget *, QMenuBar *> MenuBarHash;
783 Q_GLOBAL_STATIC(MenuBarHash, menubars)
784 static QMenuBar *fallback = 0;
786 QCocoaMenuBar::QCocoaMenuBar(QMenuBar *a_qtMenuBar) : menu(0), apple_menu(0), qtMenuBar(a_qtMenuBar)
788 macCreateMenuBar(qtMenuBar->parentWidget());
791 QCocoaMenuBar::~QCocoaMenuBar()
793 for(QList<QCocoaMenuAction*>::Iterator it = actionItems.begin(); it != actionItems.end(); ++it)
795 [apple_menu release];
798 void QCocoaMenuBar::handleReparent(QWidget *newParent)
800 if (macWidgetHasNativeMenubar(newParent)) {
801 // If the new parent got a native menubar from before, keep that
802 // menubar rather than replace it with this one (because a parents
803 // menubar has precedence over children menubars).
805 macCreateMenuBar(newParent);
810 void QCocoaMenuBar::addAction(QAction *action, QAction *beforeAction)
812 if (action->isSeparator() || !menu)
814 QCocoaMenuAction *cocoaAction = new QCocoaMenuAction;
815 cocoaAction->action = action;
816 cocoaAction->ignore_accel = 1;
817 QCocoaMenuAction *cocoaBeforeAction = findAction(beforeAction);
818 addAction(cocoaAction, cocoaBeforeAction);
821 void QCocoaMenuBar::addAction(QCocoaMenuAction *action, QCocoaMenuAction *before)
823 if (!action || !menu)
826 int before_index = actionItems.indexOf(before);
827 if (before_index < 0) {
829 before_index = actionItems.size();
831 actionItems.insert(before_index, action);
833 MenuItemIndex index = actionItems.size()-1;
836 QCocoaAutoReleasePool pool;
837 [action->menu retain];
838 NSMenuItem *newItem = createNSMenuItem(action->action->text());
839 action->menuItem = newItem;
842 [menu insertItem:newItem atIndex:qMax(1, before_index + 1)];
843 index = before_index;
845 [menu addItem:newItem];
847 [newItem setTag:long(static_cast<QAction *>(action->action))];
852 void QCocoaMenuBar::syncAction(QCocoaMenuAction *action)
854 if (!action || !menu)
857 QCocoaAutoReleasePool pool;
858 NSMenuItem *item = action->menuItem;
860 OSMenuRef submenu = 0;
861 bool release_submenu = false;
862 if (action->action->menu()) {
863 QCocoaMenu *cocoaMenu = static_cast<QCocoaMenu *>(action->action->menu()->platformMenu());
868 if ((submenu = cocoaMenu->macMenu(apple_menu))) {
869 if ([submenu supermenu] && [submenu supermenu] != [item menu])
872 [item setSubmenu:submenu];
877 bool visible = actualMenuItemVisibility(this, action);
878 [item setSubmenu: submenu];
879 [submenu setTitle:qt_mac_QStringToNSString(qt_mac_removeMnemonics(action->action->text()))];
880 syncNSMenuItemVisiblity(item, visible);
881 if (release_submenu) { //no pointers to it
885 qWarning("QMenu: No OSMenuRef created for popup menu");
890 void QCocoaMenuBar::removeAction(QCocoaMenuAction *action)
892 if (!action || !menu)
894 QCocoaAutoReleasePool pool;
895 [action->menu removeItem:action->menuItem];
896 actionItems.removeAll(action);
899 void QCocoaMenuBar::syncAction(QAction *a)
901 syncAction(findAction(a));
904 void QCocoaMenuBar::removeAction(QAction *a)
906 removeAction(findAction(a));
909 QCocoaMenuAction *QCocoaMenuBar::findAction(QAction *action) const
911 for (int i = 0; i < actionItems.size(); i++) {
912 QCocoaMenuAction *act = actionItems[i];
913 if (action == act->action)
919 bool QCocoaMenuBar::macWidgetHasNativeMenubar(QWidget *widget)
921 // This function is different from q->isNativeMenuBar(), as
922 // it returns true only if a native menu bar is actually
926 return menubars()->contains(widget->window());
929 void QCocoaMenuBar::macCreateMenuBar(QWidget *parent)
931 static int dontUseNativeMenuBar = -1;
932 // We call the isNativeMenuBar function here
933 // because that will make sure that local overrides
934 // are dealt with correctly. q->isNativeMenuBar() will, if not
935 // overridden, depend on the attribute Qt::AA_DontUseNativeMenuBar:
936 bool qt_mac_no_native_menubar = !qtMenuBar->isNativeMenuBar();
937 if (qt_mac_no_native_menubar == false && dontUseNativeMenuBar < 0) {
938 // The menubar is set to be native. Let's check (one time only
939 // for all menubars) if this is OK with the rest of the environment.
940 // As a result, Qt::AA_DontUseNativeMenuBar is set. NB: the application
941 // might still choose to not respect, or change, this flag.
942 bool isPlugin = QApplication::testAttribute(Qt::AA_MacPluginApplication);
943 bool environmentSaysNo = !qgetenv("QT_MAC_NO_NATIVE_MENUBAR").isEmpty();
944 dontUseNativeMenuBar = isPlugin || environmentSaysNo;
945 QApplication::instance()->setAttribute(Qt::AA_DontUseNativeMenuBar, dontUseNativeMenuBar);
946 qt_mac_no_native_menubar = !qtMenuBar->isNativeMenuBar();
948 if (qt_mac_no_native_menubar == false) {
949 // INVARIANT: Use native menubar.
951 if (!parent && !fallback) {
952 fallback = qtMenuBar;
953 } else if (parent && parent->isWindow()) {
954 menubars()->insert(qtMenuBar->window(), qtMenuBar);
959 void QCocoaMenuBar::macDestroyMenuBar()
961 QCocoaAutoReleasePool pool;
962 if (fallback == qtMenuBar)
964 QWidget *tlw = qtMenuBar->window();
965 menubars()->remove(tlw);
967 if (!qt_mac_current_menubar.qmenubar || qt_mac_current_menubar.qmenubar == qtMenuBar) {
968 QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
969 [loader removeActionsFromAppMenu];
970 QCocoaMenuBar::macUpdateMenuBar();
974 OSMenuRef QCocoaMenuBar::macMenu()
976 if (!qtMenuBar->isNativeMenuBar()) {
979 menu = qt_mac_create_menu(qtMenuBar);
980 ProcessSerialNumber mine, front;
981 if (GetCurrentProcess(&mine) == noErr && GetFrontProcess(&front) == noErr) {
982 if (!qt_mac_no_menubar_merge && !apple_menu) {
983 apple_menu = qt_mac_create_menu(qtMenuBar);
984 [apple_menu setTitle:qt_mac_QStringToNSString(QString(QChar(0x14)))];
985 NSMenuItem *apple_menuItem = [[NSMenuItem alloc] init];
986 [apple_menuItem setSubmenu:menu];
987 [apple_menu addItem:apple_menuItem];
988 [apple_menuItem release];
991 QCocoaMenu::mergeMenuHash.insert(menu, apple_menu);
993 QList<QAction*> items = qtMenuBar->actions();
994 for(int i = 0; i < items.count(); i++)
995 addAction(items[i], 0);
1004 This function will return the OSMenuRef used to create the native menu bar
1005 bindings. This OSMenuRef is then set as the root menu for the Menu
1008 \warning This function is not portable.
1010 \sa QMenu::macMenu()
1012 //OSMenuRef QMenuBar::macMenu() { return d_func()->macMenu(); }
1016 Ancestor function that crosses windows (QWidget::isAncestorOf
1017 only considers widgets within the same window).
1019 static bool qt_mac_is_ancestor(QWidget* possibleAncestor, QWidget *child)
1021 if (!possibleAncestor)
1024 QWidget * current = child->parentWidget();
1025 while (current != 0) {
1026 if (current == possibleAncestor)
1028 current = current->parentWidget();
1035 Returns true if the entries of menuBar should be disabled,
1036 based on the modality type of modalWidget.
1038 static bool qt_mac_should_disable_menu(QMenuBar *menuBar)
1040 QWidget *modalWidget = qApp->activeModalWidget();
1044 if (menuBar && menuBar == menubars()->value(modalWidget))
1045 // The menu bar is owned by the modal widget.
1046 // In that case we should enable it:
1049 // When there is an application modal window on screen, the entries of
1050 // the menubar should be disabled. The exception in Qt is that if the
1051 // modal window is the only window on screen, then we enable the menu bar.
1052 QWidget *w = modalWidget;
1053 QWidgetList topLevelWidgets = QApplication::topLevelWidgets();
1055 if (w->isVisible() && w->windowModality() == Qt::ApplicationModal) {
1056 for (int i=0; i<topLevelWidgets.size(); ++i) {
1057 QWidget *top = topLevelWidgets.at(i);
1058 if (w != top && top->isVisible()) {
1059 // INVARIANT: we found another visible window
1060 // on screen other than our modalWidget. We therefore
1061 // disable the menu bar to follow normal modality logic:
1065 // INVARIANT: We have only one window on screen that happends
1066 // to be application modal. We choose to enable the menu bar
1067 // in that case to e.g. enable the quit menu item.
1070 w = w->parentWidget();
1073 // INVARIANT: modalWidget is window modal. Disable menu entries
1074 // if the menu bar belongs to an ancestor of modalWidget. If menuBar
1075 // is nil, we understand it as the default menu bar set by the nib:
1076 return menuBar ? qt_mac_is_ancestor(menuBar->parentWidget(), modalWidget) : false;
1079 static QWidget *findWindowThatShouldDisplayMenubar()
1081 QWidget *w = qApp->activeWindow();
1084 // We have no active window on screen. Try to
1085 // find a window from the list of top levels:
1086 QWidgetList tlws = QApplication::topLevelWidgets();
1087 for(int i = 0; i < tlws.size(); ++i) {
1088 QWidget *tlw = tlws.at(i);
1089 if ((tlw->isVisible() && tlw->windowType() != Qt::Tool &&
1090 tlw->windowType() != Qt::Popup)) {
1100 static QMenuBar *findMenubarForWindow(QWidget *w)
1104 mb = menubars()->value(w);
1108 //#ifndef QT_NO_MAINWINDOW
1109 QDockWidget *dw = qobject_cast<QDockWidget *>(w);
1111 QMainWindow *mw = qobject_cast<QMainWindow *>(dw->parentWidget());
1112 if (mw && (mb = menubars()->value(mw)))
1117 mb = menubars()->value((w = w->parentWidget()));
1121 // We could not find a menu bar for the window. Lets
1122 // check if we have a global (parentless) menu bar instead:
1129 void qt_mac_clear_menubar()
1131 if (QApplication::testAttribute(Qt::AA_MacPluginApplication))
1134 QCocoaAutoReleasePool pool;
1135 QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
1136 NSMenu *menu = [loader menu];
1137 [loader ensureAppMenuInMenu:menu];
1138 [NSApp setMainMenu:menu];
1139 const bool modal = qt_mac_should_disable_menu(0);
1140 if (qt_mac_current_menubar.qmenubar || modal != qt_mac_current_menubar.modal)
1141 qt_mac_set_modal_state(menu, modal);
1142 qt_mac_current_menubar.qmenubar = 0;
1143 qt_mac_current_menubar.modal = modal;
1149 This function will update the current menu bar and set it as the
1150 active menu bar in the Menu Manager.
1152 \warning This function is not portable.
1154 void QCocoaMenuBar::macUpdateMenuBar()
1156 [getMenuLoader() performSelectorOnMainThread: @selector(qtUpdateMenubar) withObject: nil waitUntilDone: NO];
1159 bool QCocoaMenuBar::macUpdateMenuBarImmediatly()
1162 cancelAllMenuTracking();
1163 QWidget *w = findWindowThatShouldDisplayMenubar();
1164 QMenuBar *mb = findMenubarForWindow(w);
1166 // ### extern bool qt_mac_app_fullscreen; //qapplication_mac.mm
1167 bool qt_mac_app_fullscreen = false;
1168 // We need to see if we are in full screen mode, if so we need to
1169 // switch the full screen mode to be able to show or hide the menubar.
1171 // This case means we are creating a menubar, check if full screen
1172 if(w->isFullScreen()) {
1173 // Ok, switch to showing the menubar when hovering over it.
1174 SetSystemUIMode(kUIModeAllHidden, kUIOptionAutoShowMenuBar);
1175 qt_mac_app_fullscreen = true;
1178 // Removing a menubar
1179 if(w->isFullScreen()) {
1180 // Ok, switch to not showing the menubar when hovering on it
1181 SetSystemUIMode(kUIModeAllHidden, 0);
1182 qt_mac_app_fullscreen = true;
1186 if (mb && mb->isNativeMenuBar()) {
1190 //bool modal = QGuiApplicationPrivate::modalState();
1191 QCocoaAutoReleasePool pool;
1192 if (OSMenuRef menu = reinterpret_cast<QCocoaMenuBar *>(mb->platformMenuBar())->macMenu()) {
1193 QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
1194 [loader ensureAppMenuInMenu:menu];
1195 [NSApp setMainMenu:menu];
1196 syncMenuBarItemsVisiblity(reinterpret_cast<QCocoaMenuBar *>(mb->platformMenuBar()));
1198 if (OSMenuRef tmpMerge = QCocoaMenu::mergeMenuHash.value(menu)) {
1199 if (QMenuMergeList *mergeList
1200 = QCocoaMenu::mergeMenuItemsHash.value(tmpMerge)) {
1201 const int mergeListSize = mergeList->size();
1203 for (int i = 0; i < mergeListSize; ++i) {
1204 const QMenuMergeItem &mergeItem = mergeList->at(i);
1205 // Ideally we would call QCocoaMenu::syncAction, but that requires finding
1206 // the original QMen and likely doing more work than we need.
1207 // For example, enabled is handled below.
1208 [mergeItem.menuItem setTag:reinterpret_cast<long>(
1209 static_cast<QAction *>(mergeItem.action->action))];
1210 [mergeItem.menuItem setHidden:!(mergeItem.action->action->isVisible())];
1214 // Check if menu is modally shaddowed and should be disabled:
1215 modal = qt_mac_should_disable_menu(mb);
1216 if (mb != qt_mac_current_menubar.qmenubar || modal != qt_mac_current_menubar.modal)
1217 qt_mac_set_modal_state(menu, modal);
1219 qt_mac_current_menubar.qmenubar = mb;
1220 qt_mac_current_menubar.modal = modal;
1222 } else if (qt_mac_current_menubar.qmenubar && qt_mac_current_menubar.qmenubar->isNativeMenuBar()) {
1223 // INVARIANT: The currently active menu bar (if any) is not native. But we do have a
1224 // native menu bar from before. So we need to decide whether or not is should be enabled:
1225 const bool modal = qt_mac_should_disable_menu(qt_mac_current_menubar.qmenubar);
1226 if (modal != qt_mac_current_menubar.modal) {
1228 if (OSMenuRef menu = reinterpret_cast<QCocoaMenuBar *>(qt_mac_current_menubar.qmenubar->platformMenuBar())->macMenu()) {
1229 QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
1230 [loader ensureAppMenuInMenu:menu];
1231 [NSApp setMainMenu:menu];
1232 syncMenuBarItemsVisiblity(reinterpret_cast<QCocoaMenuBar *>(qt_mac_current_menubar.qmenubar->platformMenuBar()));
1233 qt_mac_set_modal_state(menu, modal);
1235 qt_mac_current_menubar.modal = modal;
1240 qt_mac_clear_menubar();
1245 QHash<OSMenuRef, OSMenuRef> QCocoaMenu::mergeMenuHash;
1246 QHash<OSMenuRef, QMenuMergeList*> QCocoaMenu::mergeMenuItemsHash;
1248 bool QCocoaMenu::merged(const QAction *action) const
1250 if (OSMenuRef merge = mergeMenuHash.value(menu)) {
1251 if (QMenuMergeList *list = mergeMenuItemsHash.value(merge)) {
1252 for(int i = 0; i < list->size(); ++i) {
1253 const QMenuMergeItem &item = list->at(i);
1254 if (item.action->action == action)
1262 //creation of the OSMenuRef
1263 static OSMenuRef qt_mac_create_menu(QWidget *w)
1266 if (QMenu *qmenu = qobject_cast<QMenu *>(w)){
1267 ret = [[QT_MANGLE_NAMESPACE(QNativeCocoaMenu) alloc] initWithQMenu:qmenu];
1269 ret = [[NSMenu alloc] init];