source: trunk/src/gui/widgets/qmenu_mac.mm@ 324

Last change on this file since 324 was 2, checked in by Dmitry A. Kuminov, 16 years ago

Initially imported qt-all-opensource-src-4.5.1 from Trolltech.

File size: 72.4 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4** Contact: Qt Software Information ([email protected])
5**
6** This file is part of the QtGui module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial Usage
10** Licensees holding valid Qt Commercial licenses may use this file in
11** accordance with the Qt Commercial License Agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and Nokia.
14**
15** GNU Lesser General Public License Usage
16** Alternatively, this file may be used under the terms of the GNU Lesser
17** General Public License version 2.1 as published by the Free Software
18** Foundation and appearing in the file LICENSE.LGPL included in the
19** packaging of this file. Please review the following information to
20** ensure the GNU Lesser General Public License version 2.1 requirements
21** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
22**
23** In addition, as a special exception, Nokia gives you certain
24** additional rights. These rights are described in the Nokia Qt LGPL
25** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
26** package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you are unsure which license is appropriate for your use, please
37** contact the sales department at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qmenu.h"
43#include "qhash.h"
44#include <qdebug.h>
45#include "qapplication.h"
46#include <private/qt_mac_p.h>
47#include "qregexp.h"
48#include "qmainwindow.h"
49#include "qdockwidget.h"
50#include "qtoolbar.h"
51#include "qevent.h"
52#include "qstyle.h"
53#include "qwidgetaction.h"
54#include "qmacnativewidget_mac.h"
55
56#include <private/qapplication_p.h>
57#include <private/qcocoaapplication_mac_p.h>
58#include <private/qmenu_p.h>
59#include <private/qmenubar_p.h>
60#include <private/qcocoamenuloader_mac_p.h>
61#include <private/qcocoamenu_mac_p.h>
62#include <private/qt_cocoa_helpers_mac_p.h>
63#include <Cocoa/Cocoa.h>
64
65QT_BEGIN_NAMESPACE
66
67/*****************************************************************************
68 QMenu debug facilities
69 *****************************************************************************/
70
71/*****************************************************************************
72 QMenu globals
73 *****************************************************************************/
74bool qt_mac_no_native_menubar = false;
75bool qt_mac_no_menubar_merge = false;
76bool qt_mac_quit_menu_item_enabled = true;
77int qt_mac_menus_open_count = 0;
78
79static OSMenuRef qt_mac_create_menu(QWidget *w);
80
81#ifndef QT_MAC_USE_COCOA
82static uint qt_mac_menu_static_cmd_id = 'QT00';
83const UInt32 kMenuCreatorQt = 'cute';
84enum {
85 kMenuPropertyQAction = 'QAcT',
86 kMenuPropertyQWidget = 'QWId',
87 kMenuPropertyCausedQWidget = 'QCAU',
88 kMenuPropertyMergeMenu = 'QApP',
89 kMenuPropertyMergeList = 'QAmL',
90 kMenuPropertyWidgetActionWidget = 'QWid',
91 kMenuPropertyWidgetMenu = 'QWMe',
92
93 kHICommandAboutQt = 'AOQT',
94 kHICommandCustomMerge = 'AQt0'
95};
96#endif
97
98static struct {
99 QPointer<QMenuBar> qmenubar;
100 bool modal;
101} qt_mac_current_menubar = { 0, false };
102
103
104
105
106/*****************************************************************************
107 Externals
108 *****************************************************************************/
109extern OSViewRef qt_mac_hiview_for(const QWidget *w); //qwidget_mac.cpp
110extern HIViewRef qt_mac_hiview_for(OSWindowRef w); //qwidget_mac.cpp
111extern IconRef qt_mac_create_iconref(const QPixmap &px); //qpixmap_mac.cpp
112extern QWidget * mac_keyboard_grabber; //qwidget_mac.cpp
113extern bool qt_sendSpontaneousEvent(QObject*, QEvent*); //qapplication_xxx.cpp
114RgnHandle qt_mac_get_rgn(); //qregion_mac.cpp
115void qt_mac_dispose_rgn(RgnHandle r); //qregion_mac.cpp
116
117/*****************************************************************************
118 QMenu utility functions
119 *****************************************************************************/
120bool qt_mac_watchingAboutToShow(QMenu *menu)
121{
122 return menu && menu->receivers(SIGNAL(aboutToShow()));
123}
124
125static int qt_mac_CountMenuItems(OSMenuRef menu)
126{
127 if (menu) {
128#ifndef QT_MAC_USE_COCOA
129 int ret = 0;
130 const int items = CountMenuItems(menu);
131 for(int i = 0; i < items; i++) {
132 MenuItemAttributes attr;
133 if (GetMenuItemAttributes(menu, i+1, &attr) == noErr &&
134 attr & kMenuItemAttrHidden)
135 continue;
136 ++ret;
137 }
138 return ret;
139#else
140 return [menu numberOfItems];
141#endif
142 }
143 return 0;
144}
145
146static bool actualMenuItemVisibility(const QMenuBarPrivate::QMacMenuBarPrivate *mbp,
147 const QMacMenuAction *action)
148{
149 bool visible = action->action->isVisible();
150 if (visible && action->action->text() == QString(QChar(0x14)))
151 return false;
152 if (visible && action->action->menu() && !action->action->menu()->actions().isEmpty() &&
153 !qt_mac_CountMenuItems(action->action->menu()->macMenu(mbp->apple_menu)) &&
154 !qt_mac_watchingAboutToShow(action->action->menu())) {
155 return false;
156 }
157 return visible;
158}
159
160#ifndef QT_MAC_USE_COCOA
161bool qt_mac_activate_action(MenuRef menu, uint command, QAction::ActionEvent action_e, bool by_accel)
162{
163 //fire event
164 QMacMenuAction *action = 0;
165 if (GetMenuCommandProperty(menu, command, kMenuCreatorQt, kMenuPropertyQAction, sizeof(action), 0, &action) != noErr) {
166 QMenuMergeList *list = 0;
167 GetMenuItemProperty(menu, 0, kMenuCreatorQt, kMenuPropertyMergeList,
168 sizeof(list), 0, &list);
169 if (!list && qt_mac_current_menubar.qmenubar) {
170 MenuRef apple_menu = qt_mac_current_menubar.qmenubar->d_func()->mac_menubar->apple_menu;
171 GetMenuItemProperty(apple_menu, 0, kMenuCreatorQt, kMenuPropertyMergeList, sizeof(list), 0, &list);
172 if (list)
173 menu = apple_menu;
174 }
175 if (list) {
176 for(int i = 0; i < list->size(); ++i) {
177 QMenuMergeItem item = list->at(i);
178 if (item.command == command && item.action) {
179 action = item.action;
180 break;
181 }
182 }
183 }
184 if (!action)
185 return false;
186 }
187
188 if (action_e == QAction::Trigger && by_accel && action->ignore_accel) //no, not a real accel (ie tab)
189 return false;
190
191 // Unhighlight the highlighted menu item before triggering the action to
192 // prevent items from staying highlighted while a modal dialog is shown.
193 // This also fixed the problem that parentless modal dialogs leave
194 // the menu item highlighted (since the menu bar is cleared for these types of dialogs).
195 if (action_e == QAction::Trigger)
196 HiliteMenu(0);
197
198 action->action->activate(action_e);
199
200 //now walk up firing for each "caused" widget (like in the platform independent menu)
201 QWidget *caused = 0;
202 if (GetMenuItemProperty(menu, 0, kMenuCreatorQt, kMenuPropertyCausedQWidget, sizeof(caused), 0, &caused) == noErr) {
203 MenuRef caused_menu = 0;
204 if (QMenu *qmenu2 = qobject_cast<QMenu*>(caused))
205 caused_menu = qmenu2->macMenu();
206 else if (QMenuBar *qmenubar2 = qobject_cast<QMenuBar*>(caused))
207 caused_menu = qmenubar2->macMenu();
208 else
209 caused_menu = 0;
210 while(caused_menu) {
211 //fire
212 QWidget *widget = 0;
213 GetMenuItemProperty(caused_menu, 0, kMenuCreatorQt, kMenuPropertyQWidget, sizeof(widget), 0, &widget);
214 if (QMenu *qmenu = qobject_cast<QMenu*>(widget)) {
215 if (action_e == QAction::Trigger) {
216 emit qmenu->triggered(action->action);
217 } else if (action_e == QAction::Hover) {
218 action->action->showStatusText(widget);
219 emit qmenu->hovered(action->action);
220 }
221 } else if (QMenuBar *qmenubar = qobject_cast<QMenuBar*>(widget)) {
222 if (action_e == QAction::Trigger) {
223 emit qmenubar->triggered(action->action);
224 } else if (action_e == QAction::Hover) {
225 action->action->showStatusText(widget);
226 emit qmenubar->hovered(action->action);
227 }
228 break; //nothing more..
229 }
230
231 //walk up
232 if (GetMenuItemProperty(caused_menu, 0, kMenuCreatorQt, kMenuPropertyCausedQWidget,
233 sizeof(caused), 0, &caused) != noErr)
234 break;
235 if (QMenu *qmenu2 = qobject_cast<QMenu*>(caused))
236 caused_menu = qmenu2->macMenu();
237 else if (QMenuBar *qmenubar2 = qobject_cast<QMenuBar*>(caused))
238 caused_menu = qmenubar2->macMenu();
239 else
240 caused_menu = 0;
241 }
242 }
243 return true;
244}
245
246//lookup a QMacMenuAction in a menu
247static int qt_mac_menu_find_action(MenuRef menu, MenuCommand cmd)
248{
249 MenuItemIndex ret_idx;
250 MenuRef ret_menu;
251 if (GetIndMenuItemWithCommandID(menu, cmd, 1, &ret_menu, &ret_idx) == noErr) {
252 if (ret_menu == menu)
253 return (int)ret_idx;
254 }
255 return -1;
256}
257static int qt_mac_menu_find_action(MenuRef menu, QMacMenuAction *action)
258{
259 return qt_mac_menu_find_action(menu, action->command);
260}
261
262typedef QMultiHash<OSMenuRef, EventHandlerRef> EventHandlerHash;
263Q_GLOBAL_STATIC(EventHandlerHash, menu_eventHandlers_hash)
264
265static EventTypeSpec widget_in_menu_events[] = {
266 { kEventClassMenu, kEventMenuMeasureItemWidth },
267 { kEventClassMenu, kEventMenuMeasureItemHeight },
268 { kEventClassMenu, kEventMenuDrawItem },
269 { kEventClassMenu, kEventMenuCalculateSize }
270};
271
272static OSStatus qt_mac_widget_in_menu_eventHandler(EventHandlerCallRef er, EventRef event, void *)
273{
274 UInt32 ekind = GetEventKind(event);
275 UInt32 eclass = GetEventClass(event);
276 OSStatus result = eventNotHandledErr;
277 switch (eclass) {
278 case kEventClassMenu:
279 switch (ekind) {
280 default:
281 break;
282 case kEventMenuMeasureItemWidth: {
283 MenuItemIndex item;
284 GetEventParameter(event, kEventParamMenuItemIndex, typeMenuItemIndex,
285 0, sizeof(item), 0, &item);
286 OSMenuRef menu;
287 GetEventParameter(event, kEventParamDirectObject, typeMenuRef, 0, sizeof(menu), 0, &menu);
288 QWidget *widget;
289 if (GetMenuItemProperty(menu, item, kMenuCreatorQt, kMenuPropertyWidgetActionWidget,
290 sizeof(widget), 0, &widget) == noErr) {
291 short width = short(widget->sizeHint().width());
292 SetEventParameter(event, kEventParamMenuItemWidth, typeSInt16,
293 sizeof(short), &width);
294 result = noErr;
295 }
296 break; }
297 case kEventMenuMeasureItemHeight: {
298 MenuItemIndex item;
299 GetEventParameter(event, kEventParamMenuItemIndex, typeMenuItemIndex,
300 0, sizeof(item), 0, &item);
301 OSMenuRef menu;
302 GetEventParameter(event, kEventParamDirectObject, typeMenuRef, 0, sizeof(menu), 0, &menu);
303 QWidget *widget;
304 if (GetMenuItemProperty(menu, item, kMenuCreatorQt, kMenuPropertyWidgetActionWidget,
305 sizeof(widget), 0, &widget) == noErr && widget) {
306 short height = short(widget->sizeHint().height());
307 SetEventParameter(event, kEventParamMenuItemHeight, typeSInt16,
308 sizeof(short), &height);
309 result = noErr;
310 }
311 break; }
312 case kEventMenuDrawItem:
313 result = noErr;
314 break;
315 case kEventMenuCalculateSize: {
316 result = CallNextEventHandler(er, event);
317 if (result == noErr) {
318 OSMenuRef menu;
319 GetEventParameter(event, kEventParamDirectObject, typeMenuRef, 0, sizeof(menu), 0, &menu);
320 HIViewRef content;
321 HIMenuGetContentView(menu, kThemeMenuTypePullDown, &content);
322 UInt16 count = CountMenuItems(menu);
323 for (MenuItemIndex i = 1; i <= count; ++i) {
324 QWidget *widget;
325 if (GetMenuItemProperty(menu, i, kMenuCreatorQt, kMenuPropertyWidgetActionWidget,
326 sizeof(widget), 0, &widget) == noErr && widget) {
327 RgnHandle itemRgn = qt_mac_get_rgn();
328 GetControlRegion(content, i, itemRgn);
329
330 Rect bounds;
331 GetRegionBounds( itemRgn, &bounds );
332 qt_mac_dispose_rgn(itemRgn);
333 widget->setGeometry(bounds.left, bounds.top,
334 bounds.right - bounds.left, bounds.bottom - bounds.top);
335 }
336 }
337 }
338 break; }
339 }
340 }
341 return result;
342}
343
344//handling of events for menurefs created by Qt..
345static EventTypeSpec menu_events[] = {
346 { kEventClassCommand, kEventCommandProcess },
347 { kEventClassMenu, kEventMenuTargetItem },
348 { kEventClassMenu, kEventMenuOpening },
349 { kEventClassMenu, kEventMenuClosed }
350};
351
352// Special case for kEventMenuMatchKey, see qt_mac_create_menu below.
353static EventTypeSpec menu_menu_events[] = {
354 { kEventClassMenu, kEventMenuMatchKey }
355};
356
357OSStatus qt_mac_menu_event(EventHandlerCallRef er, EventRef event, void *)
358{
359 QScopedLoopLevelCounter loopLevelCounter(QApplicationPrivate::instance()->threadData);
360
361 bool handled_event = true;
362 UInt32 ekind = GetEventKind(event), eclass = GetEventClass(event);
363 switch(eclass) {
364 case kEventClassCommand:
365 if (ekind == kEventCommandProcess) {
366 UInt32 context;
367 GetEventParameter(event, kEventParamMenuContext, typeUInt32,
368 0, sizeof(context), 0, &context);
369 HICommand cmd;
370 GetEventParameter(event, kEventParamDirectObject, typeHICommand,
371 0, sizeof(cmd), 0, &cmd);
372 if (!mac_keyboard_grabber && (context & kMenuContextKeyMatching)) {
373 QMacMenuAction *action = 0;
374 if (GetMenuCommandProperty(cmd.menu.menuRef, cmd.commandID, kMenuCreatorQt,
375 kMenuPropertyQAction, sizeof(action), 0, &action) == noErr) {
376 QWidget *widget = 0;
377 if (qApp->activePopupWidget())
378 widget = (qApp->activePopupWidget()->focusWidget() ?
379 qApp->activePopupWidget()->focusWidget() : qApp->activePopupWidget());
380 else if (QApplicationPrivate::focus_widget)
381 widget = QApplicationPrivate::focus_widget;
382 if (widget) {
383 int key = action->action->shortcut();
384 QKeyEvent accel_ev(QEvent::ShortcutOverride, (key & (~Qt::KeyboardModifierMask)),
385 Qt::KeyboardModifiers(key & Qt::KeyboardModifierMask));
386 accel_ev.ignore();
387 qt_sendSpontaneousEvent(widget, &accel_ev);
388 if (accel_ev.isAccepted()) {
389 handled_event = false;
390 break;
391 }
392 }
393 }
394 }
395 handled_event = qt_mac_activate_action(cmd.menu.menuRef, cmd.commandID,
396 QAction::Trigger, context & kMenuContextKeyMatching);
397 }
398 break;
399 case kEventClassMenu: {
400 MenuRef menu;
401 GetEventParameter(event, kEventParamDirectObject, typeMenuRef, NULL, sizeof(menu), NULL, &menu);
402 if (ekind == kEventMenuMatchKey) {
403 // Don't activate any actions if we are showing a native modal dialog,
404 // the key events should go to the dialog in this case.
405 if (QApplicationPrivate::native_modal_dialog_active)
406 return menuItemNotFoundErr;
407
408 handled_event = false;
409 } else if (ekind == kEventMenuTargetItem) {
410 MenuCommand command;
411 GetEventParameter(event, kEventParamMenuCommand, typeMenuCommand,
412 0, sizeof(command), 0, &command);
413 handled_event = qt_mac_activate_action(menu, command, QAction::Hover, false);
414 } else if (ekind == kEventMenuOpening || ekind == kEventMenuClosed) {
415 qt_mac_menus_open_count += (ekind == kEventMenuOpening) ? 1 : -1;
416 MenuRef mr;
417 GetEventParameter(event, kEventParamDirectObject, typeMenuRef,
418 0, sizeof(mr), 0, &mr);
419
420 QWidget *widget = 0;
421 if (GetMenuItemProperty(mr, 0, kMenuCreatorQt, kMenuPropertyQWidget, sizeof(widget), 0, &widget) == noErr) {
422 if (QMenu *qmenu = qobject_cast<QMenu*>(widget)) {
423 handled_event = true;
424 if (ekind == kEventMenuOpening) {
425 emit qmenu->aboutToShow();
426
427 int merged = 0;
428 const QMenuPrivate::QMacMenuPrivate *mac_menu = qmenu->d_func()->mac_menu;
429 for(int i = 0; i < mac_menu->actionItems.size(); ++i) {
430 QMacMenuAction *action = mac_menu->actionItems.at(i);
431 if (action->action->isSeparator()) {
432 bool hide = false;
433 if(!action->action->isVisible()) {
434 hide = true;
435 } else if (merged && merged == i) {
436 hide = true;
437 } else {
438 for(int l = i+1; l < mac_menu->actionItems.size(); ++l) {
439 QMacMenuAction *action = mac_menu->actionItems.at(l);
440 if (action->merged) {
441 hide = true;
442 } else if (action->action->isSeparator()) {
443 if (hide)
444 break;
445 } else if (!action->merged) {
446 hide = false;
447 break;
448 }
449 }
450 }
451
452 const int index = qt_mac_menu_find_action(mr, action);
453 if (hide) {
454 ++merged;
455 ChangeMenuItemAttributes(mr, index, kMenuItemAttrHidden, 0);
456 } else {
457 ChangeMenuItemAttributes(mr, index, 0, kMenuItemAttrHidden);
458 }
459 } else if (action->merged) {
460 ++merged;
461 }
462 }
463 } else {
464 emit qmenu->aboutToHide();
465 }
466 }
467 }
468 } else {
469 handled_event = false;
470 }
471 break; }
472 default:
473 handled_event = false;
474 break;
475 }
476 if (!handled_event) //let the event go through
477 return CallNextEventHandler(er, event);
478 return noErr; //we eat the event
479}
480static EventHandlerRef mac_menu_event_handler = 0;
481static EventHandlerUPP mac_menu_eventUPP = 0;
482static void qt_mac_cleanup_menu_event()
483{
484 if (mac_menu_event_handler) {
485 RemoveEventHandler(mac_menu_event_handler);
486 mac_menu_event_handler = 0;
487 }
488 if (mac_menu_eventUPP) {
489 DisposeEventHandlerUPP(mac_menu_eventUPP);
490 mac_menu_eventUPP = 0;
491 }
492}
493static inline void qt_mac_create_menu_event_handler()
494{
495 if (!mac_menu_event_handler) {
496 mac_menu_eventUPP = NewEventHandlerUPP(qt_mac_menu_event);
497 InstallEventHandler(GetApplicationEventTarget(), mac_menu_eventUPP,
498 GetEventTypeCount(menu_events), menu_events, 0,
499 &mac_menu_event_handler);
500 qAddPostRoutine(qt_mac_cleanup_menu_event);
501 }
502}
503
504
505//enabling of commands
506static void qt_mac_command_set_enabled(MenuRef menu, UInt32 cmd, bool b)
507{
508 if (cmd == kHICommandQuit)
509 qt_mac_quit_menu_item_enabled = b;
510
511 if (b) {
512 EnableMenuCommand(menu, cmd);
513 if (MenuRef dock_menu = GetApplicationDockTileMenu())
514 EnableMenuCommand(dock_menu, cmd);
515 } else {
516 DisableMenuCommand(menu, cmd);
517 if (MenuRef dock_menu = GetApplicationDockTileMenu())
518 DisableMenuCommand(dock_menu, cmd);
519 }
520}
521
522static bool qt_mac_auto_apple_menu(MenuCommand cmd)
523{
524 return (cmd == kHICommandPreferences || cmd == kHICommandQuit);
525}
526
527static void qt_mac_get_accel(quint32 accel_key, quint32 *modif, quint32 *key) {
528 if (modif) {
529 *modif = 0;
530 if ((accel_key & Qt::CTRL) != Qt::CTRL)
531 *modif |= kMenuNoCommandModifier;
532 if ((accel_key & Qt::META) == Qt::META)
533 *modif |= kMenuControlModifier;
534 if ((accel_key & Qt::ALT) == Qt::ALT)
535 *modif |= kMenuOptionModifier;
536 if ((accel_key & Qt::SHIFT) == Qt::SHIFT)
537 *modif |= kMenuShiftModifier;
538 }
539
540 accel_key &= ~(Qt::MODIFIER_MASK | Qt::UNICODE_ACCEL);
541 if (key) {
542 *key = 0;
543 if (accel_key == Qt::Key_Return)
544 *key = kMenuReturnGlyph;
545 else if (accel_key == Qt::Key_Enter)
546 *key = kMenuEnterGlyph;
547 else if (accel_key == Qt::Key_Tab)
548 *key = kMenuTabRightGlyph;
549 else if (accel_key == Qt::Key_Backspace)
550 *key = kMenuDeleteLeftGlyph;
551 else if (accel_key == Qt::Key_Delete)
552 *key = kMenuDeleteRightGlyph;
553 else if (accel_key == Qt::Key_Escape)
554 *key = kMenuEscapeGlyph;
555 else if (accel_key == Qt::Key_PageUp)
556 *key = kMenuPageUpGlyph;
557 else if (accel_key == Qt::Key_PageDown)
558 *key = kMenuPageDownGlyph;
559 else if (accel_key == Qt::Key_Up)
560 *key = kMenuUpArrowGlyph;
561 else if (accel_key == Qt::Key_Down)
562 *key = kMenuDownArrowGlyph;
563 else if (accel_key == Qt::Key_Left)
564 *key = kMenuLeftArrowGlyph;
565 else if (accel_key == Qt::Key_Right)
566 *key = kMenuRightArrowGlyph;
567 else if (accel_key == Qt::Key_CapsLock)
568 *key = kMenuCapsLockGlyph;
569 else if (accel_key >= Qt::Key_F1 && accel_key <= Qt::Key_F15)
570 *key = (accel_key - Qt::Key_F1) + kMenuF1Glyph;
571 else if (accel_key == Qt::Key_Home)
572 *key = kMenuNorthwestArrowGlyph;
573 else if (accel_key == Qt::Key_End)
574 *key = kMenuSoutheastArrowGlyph;
575 }
576}
577#else // Cocoa
578static inline void syncNSMenuItemVisiblity(NSMenuItem *menuItem, bool actionVisibility)
579{
580 [menuItem setHidden:NO];
581 [menuItem setHidden:YES];
582 [menuItem setHidden:!actionVisibility];
583}
584
585static inline void syncMenuBarItemsVisiblity(const QMenuBarPrivate::QMacMenuBarPrivate *mac_menubar)
586{
587 const QList<QMacMenuAction *> &menubarActions = mac_menubar->actionItems;
588 for (int i = 0; i < menubarActions.size(); ++i) {
589 const QMacMenuAction *action = menubarActions.at(i);
590 syncNSMenuItemVisiblity(action->menuItem, actualMenuItemVisibility(mac_menubar, action));
591 }
592}
593
594static inline QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *getMenuLoader()
595{
596 return [NSApp QT_MANGLE_NAMESPACE(qt_qcocoamenuLoader)];
597}
598
599static NSMenuItem *createNSMenuItem(const QString &title)
600{
601 NSMenuItem *item = [[NSMenuItem alloc]
602 initWithTitle:reinterpret_cast<const NSString *>(static_cast<CFStringRef>(QCFString(title)))
603 action:@selector(qtDispatcherToQAction:) keyEquivalent:@""];
604 [item setTarget:getMenuLoader()];
605 return item;
606}
607#endif
608
609
610
611// helper that recurses into a menu structure and en/dis-ables them
612void qt_mac_set_modal_state_helper_recursive(OSMenuRef menu, OSMenuRef merge, bool on)
613{
614#ifndef QT_MAC_USE_COCOA
615 for (int i = 0; i < CountMenuItems(menu); i++) {
616 OSMenuRef submenu;
617 GetMenuItemHierarchicalMenu(menu, i+1, &submenu);
618 if (submenu != merge) {
619 if (submenu)
620 qt_mac_set_modal_state_helper_recursive(submenu, merge, on);
621 if (on)
622 DisableMenuItem(submenu, 0);
623 else
624 EnableMenuItem(submenu, 0);
625 }
626 }
627#else
628 for (NSMenuItem *item in [menu itemArray]) {
629 OSMenuRef submenu = [item submenu];
630 if (submenu != merge) {
631 if (submenu)
632 qt_mac_set_modal_state_helper_recursive(submenu, merge, on);
633 if (!on) {
634 // The item should follow what the QAction has.
635 if ([item tag]) {
636 QAction *action = reinterpret_cast<QAction *>([item tag]);
637 [item setEnabled:action->isEnabled()];
638 } else {
639 [item setEnabled:YES];
640 }
641 } else {
642 [item setEnabled:NO];
643 }
644 }
645 }
646#endif
647}
648
649//toggling of modal state
650static void qt_mac_set_modal_state(OSMenuRef menu, bool on)
651{
652#ifndef QT_MAC_USE_COCOA
653 OSMenuRef merge = 0;
654 GetMenuItemProperty(menu, 0, kMenuCreatorQt, kMenuPropertyMergeMenu,
655 sizeof(merge), 0, &merge);
656
657 qt_mac_set_modal_state_helper_recursive(menu, merge, on);
658
659 UInt32 commands[] = { kHICommandQuit, kHICommandPreferences, kHICommandAbout, kHICommandAboutQt, 0 };
660 for(int c = 0; commands[c]; c++) {
661 bool enabled = !on;
662 if (enabled) {
663 QMacMenuAction *action = 0;
664 GetMenuCommandProperty(menu, commands[c], kMenuCreatorQt, kMenuPropertyQAction,
665 sizeof(action), 0, &action);
666 if (!action && merge) {
667 GetMenuCommandProperty(merge, commands[c], kMenuCreatorQt, kMenuPropertyQAction,
668 sizeof(action), 0, &action);
669 if (!action) {
670 QMenuMergeList *list = 0;
671 GetMenuItemProperty(merge, 0, kMenuCreatorQt, kMenuPropertyMergeList,
672 sizeof(list), 0, &list);
673 for(int i = 0; list && i < list->size(); ++i) {
674 QMenuMergeItem item = list->at(i);
675 if (item.command == commands[c] && item.action) {
676 action = item.action;
677 break;
678 }
679 }
680 }
681 }
682
683 if (!action) {
684 if (commands[c] != kHICommandQuit)
685 enabled = false;
686 } else {
687 enabled = action->action ? action->action->isEnabled() : 0;
688 }
689 }
690 qt_mac_command_set_enabled(menu, commands[c], enabled);
691 }
692#else
693 OSMenuRef merge = QMenuPrivate::mergeMenuHash.value(menu);
694 qt_mac_set_modal_state_helper_recursive(menu, merge, on);
695 // I'm ignoring the special items now, since they should get handled via a syncAction()
696#endif
697}
698
699bool qt_mac_menubar_is_open()
700{
701 return qt_mac_menus_open_count > 0;
702}
703
704void qt_mac_clear_menubar()
705{
706#ifndef QT_MAC_USE_COCOA
707 MenuRef clear_menu = 0;
708 if (CreateNewMenu(0, 0, &clear_menu) == noErr) {
709 SetRootMenu(clear_menu);
710 ReleaseMenu(clear_menu);
711 } else {
712 qWarning("QMenu: Internal error at %s:%d", __FILE__, __LINE__);
713 }
714 ClearMenuBar();
715 qt_mac_command_set_enabled(0, kHICommandPreferences, false);
716 InvalMenuBar();
717#else
718 QMacCocoaAutoReleasePool pool;
719 QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
720 NSMenu *menu = [loader menu];
721 [loader ensureAppMenuInMenu:menu];
722 [NSApp setMainMenu:menu];
723#endif
724}
725
726
727QMacMenuAction::~QMacMenuAction()
728{
729#ifdef QT_MAC_USE_COCOA
730 [menu release];
731 [menuItem setTag:nil];
732 [menuItem release];
733#endif
734}
735
736#ifndef QT_MAC_USE_COCOA
737static MenuCommand qt_mac_menu_merge_action(MenuRef merge, QMacMenuAction *action)
738#else
739static NSMenuItem *qt_mac_menu_merge_action(OSMenuRef merge, QMacMenuAction *action)
740#endif
741{
742 if (qt_mac_no_menubar_merge || action->action->menu() || action->action->isSeparator()
743 || action->action->menuRole() == QAction::NoRole)
744 return 0;
745
746 QString t = qt_mac_removeMnemonics(action->action->text().toLower());
747 int st = t.lastIndexOf(QLatin1Char('\t'));
748 if (st != -1)
749 t.remove(st, t.length()-st);
750 t.replace(QRegExp(QString::fromLatin1("\\.*$")), QLatin1String("")); //no ellipses
751 //now the fun part
752#ifndef QT_MAC_USE_COCOA
753 MenuCommand ret = 0;
754#else
755 NSMenuItem *ret = 0;
756 QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
757#endif
758 switch (action->action->menuRole()) {
759 case QAction::NoRole:
760 ret = 0;
761 break;
762 case QAction::ApplicationSpecificRole:
763#ifndef QT_MAC_USE_COCOA
764 {
765 QMenuMergeList *list = 0;
766 if (GetMenuItemProperty(merge, 0, kMenuCreatorQt, kMenuPropertyMergeList,
767 sizeof(list), 0, &list) == noErr && list) {
768 MenuCommand lastCustom = kHICommandCustomMerge;
769 for(int i = 0; i < list->size(); ++i) {
770 QMenuMergeItem item = list->at(i);
771 if (item.command == lastCustom)
772 ++lastCustom;
773 }
774 ret = lastCustom;
775 } else {
776 // The list hasn't been created, so, must be the first one.
777 ret = kHICommandCustomMerge;
778 }
779 }
780#else
781 ret = [loader appSpecificMenuItem];
782#endif
783 break;
784 case QAction::AboutRole:
785#ifndef QT_MAC_USE_COCOA
786 ret = kHICommandAbout;
787#else
788 ret = [loader aboutMenuItem];
789#endif
790 break;
791 case QAction::AboutQtRole:
792#ifndef QT_MAC_USE_COCOA
793 ret = kHICommandAboutQt;
794#else
795 ret = [loader aboutQtMenuItem];
796#endif
797 break;
798 case QAction::QuitRole:
799#ifndef QT_MAC_USE_COCOA
800 ret = kHICommandQuit;
801#else
802 ret = [loader quitMenuItem];
803#endif
804 break;
805 case QAction::PreferencesRole:
806#ifndef QT_MAC_USE_COCOA
807 ret = kHICommandPreferences;
808#else
809 ret = [loader preferencesMenuItem];
810#endif
811 break;
812 case QAction::TextHeuristicRole: {
813 QString aboutString = QMenuBar::tr("About").toLower();
814 if (t.startsWith(aboutString) || t.endsWith(aboutString)) {
815 if (t.indexOf(QRegExp(QString::fromLatin1("qt$"), Qt::CaseInsensitive)) == -1) {
816#ifndef QT_MAC_USE_COCOA
817 ret = kHICommandAbout;
818#else
819 ret = [loader aboutMenuItem];
820#endif
821 } else {
822#ifndef QT_MAC_USE_COCOA
823 ret = kHICommandAboutQt;
824#else
825 ret = [loader aboutQtMenuItem];
826#endif
827 }
828 } else if (t.startsWith(QMenuBar::tr("Config").toLower())
829 || t.startsWith(QMenuBar::tr("Preference").toLower())
830 || t.startsWith(QMenuBar::tr("Options").toLower())
831 || t.startsWith(QMenuBar::tr("Setting").toLower())
832 || t.startsWith(QMenuBar::tr("Setup").toLower())) {
833#ifndef QT_MAC_USE_COCOA
834 ret = kHICommandPreferences;
835#else
836 ret = [loader preferencesMenuItem];
837#endif
838 } else if (t.startsWith(QMenuBar::tr("Quit").toLower())
839 || t.startsWith(QMenuBar::tr("Exit").toLower())) {
840#ifndef QT_MAC_USE_COCOA
841 ret = kHICommandQuit;
842#else
843 ret = [loader quitMenuItem];
844#endif
845 }
846 }
847 break;
848 }
849
850#ifndef QT_MAC_USE_COCOA
851 QMenuMergeList *list = 0;
852 if (GetMenuItemProperty(merge, 0, kMenuCreatorQt, kMenuPropertyMergeList,
853 sizeof(list), 0, &list) == noErr && list) {
854 for(int i = 0; i < list->size(); ++i) {
855 QMenuMergeItem item = list->at(i);
856 if (item.command == ret && item.action)
857 return 0;
858 }
859 }
860
861 QAction *cmd_action = 0;
862 if (GetMenuCommandProperty(merge, ret, kMenuCreatorQt, kMenuPropertyQAction,
863 sizeof(cmd_action), 0, &cmd_action) == noErr && cmd_action)
864 return 0; //already taken
865#else
866 if (QMenuMergeList *list = QMenuPrivate::mergeMenuItemsHash.value(merge)) {
867 for(int i = 0; i < list->size(); ++i) {
868 const QMenuMergeItem &item = list->at(i);
869 if (item.menuItem == ret && item.action)
870 return 0;
871 }
872 }
873
874 if ([ret tag] != 0)
875 ret = 0; // already taken
876#endif
877 return ret;
878}
879
880static QString qt_mac_menu_merge_text(QMacMenuAction *action)
881{
882 QString ret;
883#ifdef QT_MAC_USE_COCOA
884 QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
885#endif
886 if (action->action->menuRole() == QAction::ApplicationSpecificRole)
887 ret = action->action->text();
888#ifndef QT_MAC_USE_COCOA
889 else if (action->command == kHICommandAbout)
890 ret = QMenuBar::tr("About %1").arg(qAppName());
891 else if (action->command == kHICommandAboutQt)
892 ret = QMenuBar::tr("About Qt");
893 else if (action->command == kHICommandPreferences)
894 ret = QMenuBar::tr("Preferences");
895 else if (action->command == kHICommandQuit)
896 ret = QMenuBar::tr("Quit %1").arg(qAppName());
897#else
898 else if (action->menuItem == [loader aboutMenuItem])
899 ret = QMenuBar::tr("About %1").arg(qAppName());
900 else if (action->menuItem == [loader aboutQtMenuItem])
901 ret = QMenuBar::tr("About Qt");
902 else if (action->menuItem == [loader preferencesMenuItem])
903 ret = QMenuBar::tr("Preferences");
904 else if (action->menuItem == [loader quitMenuItem])
905 ret = QMenuBar::tr("Quit %1").arg(qAppName());
906#endif
907 return ret;
908}
909
910static QKeySequence qt_mac_menu_merge_accel(QMacMenuAction *action)
911{
912 QKeySequence ret;
913#ifdef QT_MAC_USE_COCOA
914 QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
915#endif
916 if (action->action->menuRole() == QAction::ApplicationSpecificRole)
917 ret = action->action->shortcut();
918#ifndef QT_MAC_USE_COCOA
919 else if (action->command == kHICommandPreferences)
920 ret = QKeySequence(Qt::CTRL+Qt::Key_Comma);
921 else if (action->command == kHICommandQuit)
922 ret = QKeySequence(Qt::CTRL+Qt::Key_Q);
923#else
924 else if (action->menuItem == [loader preferencesMenuItem])
925 ret = QKeySequence(Qt::CTRL+Qt::Key_Comma);
926 else if (action->menuItem == [loader quitMenuItem])
927 ret = QKeySequence(Qt::CTRL+Qt::Key_Q);
928#endif
929 return ret;
930}
931
932void Q_GUI_EXPORT qt_mac_set_menubar_icons(bool b)
933{ QApplication::instance()->setAttribute(Qt::AA_DontShowIconsInMenus, !b); }
934void Q_GUI_EXPORT qt_mac_set_native_menubar(bool b) { qt_mac_no_native_menubar = !b; }
935void Q_GUI_EXPORT qt_mac_set_menubar_merge(bool b) { qt_mac_no_menubar_merge = !b; }
936
937/*****************************************************************************
938 QMenu bindings
939 *****************************************************************************/
940QMenuPrivate::QMacMenuPrivate::QMacMenuPrivate() : menu(0)
941{
942}
943
944QMenuPrivate::QMacMenuPrivate::~QMacMenuPrivate()
945{
946#ifndef QT_MAC_USE_COCOA
947 for(QList<QMacMenuAction*>::Iterator it = actionItems.begin(); it != actionItems.end(); ++it) {
948 QMacMenuAction *action = (*it);
949 RemoveMenuCommandProperty(action->menu, action->command, kMenuCreatorQt, kMenuPropertyQAction);
950 if (action->merged) {
951 QMenuMergeList *list = 0;
952 GetMenuItemProperty(action->menu, 0, kMenuCreatorQt, kMenuPropertyMergeList,
953 sizeof(list), 0, &list);
954 for(int i = 0; list && i < list->size(); ) {
955 QMenuMergeItem item = list->at(i);
956 if (item.action == action)
957 list->removeAt(i);
958 else
959 ++i;
960 }
961 }
962 delete action;
963 }
964 if (menu) {
965 EventHandlerHash::iterator it = menu_eventHandlers_hash()->find(menu);
966 while (it != menu_eventHandlers_hash()->end() && it.key() == menu) {
967 RemoveEventHandler(it.value());
968 ++it;
969 }
970 menu_eventHandlers_hash()->remove(menu);
971 ReleaseMenu(menu);
972 }
973#else
974 QMacCocoaAutoReleasePool pool;
975 while (actionItems.size()) {
976 QMacMenuAction *action = actionItems.takeFirst();
977 if (QMenuMergeList *list = mergeMenuItemsHash.value(action->menu)) {
978 int i = 0;
979 while (i < list->size()) {
980 const QMenuMergeItem &item = list->at(i);
981 if (item.action == action)
982 list->removeAt(i);
983 else
984 ++i;
985 }
986 }
987 delete action;
988 }
989 mergeMenuHash.remove(menu);
990 mergeMenuItemsHash.remove(menu);
991 [menu release];
992#endif
993}
994
995void
996QMenuPrivate::QMacMenuPrivate::addAction(QAction *a, QMacMenuAction *before, QMenuPrivate *qmenu)
997{
998 QMacMenuAction *action = new QMacMenuAction;
999 action->action = a;
1000 action->ignore_accel = 0;
1001 action->merged = 0;
1002 action->menu = 0;
1003#ifndef QT_MAC_USE_COCOA
1004 action->command = qt_mac_menu_static_cmd_id++;
1005#endif
1006 addAction(action, before, qmenu);
1007}
1008
1009void
1010QMenuPrivate::QMacMenuPrivate::addAction(QMacMenuAction *action, QMacMenuAction *before, QMenuPrivate *qmenu)
1011{
1012#ifdef QT_MAC_USE_COCOA
1013 QMacCocoaAutoReleasePool pool;
1014 Q_UNUSED(qmenu);
1015#endif
1016 if (!action)
1017 return;
1018 int before_index = actionItems.indexOf(before);
1019 if (before_index < 0) {
1020 before = 0;
1021 before_index = actionItems.size();
1022 }
1023 actionItems.insert(before_index, action);
1024
1025#ifndef QT_MAC_USE_COCOA
1026 int index = qt_mac_menu_find_action(menu, action);
1027#else
1028 [menu retain];
1029 [action->menu release];
1030#endif
1031 action->menu = menu;
1032
1033 /* When the action is considered a mergable action it
1034 will stay that way, until removed.. */
1035 if (!qt_mac_no_menubar_merge) {
1036#ifndef QT_MAC_USE_COCOA
1037 MenuRef merge = 0;
1038 GetMenuItemProperty(menu, 0, kMenuCreatorQt, kMenuPropertyMergeMenu,
1039 sizeof(merge), 0, &merge);
1040#else
1041 OSMenuRef merge = QMenuPrivate::mergeMenuHash.value(menu);
1042#endif
1043 if (merge) {
1044#ifndef QT_MAC_USE_COCOA
1045 if (MenuCommand cmd = qt_mac_menu_merge_action(merge, action)) {
1046 action->merged = 1;
1047 action->menu = merge;
1048 action->command = cmd;
1049 if (qt_mac_auto_apple_menu(cmd))
1050 index = 0; //no need
1051
1052 QMenuMergeList *list = 0;
1053 if (GetMenuItemProperty(merge, 0, kMenuCreatorQt, kMenuPropertyMergeList,
1054 sizeof(list), 0, &list) != noErr || !list) {
1055 list = new QMenuMergeList;
1056 SetMenuItemProperty(merge, 0, kMenuCreatorQt, kMenuPropertyMergeList,
1057 sizeof(list), &list);
1058 }
1059 list->append(QMenuMergeItem(cmd, action));
1060 }
1061#else
1062 if (NSMenuItem *cmd = qt_mac_menu_merge_action(merge, action)) {
1063 action->merged = 1;
1064 [merge retain];
1065 [action->menu release];
1066 action->menu = merge;
1067 [cmd retain];
1068 [cmd setAction:@selector(qtDispatcherToQAction:)];
1069 [cmd setTarget:getMenuLoader()];
1070 [action->menuItem release];
1071 action->menuItem = cmd;
1072 QMenuMergeList *list = QMenuPrivate::mergeMenuItemsHash.value(merge);
1073 if (!list) {
1074 list = new QMenuMergeList;
1075 QMenuPrivate::mergeMenuItemsHash.insert(merge, list);
1076 }
1077 list->append(QMenuMergeItem(cmd, action));
1078 }
1079#endif
1080 }
1081 }
1082
1083#ifdef QT_MAC_USE_COCOA
1084 NSMenuItem *newItem = action->menuItem;
1085#endif
1086 if (
1087#ifndef QT_MAC_USE_COCOA
1088 index == -1
1089#else
1090 newItem == 0
1091#endif
1092 ) {
1093#ifndef QT_MAC_USE_COCOA
1094 index = before_index;
1095 MenuItemAttributes attr = kMenuItemAttrAutoRepeat;
1096#else
1097 newItem = createNSMenuItem(action->action->text());
1098 action->menuItem = newItem;
1099#endif
1100 if (before) {
1101#ifndef QT_MAC_USE_COCOA
1102 InsertMenuItemTextWithCFString(action->menu, 0, qMax(before_index, 0), attr, action->command);
1103#else
1104 [menu insertItem:newItem atIndex:qMax(before_index, 0)];
1105#endif
1106 } else {
1107#ifndef QT_MAC_USE_COCOA
1108 // Append the menu item to the menu. If it is a kHICommandAbout or a kHICommandAboutQt append
1109 // a separator also (to get a separator above "Preferences"), but make sure that we don't
1110 // add separators between two "about" items.
1111
1112 // Build a set of all commands that could possibly be before the separator.
1113 QSet<MenuCommand> mergedItems;
1114 mergedItems.insert(kHICommandAbout);
1115 mergedItems.insert(kHICommandAboutQt);
1116 mergedItems.insert(kHICommandCustomMerge);
1117
1118 QMenuMergeList *list = 0;
1119 if (GetMenuItemProperty(action->menu, 0, kMenuCreatorQt, kMenuPropertyMergeList,
1120 sizeof(list), 0, &list) == noErr && list) {
1121 for (int i = 0; i < list->size(); ++i) {
1122 MenuCommand command = list->at(i).command;
1123 if (command > kHICommandCustomMerge) {
1124 mergedItems.insert(command);
1125 }
1126 }
1127 }
1128
1129 const int itemCount = CountMenuItems(action->menu);
1130 MenuItemAttributes testattr;
1131 GetMenuItemAttributes(action->menu, itemCount , &testattr);
1132 if (mergedItems.contains(action->command)
1133 && (testattr & kMenuItemAttrSeparator)) {
1134 InsertMenuItemTextWithCFString(action->menu, 0, qMax(itemCount - 1, 0), attr, action->command);
1135 index = itemCount;
1136 } else {
1137 MenuItemIndex tmpIndex;
1138 AppendMenuItemTextWithCFString(action->menu, 0, attr, action->command, &tmpIndex);
1139 index = tmpIndex;
1140 if (mergedItems.contains(action->command))
1141 AppendMenuItemTextWithCFString(action->menu, 0, kMenuItemAttrSeparator, 0, &tmpIndex);
1142 }
1143#else
1144 [menu addItem:newItem];
1145#endif
1146 }
1147
1148 QWidget *widget = qmenu ? qmenu->widgetItems.value(action->action) : 0;
1149 if (widget) {
1150#ifndef QT_MAC_USE_COCOA
1151 ChangeMenuAttributes(action->menu, kMenuAttrDoNotCacheImage, 0);
1152 attr = kMenuItemAttrCustomDraw;
1153 SetMenuItemProperty(action->menu, index, kMenuCreatorQt, kMenuPropertyWidgetActionWidget,
1154 sizeof(QWidget *), &widget);
1155 HIViewRef content;
1156 HIMenuGetContentView(action->menu, kThemeMenuTypePullDown, &content);
1157
1158 EventHandlerRef eventHandlerRef;
1159 InstallMenuEventHandler(action->menu, qt_mac_widget_in_menu_eventHandler,
1160 GetEventTypeCount(widget_in_menu_events),
1161 widget_in_menu_events, 0, &eventHandlerRef);
1162 menu_eventHandlers_hash()->insert(action->menu, eventHandlerRef);
1163
1164 QWidget *menuWidget = 0;
1165 GetMenuItemProperty(action->menu, 0, kMenuCreatorQt, kMenuPropertyWidgetMenu,
1166 sizeof(menuWidget), 0, &menuWidget);
1167 if(!menuWidget) {
1168 menuWidget = new QMacNativeWidget(content);
1169 SetMenuItemProperty(menu, 0, kMenuCreatorQt, kMenuPropertyWidgetMenu,
1170 sizeof(menuWidget), &menuWidget);
1171 menuWidget->show();
1172 }
1173 widget->setParent(menuWidget);
1174#else
1175 QMacNativeWidget *container = new QMacNativeWidget(0);
1176 container->resize(widget->sizeHint());
1177 widget->setAttribute(Qt::WA_LayoutUsesWidgetRect);
1178 widget->setParent(container);
1179
1180 NSView *containerView = qt_mac_nativeview_for(container);
1181 [containerView setAutoresizesSubviews:YES];
1182 [containerView setAutoresizingMask:NSViewWidthSizable];
1183 [qt_mac_nativeview_for(widget) setAutoresizingMask:NSViewWidthSizable];
1184
1185 [newItem setView:containerView];
1186 container->show();
1187#endif
1188 widget->show();
1189 }
1190
1191 } else {
1192#ifndef QT_MAC_USE_COCOA
1193 qt_mac_command_set_enabled(action->menu, action->command, !QApplicationPrivate::modalState());
1194#else
1195 [newItem setEnabled:!QApplicationPrivate::modalState()];
1196#endif
1197 }
1198#ifndef QT_MAC_USE_COCOA
1199 SetMenuCommandProperty(action->menu, action->command, kMenuCreatorQt, kMenuPropertyQAction,
1200 sizeof(action), &action);
1201#else
1202 [newItem setTag:long(static_cast<QAction *>(action->action))];
1203#endif
1204 syncAction(action);
1205}
1206
1207// return an autoreleased string given a QKeySequence (currently only looks at the first one).
1208NSString *keySequenceToKeyEqivalent(const QKeySequence &accel)
1209{
1210 quint32 accel_key = (accel[0] & ~(Qt::MODIFIER_MASK | Qt::UNICODE_ACCEL));
1211 unichar keyEquiv[1] = { 0 };
1212 if (accel_key == Qt::Key_Return)
1213 keyEquiv[0] = kReturnCharCode;
1214 else if (accel_key == Qt::Key_Enter)
1215 keyEquiv[0] = kEnterCharCode;
1216 else if (accel_key == Qt::Key_Tab)
1217 keyEquiv[0] = kTabCharCode;
1218 else if (accel_key == Qt::Key_Backspace)
1219 keyEquiv[0] = kBackspaceCharCode;
1220 else if (accel_key == Qt::Key_Delete)
1221 keyEquiv[0] = NSDeleteFunctionKey;
1222 else if (accel_key == Qt::Key_Escape)
1223 keyEquiv[0] = kEscapeCharCode;
1224 else if (accel_key == Qt::Key_PageUp)
1225 keyEquiv[0] = NSPageUpFunctionKey;
1226 else if (accel_key == Qt::Key_PageDown)
1227 keyEquiv[0] = NSPageDownFunctionKey;
1228 else if (accel_key == Qt::Key_Up)
1229 keyEquiv[0] = NSUpArrowFunctionKey;
1230 else if (accel_key == Qt::Key_Down)
1231 keyEquiv[0] = NSDownArrowFunctionKey;
1232 else if (accel_key == Qt::Key_Left)
1233 keyEquiv[0] = NSLeftArrowFunctionKey;
1234 else if (accel_key == Qt::Key_Right)
1235 keyEquiv[0] = NSRightArrowFunctionKey;
1236 else if (accel_key == Qt::Key_CapsLock)
1237 keyEquiv[0] = kMenuCapsLockGlyph; // ### Cocoa has no equivalent
1238 else if (accel_key >= Qt::Key_F1 && accel_key <= Qt::Key_F15)
1239 keyEquiv[0] = (accel_key - Qt::Key_F1) + NSF1FunctionKey;
1240 else if (accel_key == Qt::Key_Home)
1241 keyEquiv[0] = NSHomeFunctionKey;
1242 else if (accel_key == Qt::Key_End)
1243 keyEquiv[0] = NSEndFunctionKey;
1244 else
1245 keyEquiv[0] = unichar(QChar(accel_key).toLower().unicode());
1246 return [NSString stringWithCharacters:keyEquiv length:1];
1247}
1248
1249// return the cocoa modifier mask for the QKeySequence (currently only looks at the first one).
1250NSUInteger keySequenceModifierMask(const QKeySequence &accel)
1251{
1252 NSUInteger ret = 0;
1253 quint32 accel_key = accel[0];
1254 if ((accel_key & Qt::CTRL) == Qt::CTRL)
1255 ret |= NSCommandKeyMask;
1256 if ((accel_key & Qt::META) == Qt::META)
1257 ret |= NSControlKeyMask;
1258 if ((accel_key & Qt::ALT) == Qt::ALT)
1259 ret |= NSAlternateKeyMask;
1260 if ((accel_key & Qt::SHIFT) == Qt::SHIFT)
1261 ret |= NSShiftKeyMask;
1262 return ret;
1263}
1264
1265void
1266QMenuPrivate::QMacMenuPrivate::syncAction(QMacMenuAction *action)
1267{
1268 if (!action)
1269 return;
1270
1271#ifndef QT_MAC_USE_COCOA
1272 const int index = qt_mac_menu_find_action(action->menu, action);
1273 if (index == -1)
1274 return;
1275#else
1276 NSMenuItem *item = action->menuItem;
1277 if (!item)
1278 return;
1279#endif
1280
1281#ifndef QT_MAC_USE_COCOA
1282 if (!action->action->isVisible()) {
1283 ChangeMenuItemAttributes(action->menu, index, kMenuItemAttrHidden, 0);
1284 return;
1285 }
1286 ChangeMenuItemAttributes(action->menu, index, 0, kMenuItemAttrHidden);
1287#else
1288 QMacCocoaAutoReleasePool pool;
1289 NSMenu *menu = [item menu];
1290 bool actionVisible = action->action->isVisible();
1291 [item setHidden:!actionVisible];
1292 if (!actionVisible)
1293 return;
1294#endif
1295
1296#ifndef QT_MAC_USE_COCOA
1297 if (action->action->isSeparator()) {
1298 ChangeMenuItemAttributes(action->menu, index, kMenuItemAttrSeparator, 0);
1299 return;
1300 }
1301 ChangeMenuItemAttributes(action->menu, index, 0, kMenuItemAttrSeparator);
1302#else
1303 int itemIndex = [menu indexOfItem:item];
1304 Q_ASSERT(itemIndex != -1);
1305 if (action->action->isSeparator()) {
1306 action->menuItem = [NSMenuItem separatorItem];
1307 [action->menuItem retain];
1308 [menu insertItem: action->menuItem atIndex:itemIndex];
1309 [menu removeItem:item];
1310 [item release];
1311 item = action->menuItem;
1312 return;
1313 } else if ([item isSeparatorItem]) {
1314 // I'm no longer a separator...
1315 action->menuItem = createNSMenuItem(action->action->text());
1316 [menu insertItem:action->menuItem atIndex:itemIndex];
1317 [menu removeItem:item];
1318 [item release];
1319 item = action->menuItem;
1320 }
1321#endif
1322
1323 //find text (and accel)
1324 action->ignore_accel = 0;
1325 QString text = action->action->text();
1326 QKeySequence accel = action->action->shortcut();
1327 {
1328 int st = text.lastIndexOf(QLatin1Char('\t'));
1329 if (st != -1) {
1330 action->ignore_accel = 1;
1331 accel = QKeySequence(text.right(text.length()-(st+1)));
1332 text.remove(st, text.length()-st);
1333 }
1334 }
1335 {
1336 QString cmd_text = qt_mac_menu_merge_text(action);
1337 if (!cmd_text.isEmpty()) {
1338 text = cmd_text;
1339 accel = qt_mac_menu_merge_accel(action);
1340 }
1341 }
1342 if (accel.count() > 1)
1343 text += QLatin1String(" (****)"); //just to denote a multi stroke shortcut
1344
1345 QString finalString = qt_mac_removeMnemonics(text);
1346
1347#ifndef QT_MAC_USE_COCOA
1348 MenuItemDataRec data;
1349 memset(&data, '\0', sizeof(data));
1350
1351 //Carbon text
1352 data.whichData |= kMenuItemDataCFString;
1353 QCFString cfstring(finalString); // Hold the reference to the end of the function.
1354 data.cfText = cfstring;
1355
1356 // Carbon enabled
1357 data.whichData |= kMenuItemDataEnabled;
1358 data.enabled = action->action->isEnabled();
1359 // Carbon icon
1360 data.whichData |= kMenuItemDataIconHandle;
1361 if (!action->action->icon().isNull()
1362 && action->action->isIconVisibleInMenu()) {
1363 data.iconType = kMenuIconRefType;
1364 data.iconHandle = (Handle)qt_mac_create_iconref(action->action->icon().pixmap(22, QIcon::Normal));
1365 } else {
1366 data.iconType = kMenuNoIcon;
1367 }
1368 if (action->action->font().resolve()) { // Carbon font
1369 if (action->action->font().bold())
1370 data.style |= bold;
1371 if (action->action->font().underline())
1372 data.style |= underline;
1373 if (action->action->font().italic())
1374 data.style |= italic;
1375 if (data.style)
1376 data.whichData |= kMenuItemDataStyle;
1377 data.whichData |= kMenuItemDataFontID;
1378 data.fontID = action->action->font().macFontID();
1379 }
1380#else
1381 // Cocoa Font and title
1382 if (action->action->font().resolve()) {
1383 const QFont &actionFont = action->action->font();
1384 NSFont *customMenuFont = [NSFont fontWithName:reinterpret_cast<const NSString *>(static_cast<CFStringRef>(QCFString(actionFont.family())))
1385 size:actionFont.pointSize()];
1386 NSArray *keys = [NSArray arrayWithObjects:NSFontAttributeName, nil];
1387 NSArray *objects = [NSArray arrayWithObjects:customMenuFont, nil];
1388 NSDictionary *attributes = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
1389 NSAttributedString *str = [[[NSAttributedString alloc] initWithString:reinterpret_cast<const NSString *>(static_cast<CFStringRef>(QCFString(finalString)))
1390 attributes:attributes] autorelease];
1391 [item setAttributedTitle: str];
1392 } else {
1393 [item setTitle: reinterpret_cast<const NSString *>(static_cast<CFStringRef>(QCFString(finalString)))];
1394 }
1395 [item setTitle:reinterpret_cast<const NSString *>(static_cast<CFStringRef>(QCFString(qt_mac_removeMnemonics(text))))];
1396
1397 // Cocoa Enabled
1398 [item setEnabled: action->action->isEnabled()];
1399
1400 // Cocoa icon
1401 NSImage *nsimage = 0;
1402 if (!action->action->icon().isNull() && action->action->isIconVisibleInMenu()) {
1403 nsimage = static_cast<NSImage *>(qt_mac_create_nsimage(action->action->icon().pixmap(22, QIcon::Normal)));
1404 }
1405 [item setImage:nsimage];
1406 [nsimage release];
1407#endif
1408
1409 if (action->action->menu()) { //submenu
1410#ifndef QT_MAC_USE_COCOA
1411 data.whichData |= kMenuItemDataSubmenuHandle;
1412 data.submenuHandle = action->action->menu()->macMenu();
1413 QWidget *caused = 0;
1414 GetMenuItemProperty(action->menu, 0, kMenuCreatorQt, kMenuPropertyQWidget, sizeof(caused), 0, &caused);
1415 SetMenuItemProperty(data.submenuHandle, 0, kMenuCreatorQt, kMenuPropertyCausedQWidget, sizeof(caused), &caused);
1416#else
1417 [item setSubmenu:static_cast<NSMenu *>(action->action->menu()->macMenu())];
1418#endif
1419 } else { //respect some other items
1420#ifndef QT_MAC_USE_COCOA
1421 //shortcuts (say we are setting them all so that we can also clear them).
1422 data.whichData |= kMenuItemDataCmdKey;
1423 data.whichData |= kMenuItemDataCmdKeyModifiers;
1424 data.whichData |= kMenuItemDataCmdKeyGlyph;
1425 if (!accel.isEmpty()) {
1426 qt_mac_get_accel(accel[0], (quint32*)&data.cmdKeyModifiers, (quint32*)&data.cmdKeyGlyph);
1427 if (data.cmdKeyGlyph == 0)
1428 data.cmdKey = (UniChar)accel[0];
1429 }
1430#else
1431 [item setSubmenu:0];
1432 if (!accel.isEmpty()) {
1433 [item setKeyEquivalent:keySequenceToKeyEqivalent(accel)];
1434 [item setKeyEquivalentModifierMask:keySequenceModifierMask(accel)];
1435 } else {
1436 [item setKeyEquivalent:@""];
1437 [item setKeyEquivalentModifierMask:NSCommandKeyMask];
1438 }
1439#endif
1440 }
1441#ifndef QT_MAC_USE_COCOA
1442 //mark glyph
1443 data.whichData |= kMenuItemDataMark;
1444 if (action->action->isChecked()) {
1445#if 0
1446 if (action->action->actionGroup() &&
1447 action->action->actionGroup()->isExclusive())
1448 data.mark = diamondMark;
1449 else
1450#endif
1451 data.mark = checkMark;
1452 } else {
1453 data.mark = noMark;
1454 }
1455
1456 //actually set it
1457 SetMenuItemData(action->menu, action->command, true, &data);
1458
1459 // Free up memory
1460 if (data.iconHandle)
1461 ReleaseIconRef(IconRef(data.iconHandle));
1462#else
1463 //mark glyph
1464 [item setState:action->action->isChecked() ? NSOnState : NSOffState];
1465#endif
1466}
1467
1468void
1469QMenuPrivate::QMacMenuPrivate::removeAction(QMacMenuAction *action)
1470{
1471 if (!action)
1472 return;
1473#ifndef QT_MAC_USE_COCOA
1474 if (action->command == kHICommandQuit || action->command == kHICommandPreferences)
1475 qt_mac_command_set_enabled(action->menu, action->command, false);
1476 else
1477 DeleteMenuItem(action->menu, qt_mac_menu_find_action(action->menu, action));
1478#else
1479 QMacCocoaAutoReleasePool pool;
1480 QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
1481 if (action->menuItem == [loader quitMenuItem] || action->menuItem == [loader preferencesMenuItem])
1482 [action->menuItem setEnabled:false];
1483 else
1484 [[action->menuItem menu] removeItem:action->menuItem];
1485#endif
1486 actionItems.removeAll(action);
1487}
1488
1489OSMenuRef
1490QMenuPrivate::macMenu(OSMenuRef merge)
1491{
1492 Q_UNUSED(merge);
1493 Q_Q(QMenu);
1494 if (mac_menu && mac_menu->menu)
1495 return mac_menu->menu;
1496 if (!mac_menu)
1497 mac_menu = new QMacMenuPrivate;
1498 mac_menu->menu = qt_mac_create_menu(q);
1499 if (merge) {
1500#ifndef QT_MAC_USE_COCOA
1501 SetMenuItemProperty(mac_menu->menu, 0, kMenuCreatorQt, kMenuPropertyMergeMenu, sizeof(merge), &merge);
1502#else
1503 mergeMenuHash.insert(mac_menu->menu, merge);
1504#endif
1505 }
1506 QList<QAction*> items = q->actions();
1507 for(int i = 0; i < items.count(); i++)
1508 mac_menu->addAction(items[i], 0, this);
1509 return mac_menu->menu;
1510}
1511
1512/*!
1513 \internal
1514*/
1515void QMenuPrivate::setMacMenuEnabled(bool enable)
1516{
1517 if (!macMenu(0))
1518 return;
1519
1520 QMacCocoaAutoReleasePool pool;
1521 if (enable) {
1522 for (int i = 0; i < mac_menu->actionItems.count(); ++i) {
1523 QMacMenuAction *menuItem = mac_menu->actionItems.at(i);
1524 if (menuItem && menuItem->action && menuItem->action->isEnabled()) {
1525#ifndef QT_MAC_USE_COCOA
1526 // Only enable those items which contains an enabled QAction.
1527 // i == 0 -> the menu itself, hence i + 1 for items.
1528 EnableMenuItem(mac_menu->menu, i + 1);
1529#else
1530 [menuItem->menuItem setEnabled:true];
1531#endif
1532 }
1533 }
1534 } else {
1535#ifndef QT_MAC_USE_COCOA
1536 DisableAllMenuItems(mac_menu->menu);
1537#else
1538 NSMenu *menu = mac_menu->menu;
1539 for (NSMenuItem *item in [menu itemArray]) {
1540 [item setEnabled:false];
1541 }
1542#endif
1543 }
1544}
1545
1546/*!
1547 \internal
1548
1549 This function will return the OSMenuRef used to create the native menu bar
1550 bindings.
1551
1552 If Qt is built against Carbon, the OSMenuRef is a MenuRef that can be used
1553 with Carbon's Menu Manager API.
1554
1555 If Qt is built against Cocoa, the OSMenuRef is a NSMenu pointer.
1556
1557 \warning This function is not portable.
1558
1559 \sa QMenuBar::macMenu()
1560*/
1561OSMenuRef QMenu::macMenu(OSMenuRef merge) { return d_func()->macMenu(merge); }
1562
1563/*****************************************************************************
1564 QMenuBar bindings
1565 *****************************************************************************/
1566typedef QHash<QWidget *, QMenuBar *> MenuBarHash;
1567Q_GLOBAL_STATIC(MenuBarHash, menubars)
1568static QMenuBar *fallback = 0;
1569QMenuBarPrivate::QMacMenuBarPrivate::QMacMenuBarPrivate() : menu(0), apple_menu(0)
1570{
1571}
1572
1573QMenuBarPrivate::QMacMenuBarPrivate::~QMacMenuBarPrivate()
1574{
1575 for(QList<QMacMenuAction*>::Iterator it = actionItems.begin(); it != actionItems.end(); ++it)
1576 delete (*it);
1577#ifndef QT_MAC_USE_COCOA
1578 if (apple_menu) {
1579 QMenuMergeList *list = 0;
1580 GetMenuItemProperty(apple_menu, 0, kMenuCreatorQt, kMenuPropertyMergeList,
1581 sizeof(list), 0, &list);
1582 if (list) {
1583 RemoveMenuItemProperty(apple_menu, 0, kMenuCreatorQt, kMenuPropertyMergeList);
1584 delete list;
1585 }
1586 ReleaseMenu(apple_menu);
1587 }
1588 if (menu)
1589 ReleaseMenu(menu);
1590#else
1591 [apple_menu release];
1592 [menu release];
1593#endif
1594}
1595
1596void
1597QMenuBarPrivate::QMacMenuBarPrivate::addAction(QAction *a, QMacMenuAction *before)
1598{
1599 if (a->isSeparator() || !menu)
1600 return;
1601 QMacMenuAction *action = new QMacMenuAction;
1602 action->action = a;
1603 action->ignore_accel = 1;
1604#ifndef QT_MAC_USE_COCOA
1605 action->command = qt_mac_menu_static_cmd_id++;
1606#endif
1607 addAction(action, before);
1608}
1609
1610void
1611QMenuBarPrivate::QMacMenuBarPrivate::addAction(QMacMenuAction *action, QMacMenuAction *before)
1612{
1613 if (!action || !menu)
1614 return;
1615
1616 int before_index = actionItems.indexOf(before);
1617 if (before_index < 0) {
1618 before = 0;
1619 before_index = actionItems.size();
1620 }
1621 actionItems.insert(before_index, action);
1622
1623 MenuItemIndex index = actionItems.size()-1;
1624
1625 action->menu = menu;
1626#ifdef QT_MAC_USE_COCOA
1627 QMacCocoaAutoReleasePool pool;
1628 [action->menu retain];
1629 NSMenuItem *newItem = createNSMenuItem(action->action->text());
1630 action->menuItem = newItem;
1631#endif
1632 if (before) {
1633#ifndef QT_MAC_USE_COCOA
1634 InsertMenuItemTextWithCFString(action->menu, 0, qMax(1, before_index+1), 0, action->command);
1635#else
1636 [menu insertItem:newItem atIndex:qMax(1, before_index + 1)];
1637#endif
1638 index = before_index;
1639 } else {
1640#ifndef QT_MAC_USE_COCOA
1641 AppendMenuItemTextWithCFString(action->menu, 0, 0, action->command, &index);
1642#else
1643 [menu addItem:newItem];
1644#endif
1645 }
1646#ifndef QT_MAC_USE_COCOA
1647 SetMenuItemProperty(action->menu, index, kMenuCreatorQt, kMenuPropertyQAction, sizeof(action),
1648 &action);
1649#else
1650 [newItem setTag:long(static_cast<QAction *>(action->action))];
1651#endif
1652 syncAction(action);
1653}
1654
1655void
1656QMenuBarPrivate::QMacMenuBarPrivate::syncAction(QMacMenuAction *action)
1657{
1658 if (!action || !menu)
1659 return;
1660#ifndef QT_MAC_USE_COCOA
1661 const int index = qt_mac_menu_find_action(action->menu, action);
1662#else
1663 QMacCocoaAutoReleasePool pool;
1664 NSMenuItem *item = action->menuItem;
1665#endif
1666
1667 OSMenuRef submenu = 0;
1668 bool release_submenu = false;
1669 if (action->action->menu()) {
1670 if ((submenu = action->action->menu()->macMenu(apple_menu))) {
1671#ifndef QT_MAC_USE_COCOA
1672 QWidget *caused = 0;
1673 GetMenuItemProperty(action->menu, 0, kMenuCreatorQt, kMenuPropertyQWidget, sizeof(caused), 0, &caused);
1674 SetMenuItemProperty(submenu, 0, kMenuCreatorQt, kMenuPropertyCausedQWidget, sizeof(caused), &caused);
1675#else
1676 [item setSubmenu:submenu];
1677#endif
1678 }
1679#ifndef QT_MAC_USE_COCOA
1680 } else { // create a submenu to act as menu
1681 release_submenu = true;
1682 CreateNewMenu(0, 0, &submenu);
1683#endif
1684 }
1685
1686 if (submenu) {
1687 bool visible = actualMenuItemVisibility(this, action);
1688#ifndef QT_MAC_USE_COCOA
1689 SetMenuItemHierarchicalMenu(action->menu, index, submenu);
1690 SetMenuTitleWithCFString(submenu, QCFString(qt_mac_removeMnemonics(action->action->text())));
1691 if (visible)
1692 ChangeMenuAttributes(submenu, 0, kMenuAttrHidden);
1693 else
1694 ChangeMenuAttributes(submenu, kMenuAttrHidden, 0);
1695#else
1696 [item setSubmenu: submenu];
1697 [submenu setTitle:reinterpret_cast<const NSString *>(static_cast<CFStringRef>(QCFString(qt_mac_removeMnemonics(action->action->text()))))];
1698 syncNSMenuItemVisiblity(item, visible);
1699#endif
1700 if (release_submenu) { //no pointers to it
1701#ifndef QT_MAC_USE_COCOA
1702 ReleaseMenu(submenu);
1703#else
1704 [submenu release];
1705#endif
1706 }
1707 } else {
1708 qWarning("QMenu: No OSMenuRef created for popup menu");
1709 }
1710}
1711
1712void
1713QMenuBarPrivate::QMacMenuBarPrivate::removeAction(QMacMenuAction *action)
1714{
1715 if (!action || !menu)
1716 return;
1717#ifndef QT_MAC_USE_COCOA
1718 DeleteMenuItem(action->menu, qt_mac_menu_find_action(action->menu, action));
1719#else
1720 QMacCocoaAutoReleasePool pool;
1721 [action->menu removeItem:action->menuItem];
1722#endif
1723 actionItems.removeAll(action);
1724}
1725
1726void
1727QMenuBarPrivate::macCreateMenuBar(QWidget *parent)
1728{
1729 Q_Q(QMenuBar);
1730 static int checkEnv = -1;
1731 if (qt_mac_no_native_menubar == false && checkEnv < 0) {
1732 checkEnv = !qgetenv("QT_MAC_NO_NATIVE_MENUBAR").isEmpty();
1733 qt_mac_no_native_menubar = checkEnv;