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)
472 QCocoaMenuAction *action = new QCocoaMenuAction;
474 action->ignore_accel = 0;
478 QCocoaMenuAction *cocoaBefore = findAction(before);
479 addAction(action, cocoaBefore);
483 void QCocoaMenu::addAction(QCocoaMenuAction *action, QCocoaMenuAction *before)
485 QCocoaAutoReleasePool pool;
488 int before_index = actionItems.indexOf(before);
489 if (before_index < 0) {
491 before_index = actionItems.size();
493 actionItems.insert(before_index, action);
496 [action->menu release];
499 /* When the action is considered a mergable action it
500 will stay that way, until removed.. */
501 if (!qt_mac_no_menubar_merge) {
502 OSMenuRef merge = QCocoaMenu::mergeMenuHash.value(menu);
504 if (NSMenuItem *cmd = qt_mac_menu_merge_action(merge, action)) {
507 [action->menu release];
508 action->menu = merge;
510 [cmd setAction:@selector(qtDispatcherToQAction:)];
512 [action->menuItem release];
513 action->menuItem = cmd;
514 QMenuMergeList *list = QCocoaMenu::mergeMenuItemsHash.value(merge);
516 list = new QMenuMergeList;
517 QCocoaMenu::mergeMenuItemsHash.insert(merge, list);
519 list->append(QMenuMergeItem(cmd, action));
524 NSMenuItem *newItem = action->menuItem;
526 newItem = createNSMenuItem(action->action->text());
527 action->menuItem = newItem;
529 [menu insertItem:newItem atIndex:qMax(before_index, 0)];
531 [menu addItem:newItem];
534 [newItem setEnabled:YES];
536 //[newItem setEnabled:!QApplicationPrivate::modalState()];
539 [newItem setTag:long(static_cast<QAction *>(action->action))];
543 void QCocoaMenu::syncAction(QAction *a)
545 syncAction(findAction(a));
548 void QCocoaMenu::removeAction(QAction *a)
550 removeAction(findAction(a));
553 QCocoaMenuAction *QCocoaMenu::findAction(QAction *action) const
555 for (int i = 0; i < actionItems.size(); i++) {
556 QCocoaMenuAction *act = actionItems[i];
557 if (action == act->action)
564 // return an autoreleased string given a QKeySequence (currently only looks at the first one).
565 NSString *keySequenceToKeyEqivalent(const QKeySequence &accel)
567 quint32 accel_key = (accel[0] & ~(Qt::MODIFIER_MASK | Qt::UNICODE_ACCEL));
568 QChar cocoa_key = qt_mac_qtKey2CocoaKey(Qt::Key(accel_key));
569 if (cocoa_key.isNull())
570 cocoa_key = QChar(accel_key).toLower().unicode();
571 return [NSString stringWithCharacters:&cocoa_key.unicode() length:1];
574 // return the cocoa modifier mask for the QKeySequence (currently only looks at the first one).
575 NSUInteger keySequenceModifierMask(const QKeySequence &accel)
577 return constructModifierMask(accel[0]);
580 void QCocoaMenu::syncAction(QCocoaMenuAction *action)
585 NSMenuItem *item = action->menuItem;
589 QCocoaAutoReleasePool pool;
590 NSMenu *menu = [item menu];
591 bool actionVisible = action->action->isVisible();
592 [item setHidden:!actionVisible];
596 int itemIndex = [menu indexOfItem:item];
597 Q_ASSERT(itemIndex != -1);
598 if (action->action->isSeparator()) {
599 action->menuItem = [NSMenuItem separatorItem];
600 [action->menuItem retain];
601 [menu insertItem: action->menuItem atIndex:itemIndex];
602 [menu removeItem:item];
604 item = action->menuItem;
606 } else if ([item isSeparatorItem]) {
607 // I'm no longer a separator...
608 action->menuItem = createNSMenuItem(action->action->text());
609 [menu insertItem:action->menuItem atIndex:itemIndex];
610 [menu removeItem:item];
612 item = action->menuItem;
615 //find text (and accel)
616 action->ignore_accel = 0;
617 QString text = action->action->text();
618 QKeySequence accel = action->action->shortcut();
620 int st = text.lastIndexOf(QLatin1Char('\t'));
622 action->ignore_accel = 1;
623 accel = QKeySequence(text.right(text.length()-(st+1)));
624 text.remove(st, text.length()-st);
628 QString cmd_text = qt_mac_menu_merge_text(action);
629 if (!cmd_text.isEmpty()) {
631 accel = qt_mac_menu_merge_accel(action);
634 // Show multiple key sequences as part of the menu text.
635 if (accel.count() > 1)
636 text += QLatin1String(" (") + accel.toString(QKeySequence::NativeText) + QLatin1String(")");
639 QString finalString = qt_mac_removeMnemonics(text);
641 QString finalString = qt_mac_removeMnemonics(text);
643 // Cocoa Font and title
644 if (action->action->font().resolve()) {
645 const QFont &actionFont = action->action->font();
646 NSFont *customMenuFont = [NSFont fontWithName:qt_mac_QStringToNSString(actionFont.family())
647 size:actionFont.pointSize()];
648 NSArray *keys = [NSArray arrayWithObjects:NSFontAttributeName, nil];
649 NSArray *objects = [NSArray arrayWithObjects:customMenuFont, nil];
650 NSDictionary *attributes = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
651 NSAttributedString *str = [[[NSAttributedString alloc] initWithString:qt_mac_QStringToNSString(finalString)
652 attributes:attributes] autorelease];
653 [item setAttributedTitle: str];
655 [item setTitle: qt_mac_QStringToNSString(finalString)];
658 if (action->action->menuRole() == QAction::AboutRole || action->action->menuRole() == QAction::QuitRole)
659 [item setTitle:qt_mac_QStringToNSString(text)];
661 [item setTitle:qt_mac_QStringToNSString(qt_mac_removeMnemonics(text))];
664 [item setEnabled: action->action->isEnabled()];
667 NSImage *nsimage = 0;
668 if (!action->action->icon().isNull() && action->action->isIconVisibleInMenu()) {
669 nsimage = static_cast<NSImage *>(qt_mac_create_nsimage(action->action->icon().pixmap(16, QIcon::Normal)));
671 [item setImage:nsimage];
674 if (action->action->menu()) { //submenu
675 QCocoaMenu *cocoaMenu = static_cast<QCocoaMenu *>(action->action->menu()->platformMenu());
676 NSMenu *subMenu = cocoaMenu->macMenu();
677 if ([subMenu supermenu] && [subMenu supermenu] != [item menu]) {
678 // The menu is already a sub-menu of another one. Cocoa will throw an exception,
679 // in such cases. For the time being, a new QMenu with same set of actions is the
681 action->action->setEnabled(false);
683 [item setSubmenu:subMenu];
685 } else { //respect some other items
687 // No key equivalent set for multiple key QKeySequence.
688 if (accel.count() == 1) {
689 [item setKeyEquivalent:keySequenceToKeyEqivalent(accel)];
690 [item setKeyEquivalentModifierMask:keySequenceModifierMask(accel)];
692 [item setKeyEquivalent:@""];
693 [item setKeyEquivalentModifierMask:NSCommandKeyMask];
697 [item setState:action->action->isChecked() ? NSOnState : NSOffState];
700 void QCocoaMenu::removeAction(QCocoaMenuAction *action)
704 QCocoaAutoReleasePool pool;
705 if (action->merged) {
706 if (reinterpret_cast<QAction *>([action->menuItem tag]) == action->action) {
707 QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
708 [action->menuItem setEnabled:false];
709 if (action->menuItem != [loader quitMenuItem]
710 && action->menuItem != [loader preferencesMenuItem]) {
711 [[action->menuItem menu] removeItem:action->menuItem];
715 [[action->menuItem menu] removeItem:action->menuItem];
717 actionItems.removeAll(action);
720 OSMenuRef QCocoaMenu::macMenu(OSMenuRef merge)
724 menu = qt_mac_create_menu(qtMenu);
726 mergeMenuHash.insert(menu, merge);
728 QList<QAction*> items = qtMenu->actions();
729 for(int i = 0; i < items.count(); i++)
730 addAction(items[i], 0);
731 syncSeparatorsCollapsible(qtMenu->separatorsCollapsible());
739 QCocoaMenu::syncSeparatorsCollapsible(bool collapse)
741 qt_mac_menu_collapseSeparators(menu, collapse);
747 void QCocoaMenu::setMenuEnabled(bool enable)
749 QCocoaAutoReleasePool pool;
751 for (int i = 0; i < actionItems.count(); ++i) {
752 QCocoaMenuAction *menuItem = static_cast<QCocoaMenuAction *>(actionItems.at(i));
753 if (menuItem && menuItem->action && menuItem->action->isEnabled()) {
754 [menuItem->menuItem setEnabled:true];
759 for (NSMenuItem *item in [menu itemArray]) {
760 [item setEnabled:false];
768 This function will return the OSMenuRef used to create the native menu bar
771 If Qt is built against Carbon, the OSMenuRef is a MenuRef that can be used
772 with Carbon's Menu Manager API.
774 If Qt is built against Cocoa, the OSMenuRef is a NSMenu pointer.
776 \warning This function is not portable.
778 \sa QMenuBar::macMenu()
780 /// OSMenuRef QMenu::macMenu(OSMenuRef merge) { return d_func()->macMenu(merge); }
782 /*****************************************************************************
784 *****************************************************************************/
785 typedef QHash<QWidget *, QMenuBar *> MenuBarHash;
786 Q_GLOBAL_STATIC(MenuBarHash, menubars)
787 static QMenuBar *fallback = 0;
789 QCocoaMenuBar::QCocoaMenuBar(QMenuBar *a_qtMenuBar) : menu(0), apple_menu(0), qtMenuBar(a_qtMenuBar)
791 macCreateMenuBar(qtMenuBar->parentWidget());
794 QCocoaMenuBar::~QCocoaMenuBar()
796 for(QList<QCocoaMenuAction*>::Iterator it = actionItems.begin(); it != actionItems.end(); ++it)
798 [apple_menu release];
801 void QCocoaMenuBar::handleReparent(QWidget *newParent)
803 if (macWidgetHasNativeMenubar(newParent)) {
804 // If the new parent got a native menubar from before, keep that
805 // menubar rather than replace it with this one (because a parents
806 // menubar has precedence over children menubars).
808 macCreateMenuBar(newParent);
813 void QCocoaMenuBar::addAction(QAction *action, QAction *beforeAction)
815 if (action->isSeparator() || !menu)
817 QCocoaMenuAction *cocoaAction = new QCocoaMenuAction;
818 cocoaAction->action = action;
819 cocoaAction->ignore_accel = 1;
820 QCocoaMenuAction *cocoaBeforeAction = findAction(beforeAction);
821 addAction(cocoaAction, cocoaBeforeAction);
824 void QCocoaMenuBar::addAction(QCocoaMenuAction *action, QCocoaMenuAction *before)
826 if (!action || !menu)
829 int before_index = actionItems.indexOf(before);
830 if (before_index < 0) {
832 before_index = actionItems.size();
834 actionItems.insert(before_index, action);
836 MenuItemIndex index = actionItems.size()-1;
839 QCocoaAutoReleasePool pool;
840 [action->menu retain];
841 NSMenuItem *newItem = createNSMenuItem(action->action->text());
842 action->menuItem = newItem;
845 [menu insertItem:newItem atIndex:qMax(1, before_index + 1)];
846 index = before_index;
848 [menu addItem:newItem];
850 [newItem setTag:long(static_cast<QAction *>(action->action))];
855 void QCocoaMenuBar::syncAction(QCocoaMenuAction *action)
857 if (!action || !menu)
860 QCocoaAutoReleasePool pool;
861 NSMenuItem *item = action->menuItem;
863 OSMenuRef submenu = 0;
864 bool release_submenu = false;
865 if (action->action->menu()) {
866 QCocoaMenu *cocoaMenu = static_cast<QCocoaMenu *>(action->action->menu()->platformMenu());
871 if ((submenu = cocoaMenu->macMenu(apple_menu))) {
872 if ([submenu supermenu] && [submenu supermenu] != [item menu])
875 [item setSubmenu:submenu];
880 bool visible = actualMenuItemVisibility(this, action);
881 [item setSubmenu: submenu];
882 [submenu setTitle:qt_mac_QStringToNSString(qt_mac_removeMnemonics(action->action->text()))];
883 syncNSMenuItemVisiblity(item, visible);
884 if (release_submenu) { //no pointers to it
888 qWarning("QMenu: No OSMenuRef created for popup menu");
893 void QCocoaMenuBar::removeAction(QCocoaMenuAction *action)
895 if (!action || !menu)
897 QCocoaAutoReleasePool pool;
898 [action->menu removeItem:action->menuItem];
899 actionItems.removeAll(action);
902 void QCocoaMenuBar::syncAction(QAction *a)
904 syncAction(findAction(a));
907 void QCocoaMenuBar::removeAction(QAction *a)
909 removeAction(findAction(a));
912 QCocoaMenuAction *QCocoaMenuBar::findAction(QAction *action) const
914 for (int i = 0; i < actionItems.size(); i++) {
915 QCocoaMenuAction *act = actionItems[i];
916 if (action == act->action)
922 bool QCocoaMenuBar::macWidgetHasNativeMenubar(QWidget *widget)
924 // This function is different from q->isNativeMenuBar(), as
925 // it returns true only if a native menu bar is actually
929 return menubars()->contains(widget->window());
932 void QCocoaMenuBar::macCreateMenuBar(QWidget *parent)
934 static int dontUseNativeMenuBar = -1;
935 // We call the isNativeMenuBar function here
936 // because that will make sure that local overrides
937 // are dealt with correctly. q->isNativeMenuBar() will, if not
938 // overridden, depend on the attribute Qt::AA_DontUseNativeMenuBar:
939 bool qt_mac_no_native_menubar = !qtMenuBar->isNativeMenuBar();
940 if (qt_mac_no_native_menubar == false && dontUseNativeMenuBar < 0) {
941 // The menubar is set to be native. Let's check (one time only
942 // for all menubars) if this is OK with the rest of the environment.
943 // As a result, Qt::AA_DontUseNativeMenuBar is set. NB: the application
944 // might still choose to not respect, or change, this flag.
945 bool isPlugin = QApplication::testAttribute(Qt::AA_MacPluginApplication);
946 bool environmentSaysNo = !qgetenv("QT_MAC_NO_NATIVE_MENUBAR").isEmpty();
947 dontUseNativeMenuBar = isPlugin || environmentSaysNo;
948 QApplication::instance()->setAttribute(Qt::AA_DontUseNativeMenuBar, dontUseNativeMenuBar);
949 qt_mac_no_native_menubar = !qtMenuBar->isNativeMenuBar();
951 if (qt_mac_no_native_menubar == false) {
952 // INVARIANT: Use native menubar.
954 if (!parent && !fallback) {
955 fallback = qtMenuBar;
956 } else if (parent && parent->isWindow()) {
957 menubars()->insert(qtMenuBar->window(), qtMenuBar);
962 void QCocoaMenuBar::macDestroyMenuBar()
964 QCocoaAutoReleasePool pool;
965 if (fallback == qtMenuBar)
967 QWidget *tlw = qtMenuBar->window();
968 menubars()->remove(tlw);
970 if (!qt_mac_current_menubar.qmenubar || qt_mac_current_menubar.qmenubar == qtMenuBar) {
971 QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
972 [loader removeActionsFromAppMenu];
973 QCocoaMenuBar::macUpdateMenuBar();
977 OSMenuRef QCocoaMenuBar::macMenu()
979 if (!qtMenuBar->isNativeMenuBar()) {
982 menu = qt_mac_create_menu(qtMenuBar);
983 ProcessSerialNumber mine, front;
984 if (GetCurrentProcess(&mine) == noErr && GetFrontProcess(&front) == noErr) {
985 if (!qt_mac_no_menubar_merge && !apple_menu) {
986 apple_menu = qt_mac_create_menu(qtMenuBar);
987 [apple_menu setTitle:qt_mac_QStringToNSString(QString(QChar(0x14)))];
988 NSMenuItem *apple_menuItem = [[NSMenuItem alloc] init];
989 [apple_menuItem setSubmenu:menu];
990 [apple_menu addItem:apple_menuItem];
991 [apple_menuItem release];
994 QCocoaMenu::mergeMenuHash.insert(menu, apple_menu);
996 QList<QAction*> items = qtMenuBar->actions();
997 for(int i = 0; i < items.count(); i++)
998 addAction(items[i], 0);
1007 This function will return the OSMenuRef used to create the native menu bar
1008 bindings. This OSMenuRef is then set as the root menu for the Menu
1011 \warning This function is not portable.
1013 \sa QMenu::macMenu()
1015 //OSMenuRef QMenuBar::macMenu() { return d_func()->macMenu(); }
1019 Ancestor function that crosses windows (QWidget::isAncestorOf
1020 only considers widgets within the same window).
1022 static bool qt_mac_is_ancestor(QWidget* possibleAncestor, QWidget *child)
1024 if (!possibleAncestor)
1027 QWidget * current = child->parentWidget();
1028 while (current != 0) {
1029 if (current == possibleAncestor)
1031 current = current->parentWidget();
1038 Returns true if the entries of menuBar should be disabled,
1039 based on the modality type of modalWidget.
1041 static bool qt_mac_should_disable_menu(QMenuBar *menuBar)
1043 QWidget *modalWidget = qApp->activeModalWidget();
1047 if (menuBar && menuBar == menubars()->value(modalWidget))
1048 // The menu bar is owned by the modal widget.
1049 // In that case we should enable it:
1052 // When there is an application modal window on screen, the entries of
1053 // the menubar should be disabled. The exception in Qt is that if the
1054 // modal window is the only window on screen, then we enable the menu bar.
1055 QWidget *w = modalWidget;
1056 QWidgetList topLevelWidgets = QApplication::topLevelWidgets();
1058 if (w->isVisible() && w->windowModality() == Qt::ApplicationModal) {
1059 for (int i=0; i<topLevelWidgets.size(); ++i) {
1060 QWidget *top = topLevelWidgets.at(i);
1061 if (w != top && top->isVisible()) {
1062 // INVARIANT: we found another visible window
1063 // on screen other than our modalWidget. We therefore
1064 // disable the menu bar to follow normal modality logic:
1068 // INVARIANT: We have only one window on screen that happends
1069 // to be application modal. We choose to enable the menu bar
1070 // in that case to e.g. enable the quit menu item.
1073 w = w->parentWidget();
1076 // INVARIANT: modalWidget is window modal. Disable menu entries
1077 // if the menu bar belongs to an ancestor of modalWidget. If menuBar
1078 // is nil, we understand it as the default menu bar set by the nib:
1079 return menuBar ? qt_mac_is_ancestor(menuBar->parentWidget(), modalWidget) : false;
1082 static QWidget *findWindowThatShouldDisplayMenubar()
1084 QWidget *w = qApp->activeWindow();
1087 // We have no active window on screen. Try to
1088 // find a window from the list of top levels:
1089 QWidgetList tlws = QApplication::topLevelWidgets();
1090 for(int i = 0; i < tlws.size(); ++i) {
1091 QWidget *tlw = tlws.at(i);
1092 if ((tlw->isVisible() && tlw->windowType() != Qt::Tool &&
1093 tlw->windowType() != Qt::Popup)) {
1103 static QMenuBar *findMenubarForWindow(QWidget *w)
1107 mb = menubars()->value(w);
1111 //#ifndef QT_NO_MAINWINDOW
1112 QDockWidget *dw = qobject_cast<QDockWidget *>(w);
1114 QMainWindow *mw = qobject_cast<QMainWindow *>(dw->parentWidget());
1115 if (mw && (mb = menubars()->value(mw)))
1120 mb = menubars()->value((w = w->parentWidget()));
1124 // We could not find a menu bar for the window. Lets
1125 // check if we have a global (parentless) menu bar instead:
1132 void qt_mac_clear_menubar()
1134 if (QApplication::testAttribute(Qt::AA_MacPluginApplication))
1137 QCocoaAutoReleasePool pool;
1138 QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
1139 NSMenu *menu = [loader menu];
1140 [loader ensureAppMenuInMenu:menu];
1141 [NSApp setMainMenu:menu];
1142 const bool modal = qt_mac_should_disable_menu(0);
1143 if (qt_mac_current_menubar.qmenubar || modal != qt_mac_current_menubar.modal)
1144 qt_mac_set_modal_state(menu, modal);
1145 qt_mac_current_menubar.qmenubar = 0;
1146 qt_mac_current_menubar.modal = modal;
1152 This function will update the current menu bar and set it as the
1153 active menu bar in the Menu Manager.
1155 \warning This function is not portable.
1157 void QCocoaMenuBar::macUpdateMenuBar()
1159 [getMenuLoader() performSelectorOnMainThread: @selector(qtUpdateMenubar) withObject: nil waitUntilDone: NO];
1162 bool QCocoaMenuBar::macUpdateMenuBarImmediatly()
1165 cancelAllMenuTracking();
1166 QWidget *w = findWindowThatShouldDisplayMenubar();
1167 QMenuBar *mb = findMenubarForWindow(w);
1169 // ### extern bool qt_mac_app_fullscreen; //qapplication_mac.mm
1170 bool qt_mac_app_fullscreen = false;
1171 // We need to see if we are in full screen mode, if so we need to
1172 // switch the full screen mode to be able to show or hide the menubar.
1174 // This case means we are creating a menubar, check if full screen
1175 if(w->isFullScreen()) {
1176 // Ok, switch to showing the menubar when hovering over it.
1177 SetSystemUIMode(kUIModeAllHidden, kUIOptionAutoShowMenuBar);
1178 qt_mac_app_fullscreen = true;
1181 // Removing a menubar
1182 if(w->isFullScreen()) {
1183 // Ok, switch to not showing the menubar when hovering on it
1184 SetSystemUIMode(kUIModeAllHidden, 0);
1185 qt_mac_app_fullscreen = true;
1189 if (mb && mb->isNativeMenuBar()) {
1193 //bool modal = QGuiApplicationPrivate::modalState();
1194 QCocoaAutoReleasePool pool;
1195 if (OSMenuRef menu = reinterpret_cast<QCocoaMenuBar *>(mb->platformMenuBar())->macMenu()) {
1196 QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
1197 [loader ensureAppMenuInMenu:menu];
1198 [NSApp setMainMenu:menu];
1199 syncMenuBarItemsVisiblity(reinterpret_cast<QCocoaMenuBar *>(mb->platformMenuBar()));
1201 if (OSMenuRef tmpMerge = QCocoaMenu::mergeMenuHash.value(menu)) {
1202 if (QMenuMergeList *mergeList
1203 = QCocoaMenu::mergeMenuItemsHash.value(tmpMerge)) {
1204 const int mergeListSize = mergeList->size();
1206 for (int i = 0; i < mergeListSize; ++i) {
1207 const QMenuMergeItem &mergeItem = mergeList->at(i);
1208 // Ideally we would call QCocoaMenu::syncAction, but that requires finding
1209 // the original QMen and likely doing more work than we need.
1210 // For example, enabled is handled below.
1211 [mergeItem.menuItem setTag:reinterpret_cast<long>(
1212 static_cast<QAction *>(mergeItem.action->action))];
1213 [mergeItem.menuItem setHidden:!(mergeItem.action->action->isVisible())];
1217 // Check if menu is modally shaddowed and should be disabled:
1218 modal = qt_mac_should_disable_menu(mb);
1219 if (mb != qt_mac_current_menubar.qmenubar || modal != qt_mac_current_menubar.modal)
1220 qt_mac_set_modal_state(menu, modal);
1222 qt_mac_current_menubar.qmenubar = mb;
1223 qt_mac_current_menubar.modal = modal;
1225 } else if (qt_mac_current_menubar.qmenubar && qt_mac_current_menubar.qmenubar->isNativeMenuBar()) {
1226 // INVARIANT: The currently active menu bar (if any) is not native. But we do have a
1227 // native menu bar from before. So we need to decide whether or not is should be enabled:
1228 const bool modal = qt_mac_should_disable_menu(qt_mac_current_menubar.qmenubar);
1229 if (modal != qt_mac_current_menubar.modal) {
1231 if (OSMenuRef menu = reinterpret_cast<QCocoaMenuBar *>(qt_mac_current_menubar.qmenubar->platformMenuBar())->macMenu()) {
1232 QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
1233 [loader ensureAppMenuInMenu:menu];
1234 [NSApp setMainMenu:menu];
1235 syncMenuBarItemsVisiblity(reinterpret_cast<QCocoaMenuBar *>(qt_mac_current_menubar.qmenubar->platformMenuBar()));
1236 qt_mac_set_modal_state(menu, modal);
1238 qt_mac_current_menubar.modal = modal;
1243 qt_mac_clear_menubar();
1248 QHash<OSMenuRef, OSMenuRef> QCocoaMenu::mergeMenuHash;
1249 QHash<OSMenuRef, QMenuMergeList*> QCocoaMenu::mergeMenuItemsHash;
1251 bool QCocoaMenu::merged(const QAction *action) const
1253 if (OSMenuRef merge = mergeMenuHash.value(menu)) {
1254 if (QMenuMergeList *list = mergeMenuItemsHash.value(merge)) {
1255 for(int i = 0; i < list->size(); ++i) {
1256 const QMenuMergeItem &item = list->at(i);
1257 if (item.action->action == action)
1265 //creation of the OSMenuRef
1266 static OSMenuRef qt_mac_create_menu(QWidget *w)
1269 if (QMenu *qmenu = qobject_cast<QMenu *>(w)){
1270 ret = [[QT_MANGLE_NAMESPACE(QNativeCocoaMenu) alloc] initWithQMenu:qmenu];
1272 ret = [[NSMenu alloc] init];