1/****************************************************************************
2**
3** Copyright (C) 2015 The Qt Company Ltd.
4** Contact: http://www.qt.io/licensing/
5**
6** This file is part of the QtGui module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see http://www.qt.io/terms-conditions. For further
15** information use the contact form at http://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 2.1 or version 3 as published by the Free
20** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22** following information to ensure the GNU Lesser General Public License
23** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25**
26** As a special exception, The Qt Company gives you certain additional
27** rights. These rights are described in The Qt Company LGPL Exception
28** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29**
30** GNU General Public License Usage
31** Alternatively, this file may be used under the terms of the GNU
32** General Public License version 3.0 as published by the Free Software
33** Foundation and appearing in the file LICENSE.GPL included in the
34** packaging of this file.  Please review the following information to
35** ensure the GNU General Public License version 3.0 requirements will be
36** met: http://www.gnu.org/copyleft/gpl.html.
37**
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_menubar_merge = false;
75bool qt_mac_quit_menu_item_enabled = true;
76int qt_mac_menus_open_count = 0;
77
78static OSMenuRef qt_mac_create_menu(QWidget *w);
79
80#ifndef QT_MAC_USE_COCOA
81static uint qt_mac_menu_static_cmd_id = 'QT00';
82const UInt32 kMenuCreatorQt = 'cute';
83enum {
84    kMenuPropertyQAction = 'QAcT',
85    kMenuPropertyQWidget = 'QWId',
86    kMenuPropertyCausedQWidget = 'QCAU',
87    kMenuPropertyMergeMenu = 'QApP',
88    kMenuPropertyMergeList = 'QAmL',
89    kMenuPropertyWidgetActionWidget = 'QWid',
90    kMenuPropertyWidgetMenu = 'QWMe',
91
92    kHICommandAboutQt = 'AOQT',
93    kHICommandCustomMerge = 'AQt0'
94};
95#endif
96
97static struct {
98    QPointer<QMenuBar> qmenubar;
99    bool modal;
100} qt_mac_current_menubar = { 0, false };
101
102
103
104
105/*****************************************************************************
106  Externals
107 *****************************************************************************/
108extern OSViewRef qt_mac_hiview_for(const QWidget *w); //qwidget_mac.cpp
109extern HIViewRef qt_mac_hiview_for(OSWindowRef w); //qwidget_mac.cpp
110extern IconRef qt_mac_create_iconref(const QPixmap &px); //qpixmap_mac.cpp
111extern QWidget * mac_keyboard_grabber; //qwidget_mac.cpp
112extern bool qt_sendSpontaneousEvent(QObject*, QEvent*); //qapplication_xxx.cpp
113RgnHandle qt_mac_get_rgn(); //qregion_mac.cpp
114void qt_mac_dispose_rgn(RgnHandle r); //qregion_mac.cpp
115
116/*****************************************************************************
117  QMenu utility functions
118 *****************************************************************************/
119bool qt_mac_watchingAboutToShow(QMenu *menu)
120{
121    return menu && menu->receivers(SIGNAL(aboutToShow()));
122}
123
124static int qt_mac_CountMenuItems(OSMenuRef menu)
125{
126    if (menu) {
127#ifndef QT_MAC_USE_COCOA
128        int ret = 0;
129        const int items = CountMenuItems(menu);
130        for(int i = 0; i < items; i++) {
131            MenuItemAttributes attr;
132            if (GetMenuItemAttributes(menu, i+1, &attr) == noErr &&
133               attr & kMenuItemAttrHidden)
134                continue;
135            ++ret;
136        }
137        return ret;
138#else
139        return [menu numberOfItems];
140#endif
141    }
142    return 0;
143}
144
145static quint32 constructModifierMask(quint32 accel_key)
146{
147    quint32 ret = 0;
148    const bool dontSwap = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta);
149#ifndef QT_MAC_USE_COCOA
150    if ((accel_key & Qt::ALT) == Qt::ALT)
151        ret |= kMenuOptionModifier;
152    if ((accel_key & Qt::SHIFT) == Qt::SHIFT)
153        ret |= kMenuShiftModifier;
154    if (dontSwap) {
155        if ((accel_key & Qt::META) != Qt::META)
156            ret |= kMenuNoCommandModifier;
157        if ((accel_key & Qt::CTRL) == Qt::CTRL)
158            ret |= kMenuControlModifier;
159    } else {
160        if ((accel_key & Qt::CTRL) != Qt::CTRL)
161            ret |= kMenuNoCommandModifier;
162        if ((accel_key & Qt::META) == Qt::META)
163            ret |= kMenuControlModifier;
164    }
165#else
166    if ((accel_key & Qt::CTRL) == Qt::CTRL)
167        ret |= (dontSwap ? NSControlKeyMask : NSCommandKeyMask);
168    if ((accel_key & Qt::META) == Qt::META)
169        ret |= (dontSwap ? NSCommandKeyMask : NSControlKeyMask);
170    if ((accel_key & Qt::ALT) == Qt::ALT)
171        ret |= NSAlternateKeyMask;
172    if ((accel_key & Qt::SHIFT) == Qt::SHIFT)
173        ret |= NSShiftKeyMask;
174#endif
175    return ret;
176}
177
178static void cancelAllMenuTracking()
179{
180#ifdef QT_MAC_USE_COCOA
181    QMacCocoaAutoReleasePool pool;
182    NSMenu *mainMenu = [[NSApplication sharedApplication] mainMenu];
183    [mainMenu cancelTracking];
184    for (NSMenuItem *item in [mainMenu itemArray]) {
185        if ([item submenu]) {
186            [[item submenu] cancelTracking];
187        }
188    }
189#else
190    CancelMenuTracking(AcquireRootMenu(), true, 0);
191#endif
192}
193
194static bool actualMenuItemVisibility(const QMenuBarPrivate::QMacMenuBarPrivate *mbp,
195                                     const QMacMenuAction *action)
196{
197    bool visible = action->action->isVisible();
198    if (visible && action->action->text() == QString(QChar(0x14)))
199        return false;
200    if (visible && action->action->menu() && !action->action->menu()->actions().isEmpty() &&
201        !qt_mac_CountMenuItems(action->action->menu()->macMenu(mbp->apple_menu)) &&
202        !qt_mac_watchingAboutToShow(action->action->menu())) {
203        return false;
204    }
205    return visible;
206}
207
208#ifndef QT_MAC_USE_COCOA
209bool qt_mac_activate_action(MenuRef menu, uint command, QAction::ActionEvent action_e, bool by_accel)
210{
211    //fire event
212    QMacMenuAction *action = 0;
213    if (GetMenuCommandProperty(menu, command, kMenuCreatorQt, kMenuPropertyQAction, sizeof(action), 0, &action) != noErr) {
214        QMenuMergeList *list = 0;
215        GetMenuItemProperty(menu, 0, kMenuCreatorQt, kMenuPropertyMergeList,
216                            sizeof(list), 0, &list);
217        if (!list && qt_mac_current_menubar.qmenubar && qt_mac_current_menubar.qmenubar->isNativeMenuBar()) {
218            MenuRef apple_menu = qt_mac_current_menubar.qmenubar->d_func()->mac_menubar->apple_menu;
219            GetMenuItemProperty(apple_menu, 0, kMenuCreatorQt, kMenuPropertyMergeList, sizeof(list), 0, &list);
220            if (list)
221                menu = apple_menu;
222        }
223        if (list) {
224            for(int i = 0; i < list->size(); ++i) {
225                QMenuMergeItem item = list->at(i);
226                if (item.command == command && item.action) {
227                    action = item.action;
228                    break;
229                }
230            }
231        }
232        if (!action)
233            return false;
234    }
235
236    if (action_e == QAction::Trigger && by_accel && action->ignore_accel) //no, not a real accel (ie tab)
237        return false;
238
239    // Unhighlight the highlighted menu item before triggering the action to
240    // prevent items from staying highlighted while a modal dialog is shown.
241    // This also fixed the problem that parentless modal dialogs leave
242    // the menu item highlighted (since the menu bar is cleared for these types of dialogs).
243    if (action_e == QAction::Trigger)
244        HiliteMenu(0);
245
246    action->action->activate(action_e);
247
248    //now walk up firing for each "caused" widget (like in the platform independent menu)
249    QWidget *caused = 0;
250    if (action_e == QAction::Hover && GetMenuItemProperty(menu, 0, kMenuCreatorQt, kMenuPropertyCausedQWidget, sizeof(caused), 0, &caused) == noErr) {
251        MenuRef caused_menu = 0;
252        if (QMenu *qmenu2 = qobject_cast<QMenu*>(caused))
253            caused_menu = qmenu2->macMenu();
254        else if (QMenuBar *qmenubar2 = qobject_cast<QMenuBar*>(caused))
255            caused_menu = qmenubar2->macMenu();
256        else
257            caused_menu = 0;
258        while(caused_menu) {
259            //fire
260            QWidget *widget = 0;
261            GetMenuItemProperty(caused_menu, 0, kMenuCreatorQt, kMenuPropertyQWidget, sizeof(widget), 0, &widget);
262            if (QMenu *qmenu = qobject_cast<QMenu*>(widget)) {
263                action->action->showStatusText(widget);
264                emit qmenu->hovered(action->action);
265            } else if (QMenuBar *qmenubar = qobject_cast<QMenuBar*>(widget)) {
266                action->action->showStatusText(widget);
267                emit qmenubar->hovered(action->action);
268                break; //nothing more..
269            }
270
271            //walk up
272            if (GetMenuItemProperty(caused_menu, 0, kMenuCreatorQt, kMenuPropertyCausedQWidget,
273                                    sizeof(caused), 0, &caused) != noErr)
274                break;
275            if (QMenu *qmenu2 = qobject_cast<QMenu*>(caused))
276                caused_menu = qmenu2->macMenu();
277            else if (QMenuBar *qmenubar2 = qobject_cast<QMenuBar*>(caused))
278                caused_menu = qmenubar2->macMenu();
279            else
280                caused_menu = 0;
281        }
282    }
283    return true;
284}
285
286//lookup a QMacMenuAction in a menu
287static int qt_mac_menu_find_action(MenuRef menu, MenuCommand cmd)
288{
289    MenuItemIndex ret_idx;
290    MenuRef ret_menu;
291    if (GetIndMenuItemWithCommandID(menu, cmd, 1, &ret_menu, &ret_idx) == noErr) {
292        if (ret_menu == menu)
293            return (int)ret_idx;
294    }
295    return -1;
296}
297static int qt_mac_menu_find_action(MenuRef menu, QMacMenuAction *action)
298{
299    return qt_mac_menu_find_action(menu, action->command);
300}
301
302typedef QMultiHash<OSMenuRef, EventHandlerRef> EventHandlerHash;
303Q_GLOBAL_STATIC(EventHandlerHash, menu_eventHandlers_hash)
304
305static EventTypeSpec widget_in_menu_events[] = {
306    { kEventClassMenu, kEventMenuMeasureItemWidth },
307    { kEventClassMenu, kEventMenuMeasureItemHeight },
308    { kEventClassMenu, kEventMenuDrawItem },
309    { kEventClassMenu, kEventMenuCalculateSize }
310};
311
312static OSStatus qt_mac_widget_in_menu_eventHandler(EventHandlerCallRef er, EventRef event, void *)
313{
314    UInt32 ekind = GetEventKind(event);
315    UInt32 eclass = GetEventClass(event);
316    OSStatus result = eventNotHandledErr;
317    switch (eclass) {
318    case kEventClassMenu:
319        switch (ekind) {
320        default:
321            break;
322        case kEventMenuMeasureItemWidth: {
323            MenuItemIndex item;
324            GetEventParameter(event, kEventParamMenuItemIndex, typeMenuItemIndex,
325                              0, sizeof(item), 0, &item);
326            OSMenuRef menu;
327            GetEventParameter(event, kEventParamDirectObject, typeMenuRef, 0, sizeof(menu), 0, &menu);
328            QWidget *widget;
329            if (GetMenuItemProperty(menu, item, kMenuCreatorQt, kMenuPropertyWidgetActionWidget,
330                                 sizeof(widget), 0, &widget) == noErr) {
331                short width = short(widget->sizeHint().width());
332                SetEventParameter(event, kEventParamMenuItemWidth, typeSInt16,
333                                  sizeof(short), &width);
334                result = noErr;
335            }
336            break; }
337        case kEventMenuMeasureItemHeight: {
338            MenuItemIndex item;
339            GetEventParameter(event, kEventParamMenuItemIndex, typeMenuItemIndex,
340                              0, sizeof(item), 0, &item);
341            OSMenuRef menu;
342            GetEventParameter(event, kEventParamDirectObject, typeMenuRef, 0, sizeof(menu), 0, &menu);
343            QWidget *widget;
344            if (GetMenuItemProperty(menu, item, kMenuCreatorQt, kMenuPropertyWidgetActionWidget,
345                                     sizeof(widget), 0, &widget) == noErr && widget) {
346                short height = short(widget->sizeHint().height());
347                SetEventParameter(event, kEventParamMenuItemHeight, typeSInt16,
348                                  sizeof(short), &height);
349                result = noErr;
350            }
351            break; }
352        case kEventMenuDrawItem:
353            result = noErr;
354            break;
355        case kEventMenuCalculateSize: {
356            result = CallNextEventHandler(er, event);
357            if (result == noErr) {
358                OSMenuRef menu;
359                GetEventParameter(event, kEventParamDirectObject, typeMenuRef, 0, sizeof(menu), 0, &menu);
360                HIViewRef content;
361                HIMenuGetContentView(menu, kThemeMenuTypePullDown, &content);
362                UInt16 count = CountMenuItems(menu);
363                for (MenuItemIndex i = 1; i <= count; ++i) {
364                    QWidget *widget;
365                    if (GetMenuItemProperty(menu, i, kMenuCreatorQt, kMenuPropertyWidgetActionWidget,
366                            sizeof(widget), 0, &widget) == noErr && widget) {
367                        RgnHandle itemRgn = qt_mac_get_rgn();
368                        GetControlRegion(content, i, itemRgn);
369
370                        Rect bounds;
371                        GetRegionBounds( itemRgn, &bounds );
372                        qt_mac_dispose_rgn(itemRgn);
373                        widget->setGeometry(bounds.left, bounds.top,
374                                            bounds.right - bounds.left, bounds.bottom - bounds.top);
375                    }
376                }
377            }
378            break; }
379        }
380    }
381    return result;
382}
383
384//handling of events for menurefs created by Qt..
385static EventTypeSpec menu_events[] = {
386    { kEventClassCommand, kEventCommandProcess },
387    { kEventClassMenu, kEventMenuTargetItem },
388    { kEventClassMenu, kEventMenuOpening },
389    { kEventClassMenu, kEventMenuClosed }
390};
391
392// Special case for kEventMenuMatchKey, see qt_mac_create_menu below.
393static EventTypeSpec menu_menu_events[] = {
394    { kEventClassMenu, kEventMenuMatchKey }
395};
396
397OSStatus qt_mac_menu_event(EventHandlerCallRef er, EventRef event, void *)
398{
399    QScopedLoopLevelCounter loopLevelCounter(QApplicationPrivate::instance()->threadData);
400
401    bool handled_event = true;
402    UInt32 ekind = GetEventKind(event), eclass = GetEventClass(event);
403    switch(eclass) {
404    case kEventClassCommand:
405        if (ekind == kEventCommandProcess) {
406            UInt32 context;
407            GetEventParameter(event, kEventParamMenuContext, typeUInt32,
408                              0, sizeof(context), 0, &context);
409            HICommand cmd;
410            GetEventParameter(event, kEventParamDirectObject, typeHICommand,
411                              0, sizeof(cmd), 0, &cmd);
412            if (!mac_keyboard_grabber && (context & kMenuContextKeyMatching)) {
413                QMacMenuAction *action = 0;
414                if (GetMenuCommandProperty(cmd.menu.menuRef, cmd.commandID, kMenuCreatorQt,
415                                          kMenuPropertyQAction, sizeof(action), 0, &action) == noErr) {
416                    QWidget *widget = 0;
417                    if (qApp->activePopupWidget())
418                        widget = (qApp->activePopupWidget()->focusWidget() ?
419                                  qApp->activePopupWidget()->focusWidget() : qApp->activePopupWidget());
420                    else if (QApplicationPrivate::focus_widget)
421                        widget = QApplicationPrivate::focus_widget;
422                    if (widget) {
423                        int key = action->action->shortcut();
424                        QKeyEvent accel_ev(QEvent::ShortcutOverride, (key & (~Qt::KeyboardModifierMask)),
425                                           Qt::KeyboardModifiers(key & Qt::KeyboardModifierMask));
426                        accel_ev.ignore();
427                        qt_sendSpontaneousEvent(widget, &accel_ev);
428                        if (accel_ev.isAccepted()) {
429                            handled_event = false;
430                            break;
431                        }
432                    }
433                }
434            }
435            handled_event = qt_mac_activate_action(cmd.menu.menuRef, cmd.commandID,
436                                                   QAction::Trigger, context & kMenuContextKeyMatching);
437        }
438        break;
439    case kEventClassMenu: {
440        MenuRef menu;
441        GetEventParameter(event, kEventParamDirectObject, typeMenuRef, NULL, sizeof(menu), NULL, &menu);
442        if (ekind == kEventMenuMatchKey) {
443            // Don't activate any actions if we are showing a native modal dialog,
444            // the key events should go to the dialog in this case.
445            if (QApplicationPrivate::native_modal_dialog_active)
446                return menuItemNotFoundErr;
447
448             handled_event = false;
449        } else if (ekind == kEventMenuTargetItem) {
450            MenuCommand command;
451            GetEventParameter(event, kEventParamMenuCommand, typeMenuCommand,
452                              0, sizeof(command), 0, &command);
453            handled_event = qt_mac_activate_action(menu, command, QAction::Hover, false);
454        } else if (ekind == kEventMenuOpening || ekind == kEventMenuClosed) {
455            qt_mac_menus_open_count += (ekind == kEventMenuOpening) ? 1 : -1;
456            MenuRef mr;
457            GetEventParameter(event, kEventParamDirectObject, typeMenuRef,
458                              0, sizeof(mr), 0, &mr);
459
460            QWidget *widget = 0;
461            if (GetMenuItemProperty(mr, 0, kMenuCreatorQt, kMenuPropertyQWidget, sizeof(widget), 0, &widget) == noErr) {
462                if (QMenu *qmenu = qobject_cast<QMenu*>(widget)) {
463                    handled_event = true;
464                    if (ekind == kEventMenuOpening) {
465                        emit qmenu->aboutToShow();
466
467                        int merged = 0;
468                        const QMenuPrivate::QMacMenuPrivate *mac_menu = qmenu->d_func()->mac_menu;
469                        const int ActionItemsCount = mac_menu->actionItems.size();
470                        for(int i = 0; i < ActionItemsCount; ++i) {
471                            QMacMenuAction *action = mac_menu->actionItems.at(i);
472                            if (action->action->isSeparator()) {
473                                bool hide = false;
474                                if(!action->action->isVisible()) {
475                                    hide = true;
476                                } else if (merged && merged == i) {
477                                    hide = true;
478                                } else {
479                                    for(int l = i+1; l < mac_menu->actionItems.size(); ++l) {
480                                        QMacMenuAction *action = mac_menu->actionItems.at(l);
481                                        if (action->merged) {
482                                            hide = true;
483                                        } else if (action->action->isSeparator()) {
484                                            if (hide)
485                                                break;
486                                        } else if (!action->merged) {
487                                            hide = false;
488                                            break;
489                                        }
490                                    }
491                                }
492
493                                const int index = qt_mac_menu_find_action(mr, action);
494                                if (hide) {
495                                    ++merged;
496                                    ChangeMenuItemAttributes(mr, index, kMenuItemAttrHidden, 0);
497                                } else {
498                                    ChangeMenuItemAttributes(mr, index, 0, kMenuItemAttrHidden);
499                                }
500                            } else if (action->merged) {
501                                ++merged;
502                            }
503                        }
504                    } else {
505                        emit qmenu->aboutToHide();
506                    }
507                }
508            }
509        } else {
510            handled_event = false;
511        }
512        break; }
513    default:
514        handled_event = false;
515        break;
516    }
517    if (!handled_event) //let the event go through
518        return CallNextEventHandler(er, event);
519    return noErr; //we eat the event
520}
521static EventHandlerRef mac_menu_event_handler = 0;
522static EventHandlerUPP mac_menu_eventUPP = 0;
523static void qt_mac_cleanup_menu_event()
524{
525    if (mac_menu_event_handler) {
526        RemoveEventHandler(mac_menu_event_handler);
527        mac_menu_event_handler = 0;
528    }
529    if (mac_menu_eventUPP) {
530        DisposeEventHandlerUPP(mac_menu_eventUPP);
531        mac_menu_eventUPP = 0;
532    }
533}
534static inline void qt_mac_create_menu_event_handler()
535{
536    if (!mac_menu_event_handler) {
537        mac_menu_eventUPP = NewEventHandlerUPP(qt_mac_menu_event);
538        InstallEventHandler(GetApplicationEventTarget(), mac_menu_eventUPP,
539                            GetEventTypeCount(menu_events), menu_events, 0,
540                            &mac_menu_event_handler);
541        qAddPostRoutine(qt_mac_cleanup_menu_event);
542    }
543}
544
545
546//enabling of commands
547static void qt_mac_command_set_enabled(MenuRef menu, UInt32 cmd, bool b)
548{
549    if (cmd == kHICommandQuit)
550        qt_mac_quit_menu_item_enabled = b;
551
552    if (b) {
553        EnableMenuCommand(menu, cmd);
554        if (MenuRef dock_menu = GetApplicationDockTileMenu())
555            EnableMenuCommand(dock_menu, cmd);
556    } else {
557        DisableMenuCommand(menu, cmd);
558        if (MenuRef dock_menu = GetApplicationDockTileMenu())
559            DisableMenuCommand(dock_menu, cmd);
560    }
561}
562
563static bool qt_mac_auto_apple_menu(MenuCommand cmd)
564{
565    return (cmd == kHICommandPreferences || cmd == kHICommandQuit);
566}
567
568static void qt_mac_get_accel(quint32 accel_key, quint32 *modif, quint32 *key) {
569    if (modif) {
570        *modif = constructModifierMask(accel_key);
571    }
572
573    accel_key &= ~(Qt::MODIFIER_MASK | Qt::UNICODE_ACCEL);
574    if (key) {
575        *key = 0;
576        if (accel_key == Qt::Key_Return)
577            *key = kMenuReturnGlyph;
578        else if (accel_key == Qt::Key_Enter)
579            *key = kMenuEnterGlyph;
580        else if (accel_key == Qt::Key_Tab)
581            *key = kMenuTabRightGlyph;
582        else if (accel_key == Qt::Key_Backspace)
583            *key = kMenuDeleteLeftGlyph;
584        else if (accel_key == Qt::Key_Delete)
585            *key = kMenuDeleteRightGlyph;
586        else if (accel_key == Qt::Key_Escape)
587            *key = kMenuEscapeGlyph;
588        else if (accel_key == Qt::Key_PageUp)
589            *key = kMenuPageUpGlyph;
590        else if (accel_key == Qt::Key_PageDown)
591            *key = kMenuPageDownGlyph;
592        else if (accel_key == Qt::Key_Up)
593            *key = kMenuUpArrowGlyph;
594        else if (accel_key == Qt::Key_Down)
595            *key = kMenuDownArrowGlyph;
596        else if (accel_key == Qt::Key_Left)
597            *key = kMenuLeftArrowGlyph;
598        else if (accel_key == Qt::Key_Right)
599            *key = kMenuRightArrowGlyph;
600        else if (accel_key == Qt::Key_CapsLock)
601            *key = kMenuCapsLockGlyph;
602        else if (accel_key >= Qt::Key_F1 && accel_key <= Qt::Key_F15)
603            *key = (accel_key - Qt::Key_F1) + kMenuF1Glyph;
604        else if (accel_key == Qt::Key_Home)
605            *key = kMenuNorthwestArrowGlyph;
606        else if (accel_key == Qt::Key_End)
607            *key = kMenuSoutheastArrowGlyph;
608    }
609}
610#else // Cocoa
611static inline void syncNSMenuItemVisiblity(NSMenuItem *menuItem, bool actionVisibility)
612{
613    [menuItem setHidden:NO];
614    [menuItem setHidden:YES];
615    [menuItem setHidden:!actionVisibility];
616}
617
618static inline void syncNSMenuItemEnabled(NSMenuItem *menuItem, bool enabled)
619{
620    [menuItem setEnabled:NO];
621    [menuItem setEnabled:YES];
622    [menuItem setEnabled:enabled];
623}
624
625static inline void syncMenuBarItemsVisiblity(const QMenuBarPrivate::QMacMenuBarPrivate *mac_menubar)
626{
627    const QList<QMacMenuAction *> &menubarActions = mac_menubar->actionItems;
628    for (int i = 0; i < menubarActions.size(); ++i) {
629        const QMacMenuAction *action = menubarActions.at(i);
630        syncNSMenuItemVisiblity(action->menuItem, actualMenuItemVisibility(mac_menubar, action));
631    }
632}
633
634static inline QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *getMenuLoader()
635{
636    return [[NSApplication sharedApplication] QT_MANGLE_NAMESPACE(qt_qcocoamenuLoader)];
637}
638
639static NSMenuItem *createNSMenuItem(const QString &title)
640{
641    NSMenuItem *item = [[NSMenuItem alloc]
642                         initWithTitle:qt_mac_QStringToNSString(title)
643                         action:@selector(qtDispatcherToQAction:) keyEquivalent:@""];
644    [item setTarget:nil];
645    return item;
646}
647#endif
648
649
650
651// helper that recurses into a menu structure and en/dis-ables them
652void qt_mac_set_modal_state_helper_recursive(OSMenuRef menu, OSMenuRef merge, bool on)
653{
654#ifndef QT_MAC_USE_COCOA
655    for (int i = 0; i < CountMenuItems(menu); i++) {
656        OSMenuRef submenu;
657        GetMenuItemHierarchicalMenu(menu, i+1, &submenu);
658        if (submenu != merge) {
659            if (submenu)
660                qt_mac_set_modal_state_helper_recursive(submenu, merge, on);
661            if (on)
662                DisableMenuItem(submenu, 0);
663            else
664                EnableMenuItem(submenu, 0);
665        }
666    }
667#else
668    bool modalWindowOnScreen = qApp->activeModalWidget() != 0;
669    for (NSMenuItem *item in [menu itemArray]) {
670        OSMenuRef submenu = [item submenu];
671        if (submenu != merge) {
672            if (submenu)
673                qt_mac_set_modal_state_helper_recursive(submenu, merge, on);
674            if (!on) {
675                // The item should follow what the QAction has.
676                if ([item tag]) {
677                    QAction *action = reinterpret_cast<QAction *>([item tag]);
678                    syncNSMenuItemEnabled(item, action->isEnabled());
679                } else {
680                    syncNSMenuItemEnabled(item, YES);
681                }
682                // We sneak in some extra code here to handle a menu problem:
683                // If there is no window on screen, we cannot set 'nil' as
684                // menu item target, because then cocoa will disable the item
685                // (guess it assumes that there will be no first responder to
686                // catch the trigger anyway?) OTOH, If we have a modal window,
687                // then setting the menu loader as target will make cocoa not
688                // deliver the trigger because the loader is then seen as modally
689                // shaddowed). So either way there are shortcomings. Instead, we
690                // decide the target as late as possible:
691                [item setTarget:modalWindowOnScreen ? nil : getMenuLoader()];
692            } else {
693                syncNSMenuItemEnabled(item, NO);
694            }
695        }
696    }
697#endif
698}
699
700//toggling of modal state
701static void qt_mac_set_modal_state(OSMenuRef menu, bool on)
702{
703#ifndef QT_MAC_USE_COCOA
704    OSMenuRef merge = 0;
705    GetMenuItemProperty(menu, 0, kMenuCreatorQt, kMenuPropertyMergeMenu,
706            sizeof(merge), 0, &merge);
707
708    qt_mac_set_modal_state_helper_recursive(menu, merge, on);
709
710    UInt32 commands[] = { kHICommandQuit, kHICommandPreferences, kHICommandAbout, kHICommandAboutQt, 0 };
711    for(int c = 0; commands[c]; c++) {
712        bool enabled = !on;
713        if (enabled) {
714            QMacMenuAction *action = 0;
715            GetMenuCommandProperty(menu, commands[c], kMenuCreatorQt, kMenuPropertyQAction,
716                    sizeof(action), 0, &action);
717            if (!action && merge) {
718                GetMenuCommandProperty(merge, commands[c], kMenuCreatorQt, kMenuPropertyQAction,
719                        sizeof(action), 0, &action);
720                if (!action) {
721                    QMenuMergeList *list = 0;
722                    GetMenuItemProperty(merge, 0, kMenuCreatorQt, kMenuPropertyMergeList,
723                            sizeof(list), 0, &list);
724                    for(int i = 0; list && i < list->size(); ++i) {
725                        QMenuMergeItem item = list->at(i);
726                        if (item.command == commands[c] && item.action) {
727                            action = item.action;
728                            break;
729                        }
730                    }
731                }
732            }
733
734            if (!action) {
735                if (commands[c] != kHICommandQuit)
736                    enabled = false;
737            } else {
738                enabled = action->action ? action->action->isEnabled() : 0;
739            }
740        }
741        qt_mac_command_set_enabled(menu, commands[c], enabled);
742    }
743#else
744    OSMenuRef merge = QMenuPrivate::mergeMenuHash.value(menu);
745    qt_mac_set_modal_state_helper_recursive(menu, merge, on);
746    // I'm ignoring the special items now, since they should get handled via a syncAction()
747#endif
748}
749
750bool qt_mac_menubar_is_open()
751{
752    return qt_mac_menus_open_count > 0;
753}
754
755QMacMenuAction::~QMacMenuAction()
756{
757#ifdef QT_MAC_USE_COCOA
758    [menu release];
759    // Update the menu item if this action still owns it. For some items
760    // (like 'Quit') ownership will be transferred between all menu bars...
761    if (action && action.data() == reinterpret_cast<QAction *>([menuItem tag])) {
762        QAction::MenuRole role = action->menuRole();
763        // Check if the item is owned by Qt, and should be hidden to keep it from causing
764        // problems. Do it for everything but the quit menu item since that should always
765        // be visible.
766        if (role > QAction::ApplicationSpecificRole && role < QAction::QuitRole) {
767            [menuItem setHidden:YES];
768        } else if (role == QAction::TextHeuristicRole
769                   && menuItem != [getMenuLoader() quitMenuItem]) {
770            [menuItem setHidden:YES];
771        }
772        [menuItem setTag:nil];
773    }
774    [menuItem release];
775#endif
776}
777
778#ifndef QT_MAC_USE_COCOA
779static MenuCommand qt_mac_menu_merge_action(MenuRef merge, QMacMenuAction *action)
780#else
781static NSMenuItem *qt_mac_menu_merge_action(OSMenuRef merge, QMacMenuAction *action)
782#endif
783{
784    if (qt_mac_no_menubar_merge || action->action->menu() || action->action->isSeparator()
785            || action->action->menuRole() == QAction::NoRole)
786        return 0;
787
788    QString t = qt_mac_removeMnemonics(action->action->text().toLower());
789    int st = t.lastIndexOf(QLatin1Char('\t'));
790    if (st != -1)
791        t.remove(st, t.length()-st);
792    t.replace(QRegExp(QString::fromLatin1("\\.*$")), QLatin1String("")); //no ellipses
793    //now the fun part
794#ifndef QT_MAC_USE_COCOA
795    MenuCommand ret = 0;
796#else
797    NSMenuItem *ret = 0;
798    QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
799#endif
800    switch (action->action->menuRole()) {
801    case QAction::NoRole:
802        ret = 0;
803        break;
804    case QAction::ApplicationSpecificRole:
805#ifndef QT_MAC_USE_COCOA
806        {
807            QMenuMergeList *list = 0;
808            if (GetMenuItemProperty(merge, 0, kMenuCreatorQt, kMenuPropertyMergeList,
809                        sizeof(list), 0, &list) == noErr && list) {
810                MenuCommand lastCustom = kHICommandCustomMerge;
811                for(int i = 0; i < list->size(); ++i) {
812                    QMenuMergeItem item = list->at(i);
813                    if (item.command == lastCustom)
814                        ++lastCustom;
815                }
816                ret = lastCustom;
817            } else {
818                // The list hasn't been created, so, must be the first one.
819                ret = kHICommandCustomMerge;
820            }
821        }
822#else
823        ret = [loader appSpecificMenuItem];
824#endif
825        break;
826    case QAction::AboutRole:
827#ifndef QT_MAC_USE_COCOA
828        ret = kHICommandAbout;
829#else
830        ret = [loader aboutMenuItem];
831#endif
832        break;
833    case QAction::AboutQtRole:
834#ifndef QT_MAC_USE_COCOA
835        ret = kHICommandAboutQt;
836#else
837        ret = [loader aboutQtMenuItem];
838#endif
839        break;
840    case QAction::QuitRole:
841#ifndef QT_MAC_USE_COCOA
842        ret = kHICommandQuit;
843#else
844        ret = [loader quitMenuItem];
845#endif
846        break;
847    case QAction::PreferencesRole:
848#ifndef QT_MAC_USE_COCOA
849        ret = kHICommandPreferences;
850#else
851        ret = [loader preferencesMenuItem];
852#endif
853        break;
854    case QAction::TextHeuristicRole: {
855        QString aboutString = QMenuBar::tr("About").toLower();
856        if (t.startsWith(aboutString) || t.endsWith(aboutString)) {
857            if (t.indexOf(QRegExp(QString::fromLatin1("qt$"), Qt::CaseInsensitive)) == -1) {
858#ifndef QT_MAC_USE_COCOA
859                ret = kHICommandAbout;
860#else
861                ret = [loader aboutMenuItem];
862#endif
863            } else {
864#ifndef QT_MAC_USE_COCOA
865                ret = kHICommandAboutQt;
866#else
867                ret = [loader aboutQtMenuItem];
868#endif
869            }
870        } else if (t.startsWith(QMenuBar::tr("Config").toLower())
871                   || t.startsWith(QMenuBar::tr("Preference").toLower())
872                   || t.startsWith(QMenuBar::tr("Options").toLower())
873                   || t.startsWith(QMenuBar::tr("Setting").toLower())
874                   || t.startsWith(QMenuBar::tr("Setup").toLower())) {
875#ifndef QT_MAC_USE_COCOA
876            ret = kHICommandPreferences;
877#else
878            ret = [loader preferencesMenuItem];
879#endif
880        } else if (t.startsWith(QMenuBar::tr("Quit").toLower())
881                   || t.startsWith(QMenuBar::tr("Exit").toLower())) {
882#ifndef QT_MAC_USE_COCOA
883            ret = kHICommandQuit;
884#else
885            ret = [loader quitMenuItem];
886#endif
887        }
888    }
889        break;
890    }
891
892#ifndef QT_MAC_USE_COCOA
893    QMenuMergeList *list = 0;
894    if (GetMenuItemProperty(merge, 0, kMenuCreatorQt, kMenuPropertyMergeList,
895                           sizeof(list), 0, &list) == noErr && list) {
896        for(int i = 0; i < list->size(); ++i) {
897            QMenuMergeItem item = list->at(i);
898            if (item.command == ret && item.action)
899                return 0;
900        }
901    }
902
903    QAction *cmd_action = 0;
904    if (GetMenuCommandProperty(merge, ret, kMenuCreatorQt, kMenuPropertyQAction,
905                              sizeof(cmd_action), 0, &cmd_action) == noErr && cmd_action)
906        return 0; //already taken
907#else
908    if (QMenuMergeList *list = QMenuPrivate::mergeMenuItemsHash.value(merge)) {
909        for(int i = 0; i < list->size(); ++i) {
910            const QMenuMergeItem &item = list->at(i);
911            if (item.menuItem == ret && item.action)
912                return 0;
913        }
914    }
915
916#endif
917    return ret;
918}
919
920static QString qt_mac_menu_merge_text(QMacMenuAction *action)
921{
922    QString ret;
923    extern QString qt_mac_applicationmenu_string(int type);
924#ifdef QT_MAC_USE_COCOA
925    QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
926#endif
927    if (action->action->menuRole() == QAction::ApplicationSpecificRole)
928        ret = action->action->text();
929#ifndef QT_MAC_USE_COCOA
930    else if (action->command == kHICommandAbout)
931        ret = qt_mac_applicationmenu_string(6).arg(qAppName());
932    else if (action->command == kHICommandAboutQt)
933        ret = QMenuBar::tr("About Qt");
934    else if (action->command == kHICommandPreferences)
935        ret = qt_mac_applicationmenu_string(4);
936    else if (action->command == kHICommandQuit)
937        ret = qt_mac_applicationmenu_string(5).arg(qAppName());
938#else
939    else if (action->menuItem == [loader aboutMenuItem]) {
940        ret = qt_mac_applicationmenu_string(6).arg(qAppName());
941    } else if (action->menuItem == [loader aboutQtMenuItem]) {
942        if (action->action->text() == QString("About Qt"))
943            ret = QMenuBar::tr("About Qt");
944        else
945            ret = action->action->text();
946    } else if (action->menuItem == [loader preferencesMenuItem]) {
947        ret = qt_mac_applicationmenu_string(4);
948    } else if (action->menuItem == [loader quitMenuItem]) {
949        ret = qt_mac_applicationmenu_string(5).arg(qAppName());
950    }
951#endif
952    return ret;
953}
954
955static QKeySequence qt_mac_menu_merge_accel(QMacMenuAction *action)
956{
957    QKeySequence ret;
958#ifdef QT_MAC_USE_COCOA
959    QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
960#endif
961    if (action->action->menuRole() == QAction::ApplicationSpecificRole)
962        ret = action->action->shortcut();
963#ifndef QT_MAC_USE_COCOA
964    else if (action->command == kHICommandPreferences)
965        ret = QKeySequence(QKeySequence::Preferences);
966    else if (action->command == kHICommandQuit)
967        ret = QKeySequence(QKeySequence::Quit);
968#else
969    else if (action->menuItem == [loader preferencesMenuItem])
970        ret = QKeySequence(QKeySequence::Preferences);
971    else if (action->menuItem == [loader quitMenuItem])
972        ret = QKeySequence(QKeySequence::Quit);
973#endif
974    return ret;
975}
976
977void Q_GUI_EXPORT qt_mac_set_menubar_icons(bool b)
978{ QApplication::instance()->setAttribute(Qt::AA_DontShowIconsInMenus, !b); }
979void Q_GUI_EXPORT qt_mac_set_native_menubar(bool b)
980{  QApplication::instance()->setAttribute(Qt::AA_DontUseNativeMenuBar, !b); }
981void Q_GUI_EXPORT qt_mac_set_menubar_merge(bool b) { qt_mac_no_menubar_merge = !b; }
982
983/*****************************************************************************
984  QMenu bindings
985 *****************************************************************************/
986QMenuPrivate::QMacMenuPrivate::QMacMenuPrivate() : menu(0)
987{
988}
989
990QMenuPrivate::QMacMenuPrivate::~QMacMenuPrivate()
991{
992#ifndef QT_MAC_USE_COCOA
993    for(QList<QMacMenuAction*>::Iterator it = actionItems.begin(); it != actionItems.end(); ++it) {
994        QMacMenuAction *action = (*it);
995        RemoveMenuCommandProperty(action->menu, action->command, kMenuCreatorQt, kMenuPropertyQAction);
996        if (action->merged) {
997            QMenuMergeList *list = 0;
998            GetMenuItemProperty(action->menu, 0, kMenuCreatorQt, kMenuPropertyMergeList,
999                                sizeof(list), 0, &list);
1000            for(int i = 0; list && i < list->size(); ) {
1001                QMenuMergeItem item = list->at(i);
1002                if (item.action == action)
1003                    list->removeAt(i);
1004                else
1005                    ++i;
1006            }
1007        }
1008        delete action;
1009    }
1010    if (menu) {
1011        EventHandlerHash::iterator it = menu_eventHandlers_hash()->find(menu);
1012        while (it != menu_eventHandlers_hash()->end() && it.key() == menu) {
1013            RemoveEventHandler(it.value());
1014            ++it;
1015        }
1016        menu_eventHandlers_hash()->remove(menu);
1017        ReleaseMenu(menu);
1018    }
1019#else
1020    QMacCocoaAutoReleasePool pool;
1021    while (actionItems.size()) {
1022        QMacMenuAction *action = actionItems.takeFirst();
1023        if (QMenuMergeList *list = mergeMenuItemsHash.value(action->menu)) {
1024            int i = 0;
1025            while (i < list->size()) {
1026                const QMenuMergeItem &item = list->at(i);
1027                if (item.action == action)
1028                    list->removeAt(i);
1029                else
1030                    ++i;
1031            }
1032        }
1033        delete action;
1034    }
1035    mergeMenuHash.remove(menu);
1036    mergeMenuItemsHash.remove(menu);
1037    [menu release];
1038#endif
1039}
1040
1041void
1042QMenuPrivate::QMacMenuPrivate::addAction(QAction *a, QMacMenuAction *before, QMenuPrivate *qmenu)
1043{
1044    QMacMenuAction *action = new QMacMenuAction;
1045    action->action = a;
1046    action->ignore_accel = 0;
1047    action->merged = 0;
1048    action->menu = 0;
1049#ifndef QT_MAC_USE_COCOA
1050    action->command = qt_mac_menu_static_cmd_id++;
1051#endif
1052    addAction(action, before, qmenu);
1053}
1054
1055void
1056QMenuPrivate::QMacMenuPrivate::addAction(QMacMenuAction *action, QMacMenuAction *before, QMenuPrivate *qmenu)
1057{
1058#ifdef QT_MAC_USE_COCOA
1059    QMacCocoaAutoReleasePool pool;
1060    Q_UNUSED(qmenu);
1061#endif
1062    if (!action)
1063        return;
1064    int before_index = actionItems.indexOf(before);
1065    if (before_index < 0) {
1066        before = 0;
1067        before_index = actionItems.size();
1068    }
1069    actionItems.insert(before_index, action);
1070
1071#ifndef QT_MAC_USE_COCOA
1072    int index = qt_mac_menu_find_action(menu, action);
1073#else
1074    [menu retain];
1075    [action->menu release];
1076#endif
1077    action->menu = menu;
1078
1079    /* When the action is considered a mergable action it
1080       will stay that way, until removed.. */
1081    if (!qt_mac_no_menubar_merge) {
1082#ifndef QT_MAC_USE_COCOA
1083        MenuRef merge = 0;
1084        GetMenuItemProperty(menu, 0, kMenuCreatorQt, kMenuPropertyMergeMenu,
1085                            sizeof(merge), 0, &merge);
1086#else
1087        OSMenuRef merge = QMenuPrivate::mergeMenuHash.value(menu);
1088#endif
1089        if (merge) {
1090#ifndef QT_MAC_USE_COCOA
1091            if (MenuCommand cmd = qt_mac_menu_merge_action(merge, action)) {
1092                action->merged = 1;
1093                action->menu = merge;
1094                action->command = cmd;
1095                if (qt_mac_auto_apple_menu(cmd))
1096                    index = 0; //no need
1097
1098                QMenuMergeList *list = 0;
1099                if (GetMenuItemProperty(merge, 0, kMenuCreatorQt, kMenuPropertyMergeList,
1100                                       sizeof(list), 0, &list) != noErr || !list) {
1101                    list = new QMenuMergeList;
1102                    SetMenuItemProperty(merge, 0, kMenuCreatorQt, kMenuPropertyMergeList,
1103                                        sizeof(list), &list);
1104                }
1105                list->append(QMenuMergeItem(cmd, action));
1106            }
1107#else
1108            if (NSMenuItem *cmd = qt_mac_menu_merge_action(merge, action)) {
1109                action->merged = 1;
1110                [merge retain];
1111                [action->menu release];
1112                action->menu = merge;
1113                [cmd retain];
1114                [cmd setAction:@selector(qtDispatcherToQAction:)];
1115                [cmd setTarget:nil];
1116                [action->menuItem release];
1117                action->menuItem = cmd;
1118                QMenuMergeList *list = QMenuPrivate::mergeMenuItemsHash.value(merge);
1119                if (!list) {
1120                    list = new QMenuMergeList;
1121                    QMenuPrivate::mergeMenuItemsHash.insert(merge, list);
1122                }
1123                list->append(QMenuMergeItem(cmd, action));
1124            }
1125#endif
1126        }
1127    }
1128
1129#ifdef QT_MAC_USE_COCOA
1130    NSMenuItem *newItem = action->menuItem;
1131#endif
1132    if (
1133#ifndef QT_MAC_USE_COCOA
1134        index == -1
1135#else
1136        newItem == 0
1137#endif
1138       ) {
1139#ifndef QT_MAC_USE_COCOA
1140        index = before_index;
1141        MenuItemAttributes attr = kMenuItemAttrAutoRepeat;
1142#else
1143        newItem = createNSMenuItem(action->action->text());
1144        action->menuItem = newItem;
1145#endif
1146        if (before) {
1147#ifndef QT_MAC_USE_COCOA
1148            InsertMenuItemTextWithCFString(action->menu, 0, qMax(before_index, 0), attr, action->command);
1149#else
1150            [menu insertItem:newItem atIndex:qMax(before_index, 0)];
1151#endif
1152        } else {
1153#ifndef QT_MAC_USE_COCOA
1154            // Append the menu item to the menu. If it is a kHICommandAbout or a kHICommandAboutQt append
1155            // a separator also (to get a separator above "Preferences"), but make sure that we don't
1156            // add separators between two "about" items.
1157
1158            // Build a set of all commands that could possibly be before the separator.
1159            QSet<MenuCommand> mergedItems;
1160            mergedItems.insert(kHICommandAbout);
1161            mergedItems.insert(kHICommandAboutQt);
1162            mergedItems.insert(kHICommandCustomMerge);
1163
1164            QMenuMergeList *list = 0;
1165            if (GetMenuItemProperty(action->menu, 0, kMenuCreatorQt, kMenuPropertyMergeList,
1166                        sizeof(list), 0, &list) == noErr && list) {
1167                for (int i = 0; i < list->size(); ++i) {
1168                    MenuCommand command = list->at(i).command;
1169                    if (command > kHICommandCustomMerge) {
1170                        mergedItems.insert(command);
1171                    }
1172                }
1173            }
1174
1175            const int itemCount = CountMenuItems(action->menu);
1176            MenuItemAttributes testattr;
1177            GetMenuItemAttributes(action->menu, itemCount , &testattr);
1178            if (mergedItems.contains(action->command)
1179                 && (testattr & kMenuItemAttrSeparator)) {
1180                InsertMenuItemTextWithCFString(action->menu, 0, qMax(itemCount - 1, 0), attr, action->command);
1181                index = itemCount;
1182            } else {
1183                MenuItemIndex tmpIndex;
1184                AppendMenuItemTextWithCFString(action->menu, 0, attr, action->command, &tmpIndex);
1185                index = tmpIndex;
1186                if (mergedItems.contains(action->command))
1187                    AppendMenuItemTextWithCFString(action->menu, 0, kMenuItemAttrSeparator, 0, &tmpIndex);
1188            }
1189#else
1190            [menu addItem:newItem];
1191#endif
1192        }
1193
1194        QWidget *widget = qmenu ? qmenu->widgetItems.value(action->action) : 0;
1195        if (widget) {
1196#ifndef QT_MAC_USE_COCOA
1197            ChangeMenuAttributes(action->menu, kMenuAttrDoNotCacheImage, 0);
1198            attr = kMenuItemAttrCustomDraw;
1199            SetMenuItemProperty(action->menu, index, kMenuCreatorQt, kMenuPropertyWidgetActionWidget,
1200                                sizeof(QWidget *), &widget);
1201            HIViewRef content;
1202            HIMenuGetContentView(action->menu, kThemeMenuTypePullDown, &content);
1203
1204            EventHandlerRef eventHandlerRef;
1205            InstallMenuEventHandler(action->menu, qt_mac_widget_in_menu_eventHandler,
1206                                    GetEventTypeCount(widget_in_menu_events),
1207                                    widget_in_menu_events, 0, &eventHandlerRef);
1208            menu_eventHandlers_hash()->insert(action->menu, eventHandlerRef);
1209
1210            QWidget *menuWidget = 0;
1211            GetMenuItemProperty(action->menu, 0, kMenuCreatorQt, kMenuPropertyWidgetMenu,
1212                                sizeof(menuWidget), 0, &menuWidget);
1213            if(!menuWidget) {
1214                menuWidget = new QMacNativeWidget(content);
1215                SetMenuItemProperty(menu, 0, kMenuCreatorQt, kMenuPropertyWidgetMenu,
1216                                    sizeof(menuWidget), &menuWidget);
1217                menuWidget->show();
1218            }
1219            widget->setParent(menuWidget);
1220#else
1221            QMacNativeWidget *container = new QMacNativeWidget(0);
1222            container->resize(widget->sizeHint());
1223            widget->setAttribute(Qt::WA_LayoutUsesWidgetRect);
1224            widget->setParent(container);
1225
1226            NSView *containerView = qt_mac_nativeview_for(container);
1227            [containerView setAutoresizesSubviews:YES];
1228            [containerView setAutoresizingMask:NSViewWidthSizable];
1229            [qt_mac_nativeview_for(widget) setAutoresizingMask:NSViewWidthSizable];
1230
1231            [newItem setView:containerView];
1232            container->show();
1233#endif
1234            widget->show();
1235        }
1236
1237    } else {
1238#ifndef QT_MAC_USE_COCOA
1239        qt_mac_command_set_enabled(action->menu, action->command, !QApplicationPrivate::modalState());
1240#else
1241        [newItem setEnabled:!QApplicationPrivate::modalState()];
1242#endif
1243    }
1244#ifndef QT_MAC_USE_COCOA
1245    SetMenuCommandProperty(action->menu, action->command, kMenuCreatorQt, kMenuPropertyQAction,
1246                           sizeof(action), &action);
1247#else
1248    [newItem setTag:long(static_cast<QAction *>(action->action))];
1249#endif
1250    syncAction(action);
1251}
1252
1253// return an autoreleased string given a QKeySequence (currently only looks at the first one).
1254NSString *keySequenceToKeyEqivalent(const QKeySequence &accel)
1255{
1256    quint32 accel_key = (accel[0] & ~(Qt::MODIFIER_MASK | Qt::UNICODE_ACCEL));
1257    extern QChar qtKey2CocoaKey(Qt::Key key);
1258    QChar cocoa_key = qtKey2CocoaKey(Qt::Key(accel_key));
1259    if (cocoa_key.isNull())
1260        cocoa_key = QChar(accel_key).toLower().unicode();
1261    return [NSString stringWithCharacters:&cocoa_key.unicode() length:1];
1262}
1263
1264// return the cocoa modifier mask for the QKeySequence (currently only looks at the first one).
1265NSUInteger keySequenceModifierMask(const QKeySequence &accel)
1266{
1267    return constructModifierMask(accel[0]);
1268}
1269
1270void
1271QMenuPrivate::QMacMenuPrivate::syncAction(QMacMenuAction *action)
1272{
1273    if (!action)
1274        return;
1275
1276#ifndef QT_MAC_USE_COCOA
1277    const int index = qt_mac_menu_find_action(action->menu, action);
1278    if (index == -1)
1279        return;
1280#else
1281    NSMenuItem *item = action->menuItem;
1282    if (!item)
1283        return;
1284#endif
1285
1286#ifndef QT_MAC_USE_COCOA
1287    if (!action->action->isVisible()) {
1288        ChangeMenuItemAttributes(action->menu, index, kMenuItemAttrHidden, 0);
1289        return;
1290    }
1291    ChangeMenuItemAttributes(action->menu, index, 0, kMenuItemAttrHidden);
1292#else
1293    QMacCocoaAutoReleasePool pool;
1294    NSMenu *menu = [item menu];
1295    bool actionVisible = action->action->isVisible();
1296    [item setHidden:!actionVisible];
1297    if (!actionVisible)
1298        return;
1299#endif
1300
1301#ifndef QT_MAC_USE_COCOA
1302    if (action->action->isSeparator()) {
1303        ChangeMenuItemAttributes(action->menu, index, kMenuItemAttrSeparator, 0);
1304        return;
1305    }
1306    ChangeMenuItemAttributes(action->menu, index, 0, kMenuItemAttrSeparator);
1307#else
1308    int itemIndex = [menu indexOfItem:item];
1309    Q_ASSERT(itemIndex != -1);
1310    if (action->action->isSeparator()) {
1311        action->menuItem = [NSMenuItem separatorItem];
1312        [action->menuItem retain];
1313        [menu insertItem: action->menuItem atIndex:itemIndex];
1314        [menu removeItem:item];
1315        [item release];
1316        item = action->menuItem;
1317        return;
1318    } else if ([item isSeparatorItem]) {
1319        // I'm no longer a separator...
1320        action->menuItem = createNSMenuItem(action->action->text());
1321        [menu insertItem:action->menuItem atIndex:itemIndex];
1322        [menu removeItem:item];
1323        [item release];
1324        item = action->menuItem;
1325    }
1326#endif
1327
1328    //find text (and accel)
1329    action->ignore_accel = 0;
1330    QString text = action->action->text();
1331    QKeySequence accel = action->action->shortcut();
1332    {
1333        int st = text.lastIndexOf(QLatin1Char('\t'));
1334        if (st != -1) {
1335            action->ignore_accel = 1;
1336            accel = QKeySequence(text.right(text.length()-(st+1)));
1337            text.remove(st, text.length()-st);
1338        }
1339    }
1340    {
1341        QString cmd_text = qt_mac_menu_merge_text(action);
1342        if (!cmd_text.isEmpty()) {
1343            text = cmd_text;
1344            accel = qt_mac_menu_merge_accel(action);
1345        }
1346    }
1347    // Show multiple key sequences as part of the menu text.
1348    if (accel.count() > 1)
1349        text += QLatin1String(" (") + accel.toString(QKeySequence::NativeText) + QLatin1String(")");
1350
1351    QString finalString = qt_mac_removeMnemonics(text);
1352
1353#ifndef QT_MAC_USE_COCOA
1354    MenuItemDataRec data;
1355    memset(&data, '\0', sizeof(data));
1356
1357    //Carbon text
1358    data.whichData |= kMenuItemDataCFString;
1359    QCFString cfstring(finalString);  // Hold the reference to the end of the function.
1360    data.cfText = cfstring;
1361
1362    // Carbon enabled
1363    data.whichData |= kMenuItemDataEnabled;
1364    data.enabled = action->action->isEnabled();
1365    // Carbon icon
1366    data.whichData |= kMenuItemDataIconHandle;
1367    if (!action->action->icon().isNull()
1368            && action->action->isIconVisibleInMenu()) {
1369        data.iconType = kMenuIconRefType;
1370        data.iconHandle = (Handle)qt_mac_create_iconref(action->action->icon().pixmap(16, QIcon::Normal));
1371    } else {
1372        data.iconType = kMenuNoIcon;
1373    }
1374    if (action->action->font().resolve()) { // Carbon font
1375        if (action->action->font().bold())
1376            data.style |= bold;
1377        if (action->action->font().underline())
1378            data.style |= underline;
1379        if (action->action->font().italic())
1380            data.style |= italic;
1381        if (data.style)
1382            data.whichData |= kMenuItemDataStyle;
1383        data.whichData |= kMenuItemDataFontID;
1384        data.fontID = action->action->font().macFontID();
1385    }
1386#else
1387    // Cocoa Font and title
1388    if (action->action->font().resolve()) {
1389        const QFont &actionFont = action->action->font();
1390        NSFont *customMenuFont = [NSFont fontWithName:qt_mac_QStringToNSString(actionFont.family())
1391                                  size:actionFont.pointSize()];
1392        NSArray *keys = [NSArray arrayWithObjects:NSFontAttributeName, nil];
1393        NSArray *objects = [NSArray arrayWithObjects:customMenuFont, nil];
1394        NSDictionary *attributes = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
1395        NSAttributedString *str = [[[NSAttributedString alloc] initWithString:qt_mac_QStringToNSString(finalString)
1396                                 attributes:attributes] autorelease];
1397       [item setAttributedTitle: str];
1398    } else {
1399        [item setTitle: qt_mac_QStringToNSString(finalString)];
1400    }
1401
1402    if (action->action->menuRole() == QAction::AboutRole || action->action->menuRole() == QAction::QuitRole)
1403        [item setTitle:qt_mac_QStringToNSString(text)];
1404    else
1405        [item setTitle:qt_mac_QStringToNSString(qt_mac_removeMnemonics(text))];
1406
1407    // Cocoa Enabled
1408    [item setEnabled: action->action->isEnabled()];
1409
1410    // Cocoa icon
1411    NSImage *nsimage = 0;
1412    if (!action->action->icon().isNull() && action->action->isIconVisibleInMenu()) {
1413        nsimage = static_cast<NSImage *>(qt_mac_create_nsimage(action->action->icon().pixmap(16, QIcon::Normal)));
1414    }
1415    [item setImage:nsimage];
1416    [nsimage release];
1417#endif
1418
1419    if (action->action->menu()) { //submenu
1420#ifndef QT_MAC_USE_COCOA
1421        data.whichData |= kMenuItemDataSubmenuHandle;
1422        data.submenuHandle = action->action->menu()->macMenu();
1423        QWidget *caused = 0;
1424        GetMenuItemProperty(action->menu, 0, kMenuCreatorQt, kMenuPropertyQWidget, sizeof(caused), 0, &caused);
1425        SetMenuItemProperty(data.submenuHandle, 0, kMenuCreatorQt, kMenuPropertyCausedQWidget, sizeof(caused), &caused);
1426#else
1427        NSMenu *subMenu  = static_cast<NSMenu *>(action->action->menu()->macMenu());
1428        if ([subMenu supermenu] && [subMenu supermenu] != [item menu]) {
1429            // The menu is already a sub-menu of another one. Cocoa will throw an exception,
1430            // in such cases. For the time being, a new QMenu with same set of actions is the
1431            // only workaround.
1432            action->action->setEnabled(false);
1433        } else {
1434            [item setSubmenu:subMenu];
1435        }
1436        [item setAction:nil];
1437#endif
1438    } else { //respect some other items
1439#ifndef QT_MAC_USE_COCOA
1440        //shortcuts (say we are setting them all so that we can also clear them).
1441        data.whichData |= kMenuItemDataCmdKey;
1442        data.whichData |= kMenuItemDataCmdKeyModifiers;
1443        data.whichData |= kMenuItemDataCmdKeyGlyph;
1444        if (accel.count() == 1) {
1445            qt_mac_get_accel(accel[0], (quint32*)&data.cmdKeyModifiers, (quint32*)&data.cmdKeyGlyph);
1446            if (data.cmdKeyGlyph == 0)
1447                data.cmdKey = (UniChar)accel[0];
1448        }
1449#else
1450        [item setSubmenu:0];
1451        if ([item action] == nil)
1452            [item setAction:@selector(qtDispatcherToQAction:)];
1453        // No key equivalent set for multiple key QKeySequence.
1454        if (accel.count() == 1) {
1455            [item setKeyEquivalent:keySequenceToKeyEqivalent(accel)];
1456            [item setKeyEquivalentModifierMask:keySequenceModifierMask(accel)];
1457        } else {
1458            [item setKeyEquivalent:@""];
1459            [item setKeyEquivalentModifierMask:NSCommandKeyMask];
1460        }
1461#endif
1462    }
1463#ifndef QT_MAC_USE_COCOA
1464    //mark glyph
1465    data.whichData |= kMenuItemDataMark;
1466    if (action->action->isChecked()) {
1467#if 0
1468        if (action->action->actionGroup() &&
1469           action->action->actionGroup()->isExclusive())
1470            data.mark = diamondMark;
1471        else
1472#endif
1473            data.mark = checkMark;
1474    } else {
1475        data.mark = noMark;
1476    }
1477
1478    //actually set it
1479    SetMenuItemData(action->menu, action->command, true, &data);
1480
1481    // Free up memory
1482    if (data.iconHandle)
1483        ReleaseIconRef(IconRef(data.iconHandle));
1484#else
1485    //mark glyph
1486    [item setState:action->action->isChecked() ?  NSOnState : NSOffState];
1487#endif
1488}
1489
1490void
1491QMenuPrivate::QMacMenuPrivate::removeAction(QMacMenuAction *action)
1492{
1493    if (!action)
1494        return;
1495#ifndef QT_MAC_USE_COCOA
1496    if (action->command == kHICommandQuit || action->command == kHICommandPreferences)
1497        qt_mac_command_set_enabled(action->menu, action->command, false);
1498    else
1499        DeleteMenuItem(action->menu, qt_mac_menu_find_action(action->menu, action));
1500#else
1501    QMacCocoaAutoReleasePool pool;
1502    if (action->merged) {
1503        if (reinterpret_cast<QAction *>([action->menuItem tag]) == action->action) {
1504            QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
1505            [action->menuItem setEnabled:false];
1506            if (action->menuItem != [loader quitMenuItem]
1507                && action->menuItem != [loader preferencesMenuItem]) {
1508                [[action->menuItem menu] removeItem:action->menuItem];
1509            }
1510            if (QMenuMergeList *list = mergeMenuItemsHash.value(action->menu)) {
1511                int i = 0;
1512                while (i < list->size()) {
1513                    const QMenuMergeItem &item = list->at(i);
1514                    if (item.action == action)
1515                        list->removeAt(i);
1516                    else
1517                        ++i;
1518                }
1519            }
1520        }
1521    } else {
1522        [[action->menuItem menu] removeItem:action->menuItem];
1523    }
1524#endif
1525    actionItems.removeAll(action);
1526}
1527
1528OSMenuRef
1529QMenuPrivate::macMenu(OSMenuRef merge)
1530{
1531    Q_UNUSED(merge);
1532    Q_Q(QMenu);
1533    if (mac_menu && mac_menu->menu)
1534        return mac_menu->menu;
1535    if (!mac_menu)
1536        mac_menu = new QMacMenuPrivate;
1537    mac_menu->menu = qt_mac_create_menu(q);
1538    if (merge) {
1539#ifndef QT_MAC_USE_COCOA
1540        SetMenuItemProperty(mac_menu->menu, 0, kMenuCreatorQt, kMenuPropertyMergeMenu, sizeof(merge), &merge);
1541#else
1542        mergeMenuHash.insert(mac_menu->menu, merge);
1543#endif
1544    }
1545    QList<QAction*> items = q->actions();
1546    for(int i = 0; i < items.count(); i++)
1547        mac_menu->addAction(items[i], 0, this);
1548    syncSeparatorsCollapsible(collapsibleSeparators);
1549    return mac_menu->menu;
1550}
1551
1552/*!
1553  \internal
1554*/
1555void
1556QMenuPrivate::syncSeparatorsCollapsible(bool collapse)
1557{
1558#ifndef QT_MAC_USE_COCOA
1559    if (collapse)
1560        ChangeMenuAttributes(mac_menu->menu, kMenuAttrCondenseSeparators, 0);
1561    else
1562        ChangeMenuAttributes(mac_menu->menu, 0, kMenuAttrCondenseSeparators);
1563#else
1564    qt_mac_menu_collapseSeparators(mac_menu->menu, collapse);
1565#endif
1566}
1567
1568
1569
1570#ifndef QT_MAC_USE_COCOA
1571/*!
1572  \internal
1573*/
1574void QMenuPrivate::setMacMenuEnabled(bool enable)
1575{
1576    if (!macMenu(0))
1577        return;
1578
1579    QMacCocoaAutoReleasePool pool;
1580    if (enable) {
1581        for (int i = 0; i < mac_menu->actionItems.count(); ++i) {
1582            QMacMenuAction *menuItem = mac_menu->actionItems.at(i);
1583            if (menuItem && menuItem->action && menuItem->action->isEnabled()) {
1584                // Only enable those items which contains an enabled QAction.
1585                // i == 0 -> the menu itself, hence i + 1 for items.
1586                EnableMenuItem(mac_menu->menu, i + 1);
1587            }
1588        }
1589    } else {
1590        DisableAllMenuItems(mac_menu->menu);
1591    }
1592}
1593#endif
1594
1595/*!
1596    \internal
1597
1598    This function will return the OSMenuRef used to create the native menu bar
1599    bindings.
1600
1601    If Qt is built against Carbon, the OSMenuRef is a MenuRef that can be used
1602    with Carbon's Menu Manager API.
1603
1604    If Qt is built against Cocoa, the OSMenuRef is a NSMenu pointer.
1605
1606    \warning This function is not portable.
1607
1608    \sa QMenuBar::macMenu()
1609*/
1610OSMenuRef QMenu::macMenu(OSMenuRef merge) { return d_func()->macMenu(merge); }
1611
1612/*****************************************************************************
1613  QMenuBar bindings
1614 *****************************************************************************/
1615typedef QHash<QWidget *, QMenuBar *> MenuBarHash;
1616Q_GLOBAL_STATIC(MenuBarHash, menubars)
1617static QMenuBar *fallback = 0;
1618QMenuBarPrivate::QMacMenuBarPrivate::QMacMenuBarPrivate() : menu(0), apple_menu(0)
1619{
1620}
1621
1622QMenuBarPrivate::QMacMenuBarPrivate::~QMacMenuBarPrivate()
1623{
1624    for(QList<QMacMenuAction*>::Iterator it = actionItems.begin(); it != actionItems.end(); ++it)
1625        delete (*it);
1626#ifndef QT_MAC_USE_COCOA
1627    if (apple_menu) {
1628        QMenuMergeList *list = 0;
1629        GetMenuItemProperty(apple_menu, 0, kMenuCreatorQt, kMenuPropertyMergeList,
1630                            sizeof(list), 0, &list);
1631        if (list) {
1632            RemoveMenuItemProperty(apple_menu, 0, kMenuCreatorQt, kMenuPropertyMergeList);
1633            delete list;
1634        }
1635        ReleaseMenu(apple_menu);
1636    }
1637    if (menu)
1638        ReleaseMenu(menu);
1639#else
1640    [apple_menu release];
1641    [menu release];
1642#endif
1643}
1644
1645void
1646QMenuBarPrivate::QMacMenuBarPrivate::addAction(QAction *a, QAction *before)
1647{
1648    if (a->isSeparator() || !menu)
1649        return;
1650    QMacMenuAction *action = new QMacMenuAction;
1651    action->action = a;
1652    action->ignore_accel = 1;
1653#ifndef QT_MAC_USE_COCOA
1654    action->command = qt_mac_menu_static_cmd_id++;
1655#endif
1656    addAction(action, findAction(before));
1657}
1658
1659void
1660QMenuBarPrivate::QMacMenuBarPrivate::addAction(QMacMenuAction *action, QMacMenuAction *before)
1661{
1662    if (!action || !menu)
1663        return;
1664
1665    int before_index = actionItems.indexOf(before);
1666    if (before_index < 0) {
1667        before = 0;
1668        before_index = actionItems.size();
1669    }
1670    actionItems.insert(before_index, action);
1671
1672    MenuItemIndex index = actionItems.size()-1;
1673
1674    action->menu = menu;
1675#ifdef QT_MAC_USE_COCOA
1676    QMacCocoaAutoReleasePool pool;
1677    [action->menu retain];
1678    NSMenuItem *newItem = createNSMenuItem(action->action->text());
1679    action->menuItem = newItem;
1680#endif
1681    if (before) {
1682#ifndef QT_MAC_USE_COCOA
1683        InsertMenuItemTextWithCFString(action->menu, 0, qMax(1, before_index+1), 0, action->command);
1684#else
1685        [menu insertItem:newItem atIndex:qMax(1, before_index + 1)];
1686#endif
1687        index = before_index;
1688    } else {
1689#ifndef QT_MAC_USE_COCOA
1690        AppendMenuItemTextWithCFString(action->menu, 0, 0, action->command, &index);
1691#else
1692        [menu addItem:newItem];
1693#endif
1694    }
1695#ifndef QT_MAC_USE_COCOA
1696    SetMenuItemProperty(action->menu, index, kMenuCreatorQt, kMenuPropertyQAction, sizeof(action),
1697                        &action);
1698#else
1699    [newItem setTag:long(static_cast<QAction *>(action->action))];
1700#endif
1701    syncAction(action);
1702}
1703
1704void
1705QMenuBarPrivate::QMacMenuBarPrivate::syncAction(QMacMenuAction *action)
1706{
1707    if (!action || !menu)
1708        return;
1709#ifndef QT_MAC_USE_COCOA
1710    const int index = qt_mac_menu_find_action(action->menu, action);
1711#else
1712    QMacCocoaAutoReleasePool pool;
1713    NSMenuItem *item = action->menuItem;
1714#endif
1715
1716    OSMenuRef submenu = 0;
1717    bool release_submenu = false;
1718    if (action->action->menu()) {
1719        if ((submenu = action->action->menu()->macMenu(apple_menu))) {
1720#ifndef QT_MAC_USE_COCOA
1721            QWidget *caused = 0;
1722            GetMenuItemProperty(action->menu, 0, kMenuCreatorQt, kMenuPropertyQWidget, sizeof(caused), 0, &caused);
1723            SetMenuItemProperty(submenu, 0, kMenuCreatorQt, kMenuPropertyCausedQWidget, sizeof(caused), &caused);
1724#else
1725            if ([submenu supermenu] && [submenu supermenu] != [item menu])
1726                return;
1727            else
1728                [item setSubmenu:submenu];
1729#endif
1730        }
1731#ifndef QT_MAC_USE_COCOA
1732    } else { // create a submenu to act as menu
1733        release_submenu = true;
1734        CreateNewMenu(0, 0, &submenu);
1735#endif
1736    }
1737
1738    if (submenu) {
1739        bool visible = actualMenuItemVisibility(this, action);
1740#ifndef QT_MAC_USE_COCOA
1741        SetMenuItemHierarchicalMenu(action->menu, index, submenu);
1742        SetMenuTitleWithCFString(submenu, QCFString(qt_mac_removeMnemonics(action->action->text())));
1743        if (visible)
1744            ChangeMenuAttributes(submenu, 0, kMenuAttrHidden);
1745        else
1746            ChangeMenuAttributes(submenu, kMenuAttrHidden, 0);
1747#else
1748        [item setSubmenu: submenu];
1749        [submenu setTitle:qt_mac_QStringToNSString(qt_mac_removeMnemonics(action->action->text()))];
1750        syncNSMenuItemVisiblity(item, visible);
1751        syncNSMenuItemEnabled(item, action->action->isEnabled());
1752#endif
1753        if (release_submenu) { //no pointers to it
1754#ifndef QT_MAC_USE_COCOA
1755            ReleaseMenu(submenu);
1756#else
1757            [submenu release];
1758#endif
1759        }
1760    } else {
1761        qWarning("QMenu: No OSMenuRef created for popup menu");
1762    }
1763}
1764
1765void
1766QMenuBarPrivate::QMacMenuBarPrivate::removeAction(QMacMenuAction *action)
1767{
1768    if (!action || !menu)
1769        return;
1770#ifndef QT_MAC_USE_COCOA
1771    DeleteMenuItem(action->menu, qt_mac_menu_find_action(action->menu, action));
1772#else
1773    QMacCocoaAutoReleasePool pool;
1774    [action->menu removeItem:action->menuItem];
1775#endif
1776    actionItems.removeAll(action);
1777}
1778
1779bool QMenuBarPrivate::macWidgetHasNativeMenubar(QWidget *widget)
1780{
1781    // This function is different from q->isNativeMenuBar(), as
1782    // it returns true only if a native menu bar is actually
1783    // _created_.
1784    if (!widget)
1785        return false;
1786    return menubars()->contains(widget->window());
1787}
1788
1789void
1790QMenuBarPrivate::macCreateMenuBar(QWidget *parent)
1791{
1792    Q_Q(QMenuBar);
1793    static int dontUseNativeMenuBar = -1;
1794    // We call the isNativeMenuBar function here
1795    // because that will make sure that local overrides
1796    // are dealt with correctly. q->isNativeMenuBar() will, if not
1797    // overridden, depend on the attribute Qt::AA_DontUseNativeMenuBar:
1798    bool qt_mac_no_native_menubar = !q->isNativeMenuBar();
1799    if (qt_mac_no_native_menubar == false && dontUseNativeMenuBar < 0) {
1800        // The menubar is set to be native. Let's check (one time only
1801        // for all menubars) if this is OK with the rest of the environment.
1802        // As a result, Qt::AA_DontUseNativeMenuBar is set. NB: the application
1803        // might still choose to not respect, or change, this flag.
1804        bool isPlugin = QApplication::testAttribute(Qt::AA_MacPluginApplication);
1805        bool environmentSaysNo = !qgetenv("QT_MAC_NO_NATIVE_MENUBAR").isEmpty();
1806        dontUseNativeMenuBar = isPlugin || environmentSaysNo;
1807        QApplication::instance()->setAttribute(Qt::AA_DontUseNativeMenuBar, dontUseNativeMenuBar);
1808        qt_mac_no_native_menubar = !q->isNativeMenuBar();
1809    }
1810    if (qt_mac_no_native_menubar == false) {
1811        // INVARIANT: Use native menubar.
1812        extern void qt_event_request_menubarupdate(); //qapplication_mac.cpp
1813        qt_event_request_menubarupdate();
1814        if (!parent && !fallback) {
1815            fallback = q;
1816            mac_menubar = new QMacMenuBarPrivate;
1817        } else if (parent && parent->isWindow()) {
1818            menubars()->insert(q->window(), q);
1819            mac_menubar = new QMacMenuBarPrivate;
1820        }
1821    }
1822}
1823
1824void QMenuBarPrivate::macDestroyMenuBar()
1825{
1826    Q_Q(QMenuBar);
1827    QMacCocoaAutoReleasePool pool;
1828    if (fallback == q)
1829        fallback = 0;
1830    delete mac_menubar;
1831    QWidget *tlw = q->window();
1832    menubars()->remove(tlw);
1833    mac_menubar = 0;
1834
1835    if (!qt_mac_current_menubar.qmenubar || qt_mac_current_menubar.qmenubar == q) {
1836#ifdef QT_MAC_USE_COCOA
1837        QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
1838        [loader removeActionsFromAppMenu];
1839#else
1840        cancelAllMenuTracking();
1841#endif
1842        extern void qt_event_request_menubarupdate(); //qapplication_mac.cpp
1843        qt_event_request_menubarupdate();
1844    }
1845}
1846
1847OSMenuRef QMenuBarPrivate::macMenu()
1848{
1849    Q_Q(QMenuBar);
1850    if (!q->isNativeMenuBar() || !mac_menubar) {
1851        return 0;
1852    } else if (!mac_menubar->menu) {
1853        mac_menubar->menu = qt_mac_create_menu(q);
1854#ifdef QT_MAC_USE_COCOA
1855        [mac_menubar->menu setAutoenablesItems:NO];
1856#endif
1857        ProcessSerialNumber mine, front;
1858        if (GetCurrentProcess(&mine) == noErr && GetFrontProcess(&front) == noErr) {
1859            if (!qt_mac_no_menubar_merge && !mac_menubar->apple_menu) {
1860                mac_menubar->apple_menu = qt_mac_create_menu(q);
1861#ifndef QT_MAC_USE_COCOA
1862                MenuItemIndex index;
1863                AppendMenuItemTextWithCFString(mac_menubar->menu, 0, 0, 0, &index);
1864
1865                SetMenuTitleWithCFString(mac_menubar->apple_menu, QCFString(QString(QChar(0x14))));
1866                SetMenuItemHierarchicalMenu(mac_menubar->menu, index, mac_menubar->apple_menu);
1867                SetMenuItemProperty(mac_menubar->apple_menu, 0, kMenuCreatorQt, kMenuPropertyQWidget, sizeof(q), &q);
1868#else
1869                [mac_menubar->apple_menu setTitle:qt_mac_QStringToNSString(QString(QChar(0x14)))];
1870                NSMenuItem *apple_menuItem = [[NSMenuItem alloc] init];
1871                [apple_menuItem setSubmenu:mac_menubar->menu];
1872                [mac_menubar->apple_menu addItem:apple_menuItem];
1873                [apple_menuItem release];
1874#endif
1875            }
1876            if (mac_menubar->apple_menu) {
1877#ifndef QT_MAC_USE_COCOA
1878                SetMenuItemProperty(mac_menubar->menu, 0, kMenuCreatorQt, kMenuPropertyMergeMenu,
1879                                    sizeof(mac_menubar->apple_menu), &mac_menubar->apple_menu);
1880#else
1881                QMenuPrivate::mergeMenuHash.insert(mac_menubar->menu, mac_menubar->apple_menu);
1882#endif
1883            }
1884            QList<QAction*> items = q->actions();
1885            for(int i = 0; i < items.count(); i++)
1886                mac_menubar->addAction(items[i]);
1887        }
1888    }
1889    return mac_menubar->menu;
1890}
1891
1892/*!
1893    \internal
1894
1895    This function will return the OSMenuRef used to create the native menu bar
1896    bindings. This OSMenuRef is then set as the root menu for the Menu
1897    Manager.
1898
1899    \warning This function is not portable.
1900
1901    \sa QMenu::macMenu()
1902*/
1903OSMenuRef QMenuBar::macMenu() { return d_func()->macMenu(); }
1904
1905/* !
1906    \internal
1907    Ancestor function that crosses windows (QWidget::isAncestorOf
1908    only considers widgets within the same window).
1909*/
1910static bool qt_mac_is_ancestor(QWidget* possibleAncestor, QWidget *child)
1911{
1912    if (!possibleAncestor)
1913        return false;
1914
1915    QWidget * current = child->parentWidget();
1916    while (current != 0) {
1917        if (current == possibleAncestor)
1918            return true;
1919        current = current->parentWidget();
1920    }
1921    return false;
1922}
1923
1924/* !
1925    \internal
1926    Returns true if the entries of menuBar should be disabled,
1927    based on the modality type of modalWidget.
1928*/
1929static bool qt_mac_should_disable_menu(QMenuBar *menuBar)
1930{
1931    QWidget *modalWidget = qApp->activeModalWidget();
1932    if (!modalWidget)
1933        return false;
1934
1935    if (menuBar && menuBar == menubars()->value(modalWidget))
1936        // The menu bar is owned by the modal widget.
1937        // In that case we should enable it:
1938        return false;
1939
1940    // When there is an application modal window on screen, the entries of
1941    // the menubar should be disabled. The exception in Qt is that if the
1942    // modal window is the only window on screen, then we enable the menu bar.
1943    QWidget *w = modalWidget;
1944    QWidgetList topLevelWidgets = QApplication::topLevelWidgets();
1945    while (w) {
1946        if (w->isVisible() && w->windowModality() == Qt::ApplicationModal) {
1947            for (int i=0; i<topLevelWidgets.size(); ++i) {
1948                QWidget *top = topLevelWidgets.at(i);
1949                if (w != top && top->isVisible()) {
1950                    // INVARIANT: we found another visible window
1951                    // on screen other than our modalWidget. We therefore
1952                    // disable the menu bar to follow normal modality logic:
1953                    return true;
1954                }
1955            }
1956            // INVARIANT: We have only one window on screen that happends
1957            // to be application modal. We choose to enable the menu bar
1958            // in that case to e.g. enable the quit menu item.
1959            return false;
1960        }
1961        w = w->parentWidget();
1962    }
1963
1964    // INVARIANT: modalWidget is window modal. Disable menu entries
1965    // if the menu bar belongs to an ancestor of modalWidget. If menuBar
1966    // is nil, we understand it as the default menu bar set by the nib:
1967    return menuBar ? qt_mac_is_ancestor(menuBar->parentWidget(), modalWidget) : false;
1968}
1969
1970static QWidget *findWindowThatShouldDisplayMenubar()
1971{
1972    QWidget *w = qApp->activeWindow();
1973    if (!w) {
1974        // We have no active window on screen. Try to
1975        // find a window from the list of top levels:
1976        QWidgetList tlws = QApplication::topLevelWidgets();
1977        for(int i = 0; i < tlws.size(); ++i) {
1978            QWidget *tlw = tlws.at(i);
1979            if ((tlw->isVisible() && tlw->windowType() != Qt::Tool &&
1980                tlw->windowType() != Qt::Popup)) {
1981                w = tlw;
1982                break;
1983            }
1984        }
1985    }
1986    return w;
1987}
1988
1989static QMenuBar *findMenubarForWindow(QWidget *w)
1990{
1991    QMenuBar *mb = 0;
1992    if (w) {
1993        mb = menubars()->value(w);
1994#ifndef QT_NO_MAINWINDOW
1995        QDockWidget *dw = qobject_cast<QDockWidget *>(w);
1996        if (!mb && dw) {
1997            QMainWindow *mw = qobject_cast<QMainWindow *>(dw->parentWidget());
1998            if (mw && (mb = menubars()->value(mw)))
1999                w = mw;
2000        }
2001#endif
2002        while(w && !mb)
2003            mb = menubars()->value((w = w->parentWidget()));
2004    }
2005
2006    if (!mb) {
2007        // We could not find a menu bar for the window. Lets
2008        // check if we have a global (parentless) menu bar instead:
2009        mb = fallback;
2010    }
2011
2012    return mb;
2013}
2014
2015void qt_mac_clear_menubar()
2016{
2017    if (QApplication::testAttribute(Qt::AA_MacPluginApplication))
2018        return;
2019
2020#ifndef QT_MAC_USE_COCOA
2021    MenuRef clear_menu = 0;
2022    if (CreateNewMenu(0, 0, &clear_menu) == noErr) {
2023        SetRootMenu(clear_menu);
2024        ReleaseMenu(clear_menu);
2025    } else {
2026        qWarning("QMenu: Internal error at %s:%d", __FILE__, __LINE__);
2027    }
2028    ClearMenuBar();
2029    qt_mac_command_set_enabled(0, kHICommandPreferences, false);
2030    InvalMenuBar();
2031#else
2032    QMacCocoaAutoReleasePool pool;
2033    QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
2034    NSMenu *menu = [loader menu];
2035    [loader ensureAppMenuInMenu:menu];
2036    [[NSApplication sharedApplication] setMainMenu:menu];
2037    const bool modal = qt_mac_should_disable_menu(0);
2038    if (qt_mac_current_menubar.qmenubar || modal != qt_mac_current_menubar.modal)
2039        qt_mac_set_modal_state(menu, modal);
2040    qt_mac_current_menubar.qmenubar = 0;
2041    qt_mac_current_menubar.modal = modal;
2042#endif
2043}
2044
2045/*!
2046  \internal
2047
2048  This function will update the current menu bar and set it as the
2049  active menu bar in the Menu Manager.
2050
2051  \warning This function is not portable.
2052
2053  \sa QMenu::macMenu(), QMenuBar::macMenu()
2054*/
2055bool QMenuBar::macUpdateMenuBar()
2056{
2057#ifdef QT_MAC_USE_COCOA
2058    QMacCocoaAutoReleasePool pool;
2059    qt_cocoaPostMessage(getMenuLoader(), @selector(qtUpdateMenubar));
2060    return true;
2061#else
2062    return QMenuBarPrivate::macUpdateMenuBarImmediatly();
2063#endif
2064}
2065
2066bool QMenuBarPrivate::macUpdateMenuBarImmediatly()
2067{
2068    bool ret = false;
2069    cancelAllMenuTracking();
2070    QWidget *w = findWindowThatShouldDisplayMenubar();
2071    QMenuBar *mb = findMenubarForWindow(w);
2072    extern bool qt_mac_app_fullscreen; //qapplication_mac.mm
2073
2074    // We need to see if we are in full screen mode, if so we need to
2075    // switch the full screen mode to be able to show or hide the menubar.
2076    if(w && mb) {
2077        // This case means we are creating a menubar, check if full screen
2078        if(w->isFullScreen()) {
2079            // Ok, switch to showing the menubar when hovering over it.
2080            SetSystemUIMode(kUIModeAllHidden, kUIOptionAutoShowMenuBar);
2081            qt_mac_app_fullscreen = true;
2082        }
2083    } else if(w) {
2084        // Removing a menubar
2085        if(w->isFullScreen()) {
2086            // Ok, switch to not showing the menubar when hovering on it
2087            SetSystemUIMode(kUIModeAllHidden, 0);
2088            qt_mac_app_fullscreen = true;
2089        }
2090    }
2091
2092    if (mb && mb->isNativeMenuBar()) {
2093        bool modal = QApplicationPrivate::modalState();
2094#ifdef QT_MAC_USE_COCOA
2095        QMacCocoaAutoReleasePool pool;
2096#endif
2097        if (OSMenuRef menu = mb->macMenu()) {
2098#ifndef QT_MAC_USE_COCOA
2099            SetRootMenu(menu);
2100#else
2101            QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
2102            [loader ensureAppMenuInMenu:menu];
2103            [[NSApplication sharedApplication] setMainMenu:menu];
2104            syncMenuBarItemsVisiblity(mb->d_func()->mac_menubar);
2105
2106            if (OSMenuRef tmpMerge = QMenuPrivate::mergeMenuHash.value(menu)) {
2107                if (QMenuMergeList *mergeList
2108                        = QMenuPrivate::mergeMenuItemsHash.value(tmpMerge)) {
2109                    const int mergeListSize = mergeList->size();
2110
2111                    for (int i = 0; i < mergeListSize; ++i) {
2112                        const QMenuMergeItem &mergeItem = mergeList->at(i);
2113                        // Ideally we would call QMenuPrivate::syncAction, but that requires finding
2114                        // the original QMen and likely doing more work than we need.
2115                        // For example, enabled is handled below.
2116                        [mergeItem.menuItem setTag:reinterpret_cast<long>(
2117                                                    static_cast<QAction *>(mergeItem.action->action))];
2118                        [mergeItem.menuItem setHidden:!(mergeItem.action->action->isVisible())];
2119                    }
2120                }
2121            }
2122#endif
2123            // Check if menu is modally shaddowed and should  be disabled:
2124            modal = qt_mac_should_disable_menu(mb);
2125            if (mb != qt_mac_current_menubar.qmenubar || modal != qt_mac_current_menubar.modal)
2126                qt_mac_set_modal_state(menu, modal);
2127        }
2128        qt_mac_current_menubar.qmenubar = mb;
2129        qt_mac_current_menubar.modal = modal;
2130        ret = true;
2131    } else if (qt_mac_current_menubar.qmenubar && qt_mac_current_menubar.qmenubar->isNativeMenuBar()) {
2132        // INVARIANT: The currently active menu bar (if any) is not native. But we do have a
2133        // native menu bar from before. So we need to decide whether or not is should be enabled:
2134        const bool modal = qt_mac_should_disable_menu(qt_mac_current_menubar.qmenubar);
2135        if (modal != qt_mac_current_menubar.modal) {
2136            ret = true;
2137            if (OSMenuRef menu = qt_mac_current_menubar.qmenubar->macMenu()) {
2138#ifndef QT_MAC_USE_COCOA
2139                SetRootMenu(menu);
2140#else
2141                QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
2142                [loader ensureAppMenuInMenu:menu];
2143                [[NSApplication sharedApplication] setMainMenu:menu];
2144                syncMenuBarItemsVisiblity(qt_mac_current_menubar.qmenubar->d_func()->mac_menubar);
2145#endif
2146                qt_mac_set_modal_state(menu, modal);
2147            }
2148            qt_mac_current_menubar.modal = modal;
2149        }
2150    }
2151
2152    if (!ret) {
2153        qt_mac_clear_menubar();
2154    }
2155    return ret;
2156}
2157
2158QHash<OSMenuRef, OSMenuRef> QMenuPrivate::mergeMenuHash;
2159QHash<OSMenuRef, QMenuMergeList*> QMenuPrivate::mergeMenuItemsHash;
2160
2161bool QMenuPrivate::QMacMenuPrivate::merged(const QAction *action) const
2162{
2163#ifndef QT_MAC_USE_COCOA
2164    MenuRef merge = 0;
2165    GetMenuItemProperty(menu, 0, kMenuCreatorQt, kMenuPropertyMergeMenu,
2166            sizeof(merge), 0, &merge);
2167    if (merge) {
2168        QMenuMergeList *list = 0;
2169        if (GetMenuItemProperty(merge, 0, kMenuCreatorQt, kMenuPropertyMergeList,
2170                    sizeof(list), 0, &list) == noErr && list) {
2171            for(int i = 0; i < list->size(); ++i) {
2172                QMenuMergeItem item = list->at(i);
2173                if (item.action->action == action)
2174                    return true;
2175            }
2176        }
2177    }
2178#else
2179    if (OSMenuRef merge = mergeMenuHash.value(menu)) {
2180        if (QMenuMergeList *list = mergeMenuItemsHash.value(merge)) {
2181            for(int i = 0; i < list->size(); ++i) {
2182                const QMenuMergeItem &item = list->at(i);
2183                if (item.action->action == action)
2184                    return true;
2185            }
2186        }
2187    }
2188#endif
2189    return false;
2190}
2191
2192//creation of the OSMenuRef
2193static OSMenuRef qt_mac_create_menu(QWidget *w)
2194{
2195    OSMenuRef ret;
2196#ifndef QT_MAC_USE_COCOA
2197    ret = 0;
2198    if (CreateNewMenu(0, 0, &ret) == noErr) {
2199        qt_mac_create_menu_event_handler();
2200        SetMenuItemProperty(ret, 0, kMenuCreatorQt, kMenuPropertyQWidget, sizeof(w), &w);
2201
2202        // kEventMenuMatchKey is only sent to the menu itself and not to
2203        // the application, install a separate handler for that event.
2204        EventHandlerRef eventHandlerRef;
2205        InstallMenuEventHandler(ret, qt_mac_menu_event,
2206                                GetEventTypeCount(menu_menu_events),
2207                                menu_menu_events, 0, &eventHandlerRef);
2208        menu_eventHandlers_hash()->insert(ret, eventHandlerRef);
2209    } else {
2210        qWarning("QMenu: Internal error");
2211    }
2212#else
2213    if (QMenu *qmenu = qobject_cast<QMenu *>(w)){
2214        ret = [[QT_MANGLE_NAMESPACE(QCocoaMenu) alloc] initWithQMenu:qmenu];
2215    } else {
2216        ret = [[NSMenu alloc] init];
2217    }
2218#endif
2219    return ret;
2220}
2221
2222
2223
2224QT_END_NAMESPACE
2225
2226