/**************************************************************************** | |
** | |
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). | |
** All rights reserved. | |
** Contact: Nokia Corporation (qt-info@nokia.com) | |
** | |
** This file is part of the QtGui module of the Qt Toolkit. | |
** | |
** $QT_BEGIN_LICENSE:LGPL$ | |
** GNU Lesser General Public License Usage | |
** This file may be used under the terms of the GNU Lesser General Public | |
** License version 2.1 as published by the Free Software Foundation and | |
** appearing in the file LICENSE.LGPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU Lesser | |
** General Public License version 2.1 requirements will be met: | |
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. | |
** | |
** In addition, as a special exception, Nokia gives you certain additional | |
** rights. These rights are described in the Nokia Qt LGPL Exception | |
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. | |
** | |
** GNU General Public License Usage | |
** Alternatively, this file may be used under the terms of the GNU General | |
** Public License version 3.0 as published by the Free Software Foundation | |
** and appearing in the file LICENSE.GPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU General | |
** Public License version 3.0 requirements will be met: | |
** http://www.gnu.org/copyleft/gpl.html. | |
** | |
** Other Usage | |
** Alternatively, this file may be used in accordance with the terms and | |
** conditions contained in a signed written agreement between you and Nokia. | |
** | |
** | |
** | |
** | |
** | |
** $QT_END_LICENSE$ | |
** | |
****************************************************************************/ | |
#include "qmenu.h" | |
#include "qhash.h" | |
#include <qdebug.h> | |
#include "qapplication.h" | |
#include <private/qt_mac_p.h> | |
#include "qregexp.h" | |
#include "qmainwindow.h" | |
#include "qdockwidget.h" | |
#include "qtoolbar.h" | |
#include "qevent.h" | |
#include "qstyle.h" | |
#include "qwidgetaction.h" | |
#include "qmacnativewidget_mac.h" | |
#include <private/qapplication_p.h> | |
#include <private/qcocoaapplication_mac_p.h> | |
#include <private/qmenu_p.h> | |
#include <private/qmenubar_p.h> | |
#include <private/qcocoamenuloader_mac_p.h> | |
#include <private/qcocoamenu_mac_p.h> | |
#include <private/qt_cocoa_helpers_mac_p.h> | |
#include <Cocoa/Cocoa.h> | |
QT_BEGIN_NAMESPACE | |
/***************************************************************************** | |
QMenu debug facilities | |
*****************************************************************************/ | |
/***************************************************************************** | |
QMenu globals | |
*****************************************************************************/ | |
bool qt_mac_no_menubar_merge = false; | |
bool qt_mac_quit_menu_item_enabled = true; | |
int qt_mac_menus_open_count = 0; | |
static OSMenuRef qt_mac_create_menu(QWidget *w); | |
#ifndef QT_MAC_USE_COCOA | |
static uint qt_mac_menu_static_cmd_id = 'QT00'; | |
const UInt32 kMenuCreatorQt = 'cute'; | |
enum { | |
kMenuPropertyQAction = 'QAcT', | |
kMenuPropertyQWidget = 'QWId', | |
kMenuPropertyCausedQWidget = 'QCAU', | |
kMenuPropertyMergeMenu = 'QApP', | |
kMenuPropertyMergeList = 'QAmL', | |
kMenuPropertyWidgetActionWidget = 'QWid', | |
kMenuPropertyWidgetMenu = 'QWMe', | |
kHICommandAboutQt = 'AOQT', | |
kHICommandCustomMerge = 'AQt0' | |
}; | |
#endif | |
static struct { | |
QPointer<QMenuBar> qmenubar; | |
bool modal; | |
} qt_mac_current_menubar = { 0, false }; | |
/***************************************************************************** | |
Externals | |
*****************************************************************************/ | |
extern OSViewRef qt_mac_hiview_for(const QWidget *w); //qwidget_mac.cpp | |
extern HIViewRef qt_mac_hiview_for(OSWindowRef w); //qwidget_mac.cpp | |
extern IconRef qt_mac_create_iconref(const QPixmap &px); //qpixmap_mac.cpp | |
extern QWidget * mac_keyboard_grabber; //qwidget_mac.cpp | |
extern bool qt_sendSpontaneousEvent(QObject*, QEvent*); //qapplication_xxx.cpp | |
RgnHandle qt_mac_get_rgn(); //qregion_mac.cpp | |
void qt_mac_dispose_rgn(RgnHandle r); //qregion_mac.cpp | |
/***************************************************************************** | |
QMenu utility functions | |
*****************************************************************************/ | |
bool qt_mac_watchingAboutToShow(QMenu *menu) | |
{ | |
return menu && menu->receivers(SIGNAL(aboutToShow())); | |
} | |
static int qt_mac_CountMenuItems(OSMenuRef menu) | |
{ | |
if (menu) { | |
#ifndef QT_MAC_USE_COCOA | |
int ret = 0; | |
const int items = CountMenuItems(menu); | |
for(int i = 0; i < items; i++) { | |
MenuItemAttributes attr; | |
if (GetMenuItemAttributes(menu, i+1, &attr) == noErr && | |
attr & kMenuItemAttrHidden) | |
continue; | |
++ret; | |
} | |
return ret; | |
#else | |
return [menu numberOfItems]; | |
#endif | |
} | |
return 0; | |
} | |
static quint32 constructModifierMask(quint32 accel_key) | |
{ | |
quint32 ret = 0; | |
const bool dontSwap = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta); | |
#ifndef QT_MAC_USE_COCOA | |
if ((accel_key & Qt::ALT) == Qt::ALT) | |
ret |= kMenuOptionModifier; | |
if ((accel_key & Qt::SHIFT) == Qt::SHIFT) | |
ret |= kMenuShiftModifier; | |
if (dontSwap) { | |
if ((accel_key & Qt::META) != Qt::META) | |
ret |= kMenuNoCommandModifier; | |
if ((accel_key & Qt::CTRL) == Qt::CTRL) | |
ret |= kMenuControlModifier; | |
} else { | |
if ((accel_key & Qt::CTRL) != Qt::CTRL) | |
ret |= kMenuNoCommandModifier; | |
if ((accel_key & Qt::META) == Qt::META) | |
ret |= kMenuControlModifier; | |
} | |
#else | |
if ((accel_key & Qt::CTRL) == Qt::CTRL) | |
ret |= (dontSwap ? NSControlKeyMask : NSCommandKeyMask); | |
if ((accel_key & Qt::META) == Qt::META) | |
ret |= (dontSwap ? NSCommandKeyMask : NSControlKeyMask); | |
if ((accel_key & Qt::ALT) == Qt::ALT) | |
ret |= NSAlternateKeyMask; | |
if ((accel_key & Qt::SHIFT) == Qt::SHIFT) | |
ret |= NSShiftKeyMask; | |
#endif | |
return ret; | |
} | |
static void cancelAllMenuTracking() | |
{ | |
#ifdef QT_MAC_USE_COCOA | |
QMacCocoaAutoReleasePool pool; | |
NSMenu *mainMenu = [NSApp mainMenu]; | |
[mainMenu cancelTracking]; | |
for (NSMenuItem *item in [mainMenu itemArray]) { | |
if ([item submenu]) { | |
[[item submenu] cancelTracking]; | |
} | |
} | |
#else | |
CancelMenuTracking(AcquireRootMenu(), true, 0); | |
#endif | |
} | |
static bool actualMenuItemVisibility(const QMenuBarPrivate::QMacMenuBarPrivate *mbp, | |
const QMacMenuAction *action) | |
{ | |
bool visible = action->action->isVisible(); | |
if (visible && action->action->text() == QString(QChar(0x14))) | |
return false; | |
if (visible && action->action->menu() && !action->action->menu()->actions().isEmpty() && | |
!qt_mac_CountMenuItems(action->action->menu()->macMenu(mbp->apple_menu)) && | |
!qt_mac_watchingAboutToShow(action->action->menu())) { | |
return false; | |
} | |
return visible; | |
} | |
#ifndef QT_MAC_USE_COCOA | |
bool qt_mac_activate_action(MenuRef menu, uint command, QAction::ActionEvent action_e, bool by_accel) | |
{ | |
//fire event | |
QMacMenuAction *action = 0; | |
if (GetMenuCommandProperty(menu, command, kMenuCreatorQt, kMenuPropertyQAction, sizeof(action), 0, &action) != noErr) { | |
QMenuMergeList *list = 0; | |
GetMenuItemProperty(menu, 0, kMenuCreatorQt, kMenuPropertyMergeList, | |
sizeof(list), 0, &list); | |
if (!list && qt_mac_current_menubar.qmenubar && qt_mac_current_menubar.qmenubar->isNativeMenuBar()) { | |
MenuRef apple_menu = qt_mac_current_menubar.qmenubar->d_func()->mac_menubar->apple_menu; | |
GetMenuItemProperty(apple_menu, 0, kMenuCreatorQt, kMenuPropertyMergeList, sizeof(list), 0, &list); | |
if (list) | |
menu = apple_menu; | |
} | |
if (list) { | |
for(int i = 0; i < list->size(); ++i) { | |
QMenuMergeItem item = list->at(i); | |
if (item.command == command && item.action) { | |
action = item.action; | |
break; | |
} | |
} | |
} | |
if (!action) | |
return false; | |
} | |
if (action_e == QAction::Trigger && by_accel && action->ignore_accel) //no, not a real accel (ie tab) | |
return false; | |
// Unhighlight the highlighted menu item before triggering the action to | |
// prevent items from staying highlighted while a modal dialog is shown. | |
// This also fixed the problem that parentless modal dialogs leave | |
// the menu item highlighted (since the menu bar is cleared for these types of dialogs). | |
if (action_e == QAction::Trigger) | |
HiliteMenu(0); | |
action->action->activate(action_e); | |
//now walk up firing for each "caused" widget (like in the platform independent menu) | |
QWidget *caused = 0; | |
if (action_e == QAction::Hover && GetMenuItemProperty(menu, 0, kMenuCreatorQt, kMenuPropertyCausedQWidget, sizeof(caused), 0, &caused) == noErr) { | |
MenuRef caused_menu = 0; | |
if (QMenu *qmenu2 = qobject_cast<QMenu*>(caused)) | |
caused_menu = qmenu2->macMenu(); | |
else if (QMenuBar *qmenubar2 = qobject_cast<QMenuBar*>(caused)) | |
caused_menu = qmenubar2->macMenu(); | |
else | |
caused_menu = 0; | |
while(caused_menu) { | |
//fire | |
QWidget *widget = 0; | |
GetMenuItemProperty(caused_menu, 0, kMenuCreatorQt, kMenuPropertyQWidget, sizeof(widget), 0, &widget); | |
if (QMenu *qmenu = qobject_cast<QMenu*>(widget)) { | |
action->action->showStatusText(widget); | |
emit qmenu->hovered(action->action); | |
} else if (QMenuBar *qmenubar = qobject_cast<QMenuBar*>(widget)) { | |
action->action->showStatusText(widget); | |
emit qmenubar->hovered(action->action); | |
break; //nothing more.. | |
} | |
//walk up | |
if (GetMenuItemProperty(caused_menu, 0, kMenuCreatorQt, kMenuPropertyCausedQWidget, | |
sizeof(caused), 0, &caused) != noErr) | |
break; | |
if (QMenu *qmenu2 = qobject_cast<QMenu*>(caused)) | |
caused_menu = qmenu2->macMenu(); | |
else if (QMenuBar *qmenubar2 = qobject_cast<QMenuBar*>(caused)) | |
caused_menu = qmenubar2->macMenu(); | |
else | |
caused_menu = 0; | |
} | |
} | |
return true; | |
} | |
//lookup a QMacMenuAction in a menu | |
static int qt_mac_menu_find_action(MenuRef menu, MenuCommand cmd) | |
{ | |
MenuItemIndex ret_idx; | |
MenuRef ret_menu; | |
if (GetIndMenuItemWithCommandID(menu, cmd, 1, &ret_menu, &ret_idx) == noErr) { | |
if (ret_menu == menu) | |
return (int)ret_idx; | |
} | |
return -1; | |
} | |
static int qt_mac_menu_find_action(MenuRef menu, QMacMenuAction *action) | |
{ | |
return qt_mac_menu_find_action(menu, action->command); | |
} | |
typedef QMultiHash<OSMenuRef, EventHandlerRef> EventHandlerHash; | |
Q_GLOBAL_STATIC(EventHandlerHash, menu_eventHandlers_hash) | |
static EventTypeSpec widget_in_menu_events[] = { | |
{ kEventClassMenu, kEventMenuMeasureItemWidth }, | |
{ kEventClassMenu, kEventMenuMeasureItemHeight }, | |
{ kEventClassMenu, kEventMenuDrawItem }, | |
{ kEventClassMenu, kEventMenuCalculateSize } | |
}; | |
static OSStatus qt_mac_widget_in_menu_eventHandler(EventHandlerCallRef er, EventRef event, void *) | |
{ | |
UInt32 ekind = GetEventKind(event); | |
UInt32 eclass = GetEventClass(event); | |
OSStatus result = eventNotHandledErr; | |
switch (eclass) { | |
case kEventClassMenu: | |
switch (ekind) { | |
default: | |
break; | |
case kEventMenuMeasureItemWidth: { | |
MenuItemIndex item; | |
GetEventParameter(event, kEventParamMenuItemIndex, typeMenuItemIndex, | |
0, sizeof(item), 0, &item); | |
OSMenuRef menu; | |
GetEventParameter(event, kEventParamDirectObject, typeMenuRef, 0, sizeof(menu), 0, &menu); | |
QWidget *widget; | |
if (GetMenuItemProperty(menu, item, kMenuCreatorQt, kMenuPropertyWidgetActionWidget, | |
sizeof(widget), 0, &widget) == noErr) { | |
short width = short(widget->sizeHint().width()); | |
SetEventParameter(event, kEventParamMenuItemWidth, typeSInt16, | |
sizeof(short), &width); | |
result = noErr; | |
} | |
break; } | |
case kEventMenuMeasureItemHeight: { | |
MenuItemIndex item; | |
GetEventParameter(event, kEventParamMenuItemIndex, typeMenuItemIndex, | |
0, sizeof(item), 0, &item); | |
OSMenuRef menu; | |
GetEventParameter(event, kEventParamDirectObject, typeMenuRef, 0, sizeof(menu), 0, &menu); | |
QWidget *widget; | |
if (GetMenuItemProperty(menu, item, kMenuCreatorQt, kMenuPropertyWidgetActionWidget, | |
sizeof(widget), 0, &widget) == noErr && widget) { | |
short height = short(widget->sizeHint().height()); | |
SetEventParameter(event, kEventParamMenuItemHeight, typeSInt16, | |
sizeof(short), &height); | |
result = noErr; | |
} | |
break; } | |
case kEventMenuDrawItem: | |
result = noErr; | |
break; | |
case kEventMenuCalculateSize: { | |
result = CallNextEventHandler(er, event); | |
if (result == noErr) { | |
OSMenuRef menu; | |
GetEventParameter(event, kEventParamDirectObject, typeMenuRef, 0, sizeof(menu), 0, &menu); | |
HIViewRef content; | |
HIMenuGetContentView(menu, kThemeMenuTypePullDown, &content); | |
UInt16 count = CountMenuItems(menu); | |
for (MenuItemIndex i = 1; i <= count; ++i) { | |
QWidget *widget; | |
if (GetMenuItemProperty(menu, i, kMenuCreatorQt, kMenuPropertyWidgetActionWidget, | |
sizeof(widget), 0, &widget) == noErr && widget) { | |
RgnHandle itemRgn = qt_mac_get_rgn(); | |
GetControlRegion(content, i, itemRgn); | |
Rect bounds; | |
GetRegionBounds( itemRgn, &bounds ); | |
qt_mac_dispose_rgn(itemRgn); | |
widget->setGeometry(bounds.left, bounds.top, | |
bounds.right - bounds.left, bounds.bottom - bounds.top); | |
} | |
} | |
} | |
break; } | |
} | |
} | |
return result; | |
} | |
//handling of events for menurefs created by Qt.. | |
static EventTypeSpec menu_events[] = { | |
{ kEventClassCommand, kEventCommandProcess }, | |
{ kEventClassMenu, kEventMenuTargetItem }, | |
{ kEventClassMenu, kEventMenuOpening }, | |
{ kEventClassMenu, kEventMenuClosed } | |
}; | |
// Special case for kEventMenuMatchKey, see qt_mac_create_menu below. | |
static EventTypeSpec menu_menu_events[] = { | |
{ kEventClassMenu, kEventMenuMatchKey } | |
}; | |
OSStatus qt_mac_menu_event(EventHandlerCallRef er, EventRef event, void *) | |
{ | |
QScopedLoopLevelCounter loopLevelCounter(QApplicationPrivate::instance()->threadData); | |
bool handled_event = true; | |
UInt32 ekind = GetEventKind(event), eclass = GetEventClass(event); | |
switch(eclass) { | |
case kEventClassCommand: | |
if (ekind == kEventCommandProcess) { | |
UInt32 context; | |
GetEventParameter(event, kEventParamMenuContext, typeUInt32, | |
0, sizeof(context), 0, &context); | |
HICommand cmd; | |
GetEventParameter(event, kEventParamDirectObject, typeHICommand, | |
0, sizeof(cmd), 0, &cmd); | |
if (!mac_keyboard_grabber && (context & kMenuContextKeyMatching)) { | |
QMacMenuAction *action = 0; | |
if (GetMenuCommandProperty(cmd.menu.menuRef, cmd.commandID, kMenuCreatorQt, | |
kMenuPropertyQAction, sizeof(action), 0, &action) == noErr) { | |
QWidget *widget = 0; | |
if (qApp->activePopupWidget()) | |
widget = (qApp->activePopupWidget()->focusWidget() ? | |
qApp->activePopupWidget()->focusWidget() : qApp->activePopupWidget()); | |
else if (QApplicationPrivate::focus_widget) | |
widget = QApplicationPrivate::focus_widget; | |
if (widget) { | |
int key = action->action->shortcut(); | |
QKeyEvent accel_ev(QEvent::ShortcutOverride, (key & (~Qt::KeyboardModifierMask)), | |
Qt::KeyboardModifiers(key & Qt::KeyboardModifierMask)); | |
accel_ev.ignore(); | |
qt_sendSpontaneousEvent(widget, &accel_ev); | |
if (accel_ev.isAccepted()) { | |
handled_event = false; | |
break; | |
} | |
} | |
} | |
} | |
handled_event = qt_mac_activate_action(cmd.menu.menuRef, cmd.commandID, | |
QAction::Trigger, context & kMenuContextKeyMatching); | |
} | |
break; | |
case kEventClassMenu: { | |
MenuRef menu; | |
GetEventParameter(event, kEventParamDirectObject, typeMenuRef, NULL, sizeof(menu), NULL, &menu); | |
if (ekind == kEventMenuMatchKey) { | |
// Don't activate any actions if we are showing a native modal dialog, | |
// the key events should go to the dialog in this case. | |
if (QApplicationPrivate::native_modal_dialog_active) | |
return menuItemNotFoundErr; | |
handled_event = false; | |
} else if (ekind == kEventMenuTargetItem) { | |
MenuCommand command; | |
GetEventParameter(event, kEventParamMenuCommand, typeMenuCommand, | |
0, sizeof(command), 0, &command); | |
handled_event = qt_mac_activate_action(menu, command, QAction::Hover, false); | |
} else if (ekind == kEventMenuOpening || ekind == kEventMenuClosed) { | |
qt_mac_menus_open_count += (ekind == kEventMenuOpening) ? 1 : -1; | |
MenuRef mr; | |
GetEventParameter(event, kEventParamDirectObject, typeMenuRef, | |
0, sizeof(mr), 0, &mr); | |
QWidget *widget = 0; | |
if (GetMenuItemProperty(mr, 0, kMenuCreatorQt, kMenuPropertyQWidget, sizeof(widget), 0, &widget) == noErr) { | |
if (QMenu *qmenu = qobject_cast<QMenu*>(widget)) { | |
handled_event = true; | |
if (ekind == kEventMenuOpening) { | |
emit qmenu->aboutToShow(); | |
int merged = 0; | |
const QMenuPrivate::QMacMenuPrivate *mac_menu = qmenu->d_func()->mac_menu; | |
const int ActionItemsCount = mac_menu->actionItems.size(); | |
for(int i = 0; i < ActionItemsCount; ++i) { | |
QMacMenuAction *action = mac_menu->actionItems.at(i); | |
if (action->action->isSeparator()) { | |
bool hide = false; | |
if(!action->action->isVisible()) { | |
hide = true; | |
} else if (merged && merged == i) { | |
hide = true; | |
} else { | |
for(int l = i+1; l < mac_menu->actionItems.size(); ++l) { | |
QMacMenuAction *action = mac_menu->actionItems.at(l); | |
if (action->merged) { | |
hide = true; | |
} else if (action->action->isSeparator()) { | |
if (hide) | |
break; | |
} else if (!action->merged) { | |
hide = false; | |
break; | |
} | |
} | |
} | |
const int index = qt_mac_menu_find_action(mr, action); | |
if (hide) { | |
++merged; | |
ChangeMenuItemAttributes(mr, index, kMenuItemAttrHidden, 0); | |
} else { | |
ChangeMenuItemAttributes(mr, index, 0, kMenuItemAttrHidden); | |
} | |
} else if (action->merged) { | |
++merged; | |
} | |
} | |
} else { | |
emit qmenu->aboutToHide(); | |
} | |
} | |
} | |
} else { | |
handled_event = false; | |
} | |
break; } | |
default: | |
handled_event = false; | |
break; | |
} | |
if (!handled_event) //let the event go through | |
return CallNextEventHandler(er, event); | |
return noErr; //we eat the event | |
} | |
static EventHandlerRef mac_menu_event_handler = 0; | |
static EventHandlerUPP mac_menu_eventUPP = 0; | |
static void qt_mac_cleanup_menu_event() | |
{ | |
if (mac_menu_event_handler) { | |
RemoveEventHandler(mac_menu_event_handler); | |
mac_menu_event_handler = 0; | |
} | |
if (mac_menu_eventUPP) { | |
DisposeEventHandlerUPP(mac_menu_eventUPP); | |
mac_menu_eventUPP = 0; | |
} | |
} | |
static inline void qt_mac_create_menu_event_handler() | |
{ | |
if (!mac_menu_event_handler) { | |
mac_menu_eventUPP = NewEventHandlerUPP(qt_mac_menu_event); | |
InstallEventHandler(GetApplicationEventTarget(), mac_menu_eventUPP, | |
GetEventTypeCount(menu_events), menu_events, 0, | |
&mac_menu_event_handler); | |
qAddPostRoutine(qt_mac_cleanup_menu_event); | |
} | |
} | |
//enabling of commands | |
static void qt_mac_command_set_enabled(MenuRef menu, UInt32 cmd, bool b) | |
{ | |
if (cmd == kHICommandQuit) | |
qt_mac_quit_menu_item_enabled = b; | |
if (b) { | |
EnableMenuCommand(menu, cmd); | |
if (MenuRef dock_menu = GetApplicationDockTileMenu()) | |
EnableMenuCommand(dock_menu, cmd); | |
} else { | |
DisableMenuCommand(menu, cmd); | |
if (MenuRef dock_menu = GetApplicationDockTileMenu()) | |
DisableMenuCommand(dock_menu, cmd); | |
} | |
} | |
static bool qt_mac_auto_apple_menu(MenuCommand cmd) | |
{ | |
return (cmd == kHICommandPreferences || cmd == kHICommandQuit); | |
} | |
static void qt_mac_get_accel(quint32 accel_key, quint32 *modif, quint32 *key) { | |
if (modif) { | |
*modif = constructModifierMask(accel_key); | |
} | |
accel_key &= ~(Qt::MODIFIER_MASK | Qt::UNICODE_ACCEL); | |
if (key) { | |
*key = 0; | |
if (accel_key == Qt::Key_Return) | |
*key = kMenuReturnGlyph; | |
else if (accel_key == Qt::Key_Enter) | |
*key = kMenuEnterGlyph; | |
else if (accel_key == Qt::Key_Tab) | |
*key = kMenuTabRightGlyph; | |
else if (accel_key == Qt::Key_Backspace) | |
*key = kMenuDeleteLeftGlyph; | |
else if (accel_key == Qt::Key_Delete) | |
*key = kMenuDeleteRightGlyph; | |
else if (accel_key == Qt::Key_Escape) | |
*key = kMenuEscapeGlyph; | |
else if (accel_key == Qt::Key_PageUp) | |
*key = kMenuPageUpGlyph; | |
else if (accel_key == Qt::Key_PageDown) | |
*key = kMenuPageDownGlyph; | |
else if (accel_key == Qt::Key_Up) | |
*key = kMenuUpArrowGlyph; | |
else if (accel_key == Qt::Key_Down) | |
*key = kMenuDownArrowGlyph; | |
else if (accel_key == Qt::Key_Left) | |
*key = kMenuLeftArrowGlyph; | |
else if (accel_key == Qt::Key_Right) | |
*key = kMenuRightArrowGlyph; | |
else if (accel_key == Qt::Key_CapsLock) | |
*key = kMenuCapsLockGlyph; | |
else if (accel_key >= Qt::Key_F1 && accel_key <= Qt::Key_F15) | |
*key = (accel_key - Qt::Key_F1) + kMenuF1Glyph; | |
else if (accel_key == Qt::Key_Home) | |
*key = kMenuNorthwestArrowGlyph; | |
else if (accel_key == Qt::Key_End) | |
*key = kMenuSoutheastArrowGlyph; | |
} | |
} | |
#else // Cocoa | |
static inline void syncNSMenuItemVisiblity(NSMenuItem *menuItem, bool actionVisibility) | |
{ | |
[menuItem setHidden:NO]; | |
[menuItem setHidden:YES]; | |
[menuItem setHidden:!actionVisibility]; | |
} | |
static inline void syncNSMenuItemEnabled(NSMenuItem *menuItem, bool enabled) | |
{ | |
[menuItem setEnabled:NO]; | |
[menuItem setEnabled:YES]; | |
[menuItem setEnabled:enabled]; | |
} | |
static inline void syncMenuBarItemsVisiblity(const QMenuBarPrivate::QMacMenuBarPrivate *mac_menubar) | |
{ | |
const QList<QMacMenuAction *> &menubarActions = mac_menubar->actionItems; | |
for (int i = 0; i < menubarActions.size(); ++i) { | |
const QMacMenuAction *action = menubarActions.at(i); | |
syncNSMenuItemVisiblity(action->menuItem, actualMenuItemVisibility(mac_menubar, action)); | |
} | |
} | |
static inline QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *getMenuLoader() | |
{ | |
return [NSApp QT_MANGLE_NAMESPACE(qt_qcocoamenuLoader)]; | |
} | |
static NSMenuItem *createNSMenuItem(const QString &title) | |
{ | |
NSMenuItem *item = [[NSMenuItem alloc] | |
initWithTitle:qt_mac_QStringToNSString(title) | |
action:@selector(qtDispatcherToQAction:) keyEquivalent:@""]; | |
[item setTarget:nil]; | |
return item; | |
} | |
#endif | |
// helper that recurses into a menu structure and en/dis-ables them | |
void qt_mac_set_modal_state_helper_recursive(OSMenuRef menu, OSMenuRef merge, bool on) | |
{ | |
#ifndef QT_MAC_USE_COCOA | |
for (int i = 0; i < CountMenuItems(menu); i++) { | |
OSMenuRef submenu; | |
GetMenuItemHierarchicalMenu(menu, i+1, &submenu); | |
if (submenu != merge) { | |
if (submenu) | |
qt_mac_set_modal_state_helper_recursive(submenu, merge, on); | |
if (on) | |
DisableMenuItem(submenu, 0); | |
else | |
EnableMenuItem(submenu, 0); | |
} | |
} | |
#else | |
bool modalWindowOnScreen = qApp->activeModalWidget() != 0; | |
for (NSMenuItem *item in [menu itemArray]) { | |
OSMenuRef submenu = [item submenu]; | |
if (submenu != merge) { | |
if (submenu) | |
qt_mac_set_modal_state_helper_recursive(submenu, merge, on); | |
if (!on) { | |
// The item should follow what the QAction has. | |
if ([item tag]) { | |
QAction *action = reinterpret_cast<QAction *>([item tag]); | |
syncNSMenuItemEnabled(item, action->isEnabled()); | |
} else { | |
syncNSMenuItemEnabled(item, YES); | |
} | |
// We sneak in some extra code here to handle a menu problem: | |
// If there is no window on screen, we cannot set 'nil' as | |
// menu item target, because then cocoa will disable the item | |
// (guess it assumes that there will be no first responder to | |
// catch the trigger anyway?) OTOH, If we have a modal window, | |
// then setting the menu loader as target will make cocoa not | |
// deliver the trigger because the loader is then seen as modally | |
// shaddowed). So either way there are shortcomings. Instead, we | |
// decide the target as late as possible: | |
[item setTarget:modalWindowOnScreen ? nil : getMenuLoader()]; | |
} else { | |
syncNSMenuItemEnabled(item, NO); | |
} | |
} | |
} | |
#endif | |
} | |
//toggling of modal state | |
static void qt_mac_set_modal_state(OSMenuRef menu, bool on) | |
{ | |
#ifndef QT_MAC_USE_COCOA | |
OSMenuRef merge = 0; | |
GetMenuItemProperty(menu, 0, kMenuCreatorQt, kMenuPropertyMergeMenu, | |
sizeof(merge), 0, &merge); | |
qt_mac_set_modal_state_helper_recursive(menu, merge, on); | |
UInt32 commands[] = { kHICommandQuit, kHICommandPreferences, kHICommandAbout, kHICommandAboutQt, 0 }; | |
for(int c = 0; commands[c]; c++) { | |
bool enabled = !on; | |
if (enabled) { | |
QMacMenuAction *action = 0; | |
GetMenuCommandProperty(menu, commands[c], kMenuCreatorQt, kMenuPropertyQAction, | |
sizeof(action), 0, &action); | |
if (!action && merge) { | |
GetMenuCommandProperty(merge, commands[c], kMenuCreatorQt, kMenuPropertyQAction, | |
sizeof(action), 0, &action); | |
if (!action) { | |
QMenuMergeList *list = 0; | |
GetMenuItemProperty(merge, 0, kMenuCreatorQt, kMenuPropertyMergeList, | |
sizeof(list), 0, &list); | |
for(int i = 0; list && i < list->size(); ++i) { | |
QMenuMergeItem item = list->at(i); | |
if (item.command == commands[c] && item.action) { | |
action = item.action; | |
break; | |
} | |
} | |
} | |
} | |
if (!action) { | |
if (commands[c] != kHICommandQuit) | |
enabled = false; | |
} else { | |
enabled = action->action ? action->action->isEnabled() : 0; | |
} | |
} | |
qt_mac_command_set_enabled(menu, commands[c], enabled); | |
} | |
#else | |
OSMenuRef merge = QMenuPrivate::mergeMenuHash.value(menu); | |
qt_mac_set_modal_state_helper_recursive(menu, merge, on); | |
// I'm ignoring the special items now, since they should get handled via a syncAction() | |
#endif | |
} | |
bool qt_mac_menubar_is_open() | |
{ | |
return qt_mac_menus_open_count > 0; | |
} | |
QMacMenuAction::~QMacMenuAction() | |
{ | |
#ifdef QT_MAC_USE_COCOA | |
[menu release]; | |
// Update the menu item if this action still owns it. For some items | |
// (like 'Quit') ownership will be transferred between all menu bars... | |
if (action && action.data() == reinterpret_cast<QAction *>([menuItem tag])) { | |
QAction::MenuRole role = action->menuRole(); | |
// Check if the item is owned by Qt, and should be hidden to keep it from causing | |
// problems. Do it for everything but the quit menu item since that should always | |
// be visible. | |
if (role > QAction::ApplicationSpecificRole && role < QAction::QuitRole) { | |
[menuItem setHidden:YES]; | |
} else if (role == QAction::TextHeuristicRole | |
&& menuItem != [getMenuLoader() quitMenuItem]) { | |
[menuItem setHidden:YES]; | |
} | |
[menuItem setTag:nil]; | |
} | |
[menuItem release]; | |
#endif | |
} | |
#ifndef QT_MAC_USE_COCOA | |
static MenuCommand qt_mac_menu_merge_action(MenuRef merge, QMacMenuAction *action) | |
#else | |
static NSMenuItem *qt_mac_menu_merge_action(OSMenuRef merge, QMacMenuAction *action) | |
#endif | |
{ | |
if (qt_mac_no_menubar_merge || action->action->menu() || action->action->isSeparator() | |
|| action->action->menuRole() == QAction::NoRole) | |
return 0; | |
QString t = qt_mac_removeMnemonics(action->action->text().toLower()); | |
int st = t.lastIndexOf(QLatin1Char('\t')); | |
if (st != -1) | |
t.remove(st, t.length()-st); | |
t.replace(QRegExp(QString::fromLatin1("\\.*$")), QLatin1String("")); //no ellipses | |
//now the fun part | |
#ifndef QT_MAC_USE_COCOA | |
MenuCommand ret = 0; | |
#else | |
NSMenuItem *ret = 0; | |
QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader(); | |
#endif | |
switch (action->action->menuRole()) { | |
case QAction::NoRole: | |
ret = 0; | |
break; | |
case QAction::ApplicationSpecificRole: | |
#ifndef QT_MAC_USE_COCOA | |
{ | |
QMenuMergeList *list = 0; | |
if (GetMenuItemProperty(merge, 0, kMenuCreatorQt, kMenuPropertyMergeList, | |
sizeof(list), 0, &list) == noErr && list) { | |
MenuCommand lastCustom = kHICommandCustomMerge; | |
for(int i = 0; i < list->size(); ++i) { | |
QMenuMergeItem item = list->at(i); | |
if (item.command == lastCustom) | |
++lastCustom; | |
} | |
ret = lastCustom; | |
} else { | |
// The list hasn't been created, so, must be the first one. | |
ret = kHICommandCustomMerge; | |
} | |
} | |
#else | |
ret = [loader appSpecificMenuItem]; | |
#endif | |
break; | |
case QAction::AboutRole: | |
#ifndef QT_MAC_USE_COCOA | |
ret = kHICommandAbout; | |
#else | |
ret = [loader aboutMenuItem]; | |
#endif | |
break; | |
case QAction::AboutQtRole: | |
#ifndef QT_MAC_USE_COCOA | |
ret = kHICommandAboutQt; | |
#else | |
ret = [loader aboutQtMenuItem]; | |
#endif | |
break; | |
case QAction::QuitRole: | |
#ifndef QT_MAC_USE_COCOA | |
ret = kHICommandQuit; | |
#else | |
ret = [loader quitMenuItem]; | |
#endif | |
break; | |
case QAction::PreferencesRole: | |
#ifndef QT_MAC_USE_COCOA | |
ret = kHICommandPreferences; | |
#else | |
ret = [loader preferencesMenuItem]; | |
#endif | |
break; | |
case QAction::TextHeuristicRole: { | |
QString aboutString = QMenuBar::tr("About").toLower(); | |
if (t.startsWith(aboutString) || t.endsWith(aboutString)) { | |
if (t.indexOf(QRegExp(QString::fromLatin1("qt$"), Qt::CaseInsensitive)) == -1) { | |
#ifndef QT_MAC_USE_COCOA | |
ret = kHICommandAbout; | |
#else | |
ret = [loader aboutMenuItem]; | |
#endif | |
} else { | |
#ifndef QT_MAC_USE_COCOA | |
ret = kHICommandAboutQt; | |
#else | |
ret = [loader aboutQtMenuItem]; | |
#endif | |
} | |
} else if (t.startsWith(QMenuBar::tr("Config").toLower()) | |
|| t.startsWith(QMenuBar::tr("Preference").toLower()) | |
|| t.startsWith(QMenuBar::tr("Options").toLower()) | |
|| t.startsWith(QMenuBar::tr("Setting").toLower()) | |
|| t.startsWith(QMenuBar::tr("Setup").toLower())) { | |
#ifndef QT_MAC_USE_COCOA | |
ret = kHICommandPreferences; | |
#else | |
ret = [loader preferencesMenuItem]; | |
#endif | |
} else if (t.startsWith(QMenuBar::tr("Quit").toLower()) | |
|| t.startsWith(QMenuBar::tr("Exit").toLower())) { | |
#ifndef QT_MAC_USE_COCOA | |
ret = kHICommandQuit; | |
#else | |
ret = [loader quitMenuItem]; | |
#endif | |
} | |
} | |
break; | |
} | |
#ifndef QT_MAC_USE_COCOA | |
QMenuMergeList *list = 0; | |
if (GetMenuItemProperty(merge, 0, kMenuCreatorQt, kMenuPropertyMergeList, | |
sizeof(list), 0, &list) == noErr && list) { | |
for(int i = 0; i < list->size(); ++i) { | |
QMenuMergeItem item = list->at(i); | |
if (item.command == ret && item.action) | |
return 0; | |
} | |
} | |
QAction *cmd_action = 0; | |
if (GetMenuCommandProperty(merge, ret, kMenuCreatorQt, kMenuPropertyQAction, | |
sizeof(cmd_action), 0, &cmd_action) == noErr && cmd_action) | |
return 0; //already taken | |
#else | |
if (QMenuMergeList *list = QMenuPrivate::mergeMenuItemsHash.value(merge)) { | |
for(int i = 0; i < list->size(); ++i) { | |
const QMenuMergeItem &item = list->at(i); | |
if (item.menuItem == ret && item.action) | |
return 0; | |
} | |
} | |
#endif | |
return ret; | |
} | |
static QString qt_mac_menu_merge_text(QMacMenuAction *action) | |
{ | |
QString ret; | |
extern QString qt_mac_applicationmenu_string(int type); | |
#ifdef QT_MAC_USE_COCOA | |
QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader(); | |
#endif | |
if (action->action->menuRole() == QAction::ApplicationSpecificRole) | |
ret = action->action->text(); | |
#ifndef QT_MAC_USE_COCOA | |
else if (action->command == kHICommandAbout) | |
ret = qt_mac_applicationmenu_string(6).arg(qAppName()); | |
else if (action->command == kHICommandAboutQt) | |
ret = QMenuBar::tr("About Qt"); | |
else if (action->command == kHICommandPreferences) | |
ret = qt_mac_applicationmenu_string(4); | |
else if (action->command == kHICommandQuit) | |
ret = qt_mac_applicationmenu_string(5).arg(qAppName()); | |
#else | |
else if (action->menuItem == [loader aboutMenuItem]) { | |
ret = qt_mac_applicationmenu_string(6).arg(qAppName()); | |
} else if (action->menuItem == [loader aboutQtMenuItem]) { | |
if (action->action->text() == QString("About Qt")) | |
ret = QMenuBar::tr("About Qt"); | |
else | |
ret = action->action->text(); | |
} else if (action->menuItem == [loader preferencesMenuItem]) { | |
ret = qt_mac_applicationmenu_string(4); | |
} else if (action->menuItem == [loader quitMenuItem]) { | |
ret = qt_mac_applicationmenu_string(5).arg(qAppName()); | |
} | |
#endif | |
return ret; | |
} | |
static QKeySequence qt_mac_menu_merge_accel(QMacMenuAction *action) | |
{ | |
QKeySequence ret; | |
#ifdef QT_MAC_USE_COCOA | |
QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader(); | |
#endif | |
if (action->action->menuRole() == QAction::ApplicationSpecificRole) | |
ret = action->action->shortcut(); | |
#ifndef QT_MAC_USE_COCOA | |
else if (action->command == kHICommandPreferences) | |
ret = QKeySequence(QKeySequence::Preferences); | |
else if (action->command == kHICommandQuit) | |
ret = QKeySequence(QKeySequence::Quit); | |
#else | |
else if (action->menuItem == [loader preferencesMenuItem]) | |
ret = QKeySequence(QKeySequence::Preferences); | |
else if (action->menuItem == [loader quitMenuItem]) | |
ret = QKeySequence(QKeySequence::Quit); | |
#endif | |
return ret; | |
} | |
void Q_GUI_EXPORT qt_mac_set_menubar_icons(bool b) | |
{ QApplication::instance()->setAttribute(Qt::AA_DontShowIconsInMenus, !b); } | |
void Q_GUI_EXPORT qt_mac_set_native_menubar(bool b) | |
{ QApplication::instance()->setAttribute(Qt::AA_DontUseNativeMenuBar, !b); } | |
void Q_GUI_EXPORT qt_mac_set_menubar_merge(bool b) { qt_mac_no_menubar_merge = !b; } | |
/***************************************************************************** | |
QMenu bindings | |
*****************************************************************************/ | |
QMenuPrivate::QMacMenuPrivate::QMacMenuPrivate() : menu(0) | |
{ | |
} | |
QMenuPrivate::QMacMenuPrivate::~QMacMenuPrivate() | |
{ | |
#ifndef QT_MAC_USE_COCOA | |
for(QList<QMacMenuAction*>::Iterator it = actionItems.begin(); it != actionItems.end(); ++it) { | |
QMacMenuAction *action = (*it); | |
RemoveMenuCommandProperty(action->menu, action->command, kMenuCreatorQt, kMenuPropertyQAction); | |
if (action->merged) { | |
QMenuMergeList *list = 0; | |
GetMenuItemProperty(action->menu, 0, kMenuCreatorQt, kMenuPropertyMergeList, | |
sizeof(list), 0, &list); | |
for(int i = 0; list && i < list->size(); ) { | |
QMenuMergeItem item = list->at(i); | |
if (item.action == action) | |
list->removeAt(i); | |
else | |
++i; | |
} | |
} | |
delete action; | |
} | |
if (menu) { | |
EventHandlerHash::iterator it = menu_eventHandlers_hash()->find(menu); | |
while (it != menu_eventHandlers_hash()->end() && it.key() == menu) { | |
RemoveEventHandler(it.value()); | |
++it; | |
} | |
menu_eventHandlers_hash()->remove(menu); | |
ReleaseMenu(menu); | |
} | |
#else | |
QMacCocoaAutoReleasePool pool; | |
while (actionItems.size()) { | |
QMacMenuAction *action = actionItems.takeFirst(); | |
if (QMenuMergeList *list = mergeMenuItemsHash.value(action->menu)) { | |
int i = 0; | |
while (i < list->size()) { | |
const QMenuMergeItem &item = list->at(i); | |
if (item.action == action) | |
list->removeAt(i); | |
else | |
++i; | |
} | |
} | |
delete action; | |
} | |
mergeMenuHash.remove(menu); | |
mergeMenuItemsHash.remove(menu); | |
[menu release]; | |
#endif | |
} | |
void | |
QMenuPrivate::QMacMenuPrivate::addAction(QAction *a, QMacMenuAction *before, QMenuPrivate *qmenu) | |
{ | |
QMacMenuAction *action = new QMacMenuAction; | |
action->action = a; | |
action->ignore_accel = 0; | |
action->merged = 0; | |
action->menu = 0; | |
#ifndef QT_MAC_USE_COCOA | |
action->command = qt_mac_menu_static_cmd_id++; | |
#endif | |
addAction(action, before, qmenu); | |
} | |
void | |
QMenuPrivate::QMacMenuPrivate::addAction(QMacMenuAction *action, QMacMenuAction *before, QMenuPrivate *qmenu) | |
{ | |
#ifdef QT_MAC_USE_COCOA | |
QMacCocoaAutoReleasePool pool; | |
Q_UNUSED(qmenu); | |
#endif | |
if (!action) | |
return; | |
int before_index = actionItems.indexOf(before); | |
if (before_index < 0) { | |
before = 0; | |
before_index = actionItems.size(); | |
} | |
actionItems.insert(before_index, action); | |
#ifndef QT_MAC_USE_COCOA | |
int index = qt_mac_menu_find_action(menu, action); | |
#else | |
[menu retain]; | |
[action->menu release]; | |
#endif | |
action->menu = menu; | |
/* When the action is considered a mergable action it | |
will stay that way, until removed.. */ | |
if (!qt_mac_no_menubar_merge) { | |
#ifndef QT_MAC_USE_COCOA | |
MenuRef merge = 0; | |
GetMenuItemProperty(menu, 0, kMenuCreatorQt, kMenuPropertyMergeMenu, | |
sizeof(merge), 0, &merge); | |
#else | |
OSMenuRef merge = QMenuPrivate::mergeMenuHash.value(menu); | |
#endif | |
if (merge) { | |
#ifndef QT_MAC_USE_COCOA | |
if (MenuCommand cmd = qt_mac_menu_merge_action(merge, action)) { | |
action->merged = 1; | |
action->menu = merge; | |
action->command = cmd; | |
if (qt_mac_auto_apple_menu(cmd)) | |
index = 0; //no need | |
QMenuMergeList *list = 0; | |
if (GetMenuItemProperty(merge, 0, kMenuCreatorQt, kMenuPropertyMergeList, | |
sizeof(list), 0, &list) != noErr || !list) { | |
list = new QMenuMergeList; | |
SetMenuItemProperty(merge, 0, kMenuCreatorQt, kMenuPropertyMergeList, | |
sizeof(list), &list); | |
} | |
list->append(QMenuMergeItem(cmd, action)); | |
} | |
#else | |
if (NSMenuItem *cmd = qt_mac_menu_merge_action(merge, action)) { | |
action->merged = 1; | |
[merge retain]; | |
[action->menu release]; | |
action->menu = merge; | |
[cmd retain]; | |
[cmd setAction:@selector(qtDispatcherToQAction:)]; | |
[cmd setTarget:nil]; | |
[action->menuItem release]; | |
action->menuItem = cmd; | |
QMenuMergeList *list = QMenuPrivate::mergeMenuItemsHash.value(merge); | |
if (!list) { | |
list = new QMenuMergeList; | |
QMenuPrivate::mergeMenuItemsHash.insert(merge, list); | |
} | |
list->append(QMenuMergeItem(cmd, action)); | |
} | |
#endif | |
} | |
} | |
#ifdef QT_MAC_USE_COCOA | |
NSMenuItem *newItem = action->menuItem; | |
#endif | |
if ( | |
#ifndef QT_MAC_USE_COCOA | |
index == -1 | |
#else | |
newItem == 0 | |
#endif | |
) { | |
#ifndef QT_MAC_USE_COCOA | |
index = before_index; | |
MenuItemAttributes attr = kMenuItemAttrAutoRepeat; | |
#else | |
newItem = createNSMenuItem(action->action->text()); | |
action->menuItem = newItem; | |
#endif | |
if (before) { | |
#ifndef QT_MAC_USE_COCOA | |
InsertMenuItemTextWithCFString(action->menu, 0, qMax(before_index, 0), attr, action->command); | |
#else | |
[menu insertItem:newItem atIndex:qMax(before_index, 0)]; | |
#endif | |
} else { | |
#ifndef QT_MAC_USE_COCOA | |
// Append the menu item to the menu. If it is a kHICommandAbout or a kHICommandAboutQt append | |
// a separator also (to get a separator above "Preferences"), but make sure that we don't | |
// add separators between two "about" items. | |
// Build a set of all commands that could possibly be before the separator. | |
QSet<MenuCommand> mergedItems; | |
mergedItems.insert(kHICommandAbout); | |
mergedItems.insert(kHICommandAboutQt); | |
mergedItems.insert(kHICommandCustomMerge); | |
QMenuMergeList *list = 0; | |
if (GetMenuItemProperty(action->menu, 0, kMenuCreatorQt, kMenuPropertyMergeList, | |
sizeof(list), 0, &list) == noErr && list) { | |
for (int i = 0; i < list->size(); ++i) { | |
MenuCommand command = list->at(i).command; | |
if (command > kHICommandCustomMerge) { | |
mergedItems.insert(command); | |
} | |
} | |
} | |
const int itemCount = CountMenuItems(action->menu); | |
MenuItemAttributes testattr; | |
GetMenuItemAttributes(action->menu, itemCount , &testattr); | |
if (mergedItems.contains(action->command) | |
&& (testattr & kMenuItemAttrSeparator)) { | |
InsertMenuItemTextWithCFString(action->menu, 0, qMax(itemCount - 1, 0), attr, action->command); | |
index = itemCount; | |
} else { | |
MenuItemIndex tmpIndex; | |
AppendMenuItemTextWithCFString(action->menu, 0, attr, action->command, &tmpIndex); | |
index = tmpIndex; | |
if (mergedItems.contains(action->command)) | |
AppendMenuItemTextWithCFString(action->menu, 0, kMenuItemAttrSeparator, 0, &tmpIndex); | |
} | |
#else | |
[menu addItem:newItem]; | |
#endif | |
} | |
QWidget *widget = qmenu ? qmenu->widgetItems.value(action->action) : 0; | |
if (widget) { | |
#ifndef QT_MAC_USE_COCOA | |
ChangeMenuAttributes(action->menu, kMenuAttrDoNotCacheImage, 0); | |
attr = kMenuItemAttrCustomDraw; | |
SetMenuItemProperty(action->menu, index, kMenuCreatorQt, kMenuPropertyWidgetActionWidget, | |
sizeof(QWidget *), &widget); | |
HIViewRef content; | |
HIMenuGetContentView(action->menu, kThemeMenuTypePullDown, &content); | |
EventHandlerRef eventHandlerRef; | |
InstallMenuEventHandler(action->menu, qt_mac_widget_in_menu_eventHandler, | |
GetEventTypeCount(widget_in_menu_events), | |
widget_in_menu_events, 0, &eventHandlerRef); | |
menu_eventHandlers_hash()->insert(action->menu, eventHandlerRef); | |
QWidget *menuWidget = 0; | |
GetMenuItemProperty(action->menu, 0, kMenuCreatorQt, kMenuPropertyWidgetMenu, | |
sizeof(menuWidget), 0, &menuWidget); | |
if(!menuWidget) { | |
menuWidget = new QMacNativeWidget(content); | |
SetMenuItemProperty(menu, 0, kMenuCreatorQt, kMenuPropertyWidgetMenu, | |
sizeof(menuWidget), &menuWidget); | |
menuWidget->show(); | |
} | |
widget->setParent(menuWidget); | |
#else | |
QMacNativeWidget *container = new QMacNativeWidget(0); | |
container->resize(widget->sizeHint()); | |
widget->setAttribute(Qt::WA_LayoutUsesWidgetRect); | |
widget->setParent(container); | |
NSView *containerView = qt_mac_nativeview_for(container); | |
[containerView setAutoresizesSubviews:YES]; | |
[containerView setAutoresizingMask:NSViewWidthSizable]; | |
[qt_mac_nativeview_for(widget) setAutoresizingMask:NSViewWidthSizable]; | |
[newItem setView:containerView]; | |
container->show(); | |
#endif | |
widget->show(); | |
} | |
} else { | |
#ifndef QT_MAC_USE_COCOA | |
qt_mac_command_set_enabled(action->menu, action->command, !QApplicationPrivate::modalState()); | |
#else | |
[newItem setEnabled:!QApplicationPrivate::modalState()]; | |
#endif | |
} | |
#ifndef QT_MAC_USE_COCOA | |
SetMenuCommandProperty(action->menu, action->command, kMenuCreatorQt, kMenuPropertyQAction, | |
sizeof(action), &action); | |
#else | |
[newItem setTag:long(static_cast<QAction *>(action->action))]; | |
#endif | |
syncAction(action); | |
} | |
// return an autoreleased string given a QKeySequence (currently only looks at the first one). | |
NSString *keySequenceToKeyEqivalent(const QKeySequence &accel) | |
{ | |
quint32 accel_key = (accel[0] & ~(Qt::MODIFIER_MASK | Qt::UNICODE_ACCEL)); | |
extern QChar qt_macSymbolForQtKey(int key); // qkeysequence.cpp | |
QChar keyEquiv = qt_macSymbolForQtKey(accel_key); | |
if (keyEquiv.isNull()) { | |
if (accel_key >= Qt::Key_F1 && accel_key <= Qt::Key_F15) | |
keyEquiv = (accel_key - Qt::Key_F1) + NSF1FunctionKey; | |
else | |
keyEquiv = unichar(QChar(accel_key).toLower().unicode()); | |
} | |
return [NSString stringWithCharacters:&keyEquiv.unicode() length:1]; | |
} | |
// return the cocoa modifier mask for the QKeySequence (currently only looks at the first one). | |
NSUInteger keySequenceModifierMask(const QKeySequence &accel) | |
{ | |
return constructModifierMask(accel[0]); | |
} | |
void | |
QMenuPrivate::QMacMenuPrivate::syncAction(QMacMenuAction *action) | |
{ | |
if (!action) | |
return; | |
#ifndef QT_MAC_USE_COCOA | |
const int index = qt_mac_menu_find_action(action->menu, action); | |
if (index == -1) | |
return; | |
#else | |
NSMenuItem *item = action->menuItem; | |
if (!item) | |
return; | |
#endif | |
#ifndef QT_MAC_USE_COCOA | |
if (!action->action->isVisible()) { | |
ChangeMenuItemAttributes(action->menu, index, kMenuItemAttrHidden, 0); | |
return; | |
} | |
ChangeMenuItemAttributes(action->menu, index, 0, kMenuItemAttrHidden); | |
#else | |
QMacCocoaAutoReleasePool pool; | |
NSMenu *menu = [item menu]; | |
bool actionVisible = action->action->isVisible(); | |
[item setHidden:!actionVisible]; | |
if (!actionVisible) | |
return; | |
#endif | |
#ifndef QT_MAC_USE_COCOA | |
if (action->action->isSeparator()) { | |
ChangeMenuItemAttributes(action->menu, index, kMenuItemAttrSeparator, 0); | |
return; | |
} | |
ChangeMenuItemAttributes(action->menu, index, 0, kMenuItemAttrSeparator); | |
#else | |
int itemIndex = [menu indexOfItem:item]; | |
Q_ASSERT(itemIndex != -1); | |
if (action->action->isSeparator()) { | |
action->menuItem = [NSMenuItem separatorItem]; | |
[action->menuItem retain]; | |
[menu insertItem: action->menuItem atIndex:itemIndex]; | |
[menu removeItem:item]; | |
[item release]; | |
item = action->menuItem; | |
return; | |
} else if ([item isSeparatorItem]) { | |
// I'm no longer a separator... | |
action->menuItem = createNSMenuItem(action->action->text()); | |
[menu insertItem:action->menuItem atIndex:itemIndex]; | |
[menu removeItem:item]; | |
[item release]; | |
item = action->menuItem; | |
} | |
#endif | |
//find text (and accel) | |
action->ignore_accel = 0; | |
QString text = action->action->text(); | |
QKeySequence accel = action->action->shortcut(); | |
{ | |
int st = text.lastIndexOf(QLatin1Char('\t')); | |
if (st != -1) { | |
action->ignore_accel = 1; | |
accel = QKeySequence(text.right(text.length()-(st+1))); | |
text.remove(st, text.length()-st); | |
} | |
} | |
{ | |
QString cmd_text = qt_mac_menu_merge_text(action); | |
if (!cmd_text.isEmpty()) { | |
text = cmd_text; | |
accel = qt_mac_menu_merge_accel(action); | |
} | |
} | |
// Show multiple key sequences as part of the menu text. | |
if (accel.count() > 1) | |
text += QLatin1String(" (") + accel.toString(QKeySequence::NativeText) + QLatin1String(")"); | |
QString finalString = qt_mac_removeMnemonics(text); | |
#ifndef QT_MAC_USE_COCOA | |
MenuItemDataRec data; | |
memset(&data, '\0', sizeof(data)); | |
//Carbon text | |
data.whichData |= kMenuItemDataCFString; | |
QCFString cfstring(finalString); // Hold the reference to the end of the function. | |
data.cfText = cfstring; | |
// Carbon enabled | |
data.whichData |= kMenuItemDataEnabled; | |
data.enabled = action->action->isEnabled(); | |
// Carbon icon | |
data.whichData |= kMenuItemDataIconHandle; | |
if (!action->action->icon().isNull() | |
&& action->action->isIconVisibleInMenu()) { | |
data.iconType = kMenuIconRefType; | |
data.iconHandle = (Handle)qt_mac_create_iconref(action->action->icon().pixmap(16, QIcon::Normal)); | |
} else { | |
data.iconType = kMenuNoIcon; | |
} | |
if (action->action->font().resolve()) { // Carbon font | |
if (action->action->font().bold()) | |
data.style |= bold; | |
if (action->action->font().underline()) | |
data.style |= underline; | |
if (action->action->font().italic()) | |
data.style |= italic; | |
if (data.style) | |
data.whichData |= kMenuItemDataStyle; | |
data.whichData |= kMenuItemDataFontID; | |
data.fontID = action->action->font().macFontID(); | |
} | |
#else | |
// Cocoa Font and title | |
if (action->action->font().resolve()) { | |
const QFont &actionFont = action->action->font(); | |
NSFont *customMenuFont = [NSFont fontWithName:qt_mac_QStringToNSString(actionFont.family()) | |
size:actionFont.pointSize()]; | |
NSArray *keys = [NSArray arrayWithObjects:NSFontAttributeName, nil]; | |
NSArray *objects = [NSArray arrayWithObjects:customMenuFont, nil]; | |
NSDictionary *attributes = [NSDictionary dictionaryWithObjects:objects forKeys:keys]; | |
NSAttributedString *str = [[[NSAttributedString alloc] initWithString:qt_mac_QStringToNSString(finalString) | |
attributes:attributes] autorelease]; | |
[item setAttributedTitle: str]; | |
} else { | |
[item setTitle: qt_mac_QStringToNSString(finalString)]; | |
} | |
if (action->action->menuRole() == QAction::AboutRole || action->action->menuRole() == QAction::QuitRole) | |
[item setTitle:qt_mac_QStringToNSString(text)]; | |
else | |
[item setTitle:qt_mac_QStringToNSString(qt_mac_removeMnemonics(text))]; | |
// Cocoa Enabled | |
[item setEnabled: action->action->isEnabled()]; | |
// Cocoa icon | |
NSImage *nsimage = 0; | |
if (!action->action->icon().isNull() && action->action->isIconVisibleInMenu()) { | |
nsimage = static_cast<NSImage *>(qt_mac_create_nsimage(action->action->icon().pixmap(16, QIcon::Normal))); | |
} | |
[item setImage:nsimage]; | |
[nsimage release]; | |
#endif | |
if (action->action->menu()) { //submenu | |
#ifndef QT_MAC_USE_COCOA | |
data.whichData |= kMenuItemDataSubmenuHandle; | |
data.submenuHandle = action->action->menu()->macMenu(); | |
QWidget *caused = 0; | |
GetMenuItemProperty(action->menu, 0, kMenuCreatorQt, kMenuPropertyQWidget, sizeof(caused), 0, &caused); | |
SetMenuItemProperty(data.submenuHandle, 0, kMenuCreatorQt, kMenuPropertyCausedQWidget, sizeof(caused), &caused); | |
#else | |
NSMenu *subMenu = static_cast<NSMenu *>(action->action->menu()->macMenu()); | |
if ([subMenu supermenu] && [subMenu supermenu] != [item menu]) { | |
// The menu is already a sub-menu of another one. Cocoa will throw an exception, | |
// in such cases. For the time being, a new QMenu with same set of actions is the | |
// only workaround. | |
action->action->setEnabled(false); | |
} else { | |
[item setSubmenu:subMenu]; | |
} | |
#endif | |
} else { //respect some other items | |
#ifndef QT_MAC_USE_COCOA | |
//shortcuts (say we are setting them all so that we can also clear them). | |
data.whichData |= kMenuItemDataCmdKey; | |
data.whichData |= kMenuItemDataCmdKeyModifiers; | |
data.whichData |= kMenuItemDataCmdKeyGlyph; | |
if (accel.count() == 1) { | |
qt_mac_get_accel(accel[0], (quint32*)&data.cmdKeyModifiers, (quint32*)&data.cmdKeyGlyph); | |
if (data.cmdKeyGlyph == 0) | |
data.cmdKey = (UniChar)accel[0]; | |
} | |
#else | |
[item setSubmenu:0]; | |
// No key equivalent set for multiple key QKeySequence. | |
if (accel.count() == 1) { | |
[item setKeyEquivalent:keySequenceToKeyEqivalent(accel)]; | |
[item setKeyEquivalentModifierMask:keySequenceModifierMask(accel)]; | |
} else { | |
[item setKeyEquivalent:@""]; | |
[item setKeyEquivalentModifierMask:NSCommandKeyMask]; | |
} | |
#endif | |
} | |
#ifndef QT_MAC_USE_COCOA | |
//mark glyph | |
data.whichData |= kMenuItemDataMark; | |
if (action->action->isChecked()) { | |
#if 0 | |
if (action->action->actionGroup() && | |
action->action->actionGroup()->isExclusive()) | |
data.mark = diamondMark; | |
else | |
#endif | |
data.mark = checkMark; | |
} else { | |
data.mark = noMark; | |
} | |
//actually set it | |
SetMenuItemData(action->menu, action->command, true, &data); | |
// Free up memory | |
if (data.iconHandle) | |
ReleaseIconRef(IconRef(data.iconHandle)); | |
#else | |
//mark glyph | |
[item setState:action->action->isChecked() ? NSOnState : NSOffState]; | |
#endif | |
} | |
void | |
QMenuPrivate::QMacMenuPrivate::removeAction(QMacMenuAction *action) | |
{ | |
if (!action) | |
return; | |
#ifndef QT_MAC_USE_COCOA | |
if (action->command == kHICommandQuit || action->command == kHICommandPreferences) | |
qt_mac_command_set_enabled(action->menu, action->command, false); | |
else | |
DeleteMenuItem(action->menu, qt_mac_menu_find_action(action->menu, action)); | |
#else | |
QMacCocoaAutoReleasePool pool; | |
if (action->merged) { | |
if (reinterpret_cast<QAction *>([action->menuItem tag]) == action->action) { | |
QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader(); | |
[action->menuItem setEnabled:false]; | |
if (action->menuItem != [loader quitMenuItem] | |
&& action->menuItem != [loader preferencesMenuItem]) { | |
[[action->menuItem menu] removeItem:action->menuItem]; | |
} | |
} | |
} else { | |
[[action->menuItem menu] removeItem:action->menuItem]; | |
} | |
#endif | |
actionItems.removeAll(action); | |
} | |
OSMenuRef | |
QMenuPrivate::macMenu(OSMenuRef merge) | |
{ | |
Q_UNUSED(merge); | |
Q_Q(QMenu); | |
if (mac_menu && mac_menu->menu) | |
return mac_menu->menu; | |
if (!mac_menu) | |
mac_menu = new QMacMenuPrivate; | |
mac_menu->menu = qt_mac_create_menu(q); | |
if (merge) { | |
#ifndef QT_MAC_USE_COCOA | |
SetMenuItemProperty(mac_menu->menu, 0, kMenuCreatorQt, kMenuPropertyMergeMenu, sizeof(merge), &merge); | |
#else | |
mergeMenuHash.insert(mac_menu->menu, merge); | |
#endif | |
} | |
QList<QAction*> items = q->actions(); | |
for(int i = 0; i < items.count(); i++) | |
mac_menu->addAction(items[i], 0, this); | |
syncSeparatorsCollapsible(collapsibleSeparators); | |
return mac_menu->menu; | |
} | |
/*! | |
\internal | |
*/ | |
void | |
QMenuPrivate::syncSeparatorsCollapsible(bool collapse) | |
{ | |
#ifndef QT_MAC_USE_COCOA | |
if (collapse) | |
ChangeMenuAttributes(mac_menu->menu, kMenuAttrCondenseSeparators, 0); | |
else | |
ChangeMenuAttributes(mac_menu->menu, 0, kMenuAttrCondenseSeparators); | |
#else | |
qt_mac_menu_collapseSeparators(mac_menu->menu, collapse); | |
#endif | |
} | |
/*! | |
\internal | |
*/ | |
void QMenuPrivate::setMacMenuEnabled(bool enable) | |
{ | |
if (!macMenu(0)) | |
return; | |
QMacCocoaAutoReleasePool pool; | |
if (enable) { | |
for (int i = 0; i < mac_menu->actionItems.count(); ++i) { | |
QMacMenuAction *menuItem = mac_menu->actionItems.at(i); | |
if (menuItem && menuItem->action && menuItem->action->isEnabled()) { | |
#ifndef QT_MAC_USE_COCOA | |
// Only enable those items which contains an enabled QAction. | |
// i == 0 -> the menu itself, hence i + 1 for items. | |
EnableMenuItem(mac_menu->menu, i + 1); | |
#else | |
[menuItem->menuItem setEnabled:true]; | |
#endif | |
} | |
} | |
} else { | |
#ifndef QT_MAC_USE_COCOA | |
DisableAllMenuItems(mac_menu->menu); | |
#else | |
NSMenu *menu = mac_menu->menu; | |
for (NSMenuItem *item in [menu itemArray]) { | |
[item setEnabled:false]; | |
} | |
#endif | |
} | |
} | |
/*! | |
\internal | |
This function will return the OSMenuRef used to create the native menu bar | |
bindings. | |
If Qt is built against Carbon, the OSMenuRef is a MenuRef that can be used | |
with Carbon's Menu Manager API. | |
If Qt is built against Cocoa, the OSMenuRef is a NSMenu pointer. | |
\warning This function is not portable. | |
\sa QMenuBar::macMenu() | |
*/ | |
OSMenuRef QMenu::macMenu(OSMenuRef merge) { return d_func()->macMenu(merge); } | |
/***************************************************************************** | |
QMenuBar bindings | |
*****************************************************************************/ | |
typedef QHash<QWidget *, QMenuBar *> MenuBarHash; | |
Q_GLOBAL_STATIC(MenuBarHash, menubars) | |
static QMenuBar *fallback = 0; | |
QMenuBarPrivate::QMacMenuBarPrivate::QMacMenuBarPrivate() : menu(0), apple_menu(0) | |
{ | |
} | |
QMenuBarPrivate::QMacMenuBarPrivate::~QMacMenuBarPrivate() | |
{ | |
for(QList<QMacMenuAction*>::Iterator it = actionItems.begin(); it != actionItems.end(); ++it) | |
delete (*it); | |
#ifndef QT_MAC_USE_COCOA | |
if (apple_menu) { | |
QMenuMergeList *list = 0; | |
GetMenuItemProperty(apple_menu, 0, kMenuCreatorQt, kMenuPropertyMergeList, | |
sizeof(list), 0, &list); | |
if (list) { | |
RemoveMenuItemProperty(apple_menu, 0, kMenuCreatorQt, kMenuPropertyMergeList); | |
delete list; | |
} | |
ReleaseMenu(apple_menu); | |
} | |
if (menu) | |
ReleaseMenu(menu); | |
#else | |
[apple_menu release]; | |
[menu release]; | |
#endif | |
} | |
void | |
QMenuBarPrivate::QMacMenuBarPrivate::addAction(QAction *a, QMacMenuAction *before) | |
{ | |
if (a->isSeparator() || !menu) | |
return; | |
QMacMenuAction *action = new QMacMenuAction; | |
action->action = a; | |
action->ignore_accel = 1; | |
#ifndef QT_MAC_USE_COCOA | |
action->command = qt_mac_menu_static_cmd_id++; | |
#endif | |
addAction(action, before); | |
} | |
void | |
QMenuBarPrivate::QMacMenuBarPrivate::addAction(QMacMenuAction *action, QMacMenuAction *before) | |
{ | |
if (!action || !menu) | |
return; | |
int before_index = actionItems.indexOf(before); | |
if (before_index < 0) { | |
before = 0; | |
before_index = actionItems.size(); | |
} | |
actionItems.insert(before_index, action); | |
MenuItemIndex index = actionItems.size()-1; | |
action->menu = menu; | |
#ifdef QT_MAC_USE_COCOA | |
QMacCocoaAutoReleasePool pool; | |
[action->menu retain]; | |
NSMenuItem *newItem = createNSMenuItem(action->action->text()); | |
action->menuItem = newItem; | |
#endif | |
if (before) { | |
#ifndef QT_MAC_USE_COCOA | |
InsertMenuItemTextWithCFString(action->menu, 0, qMax(1, before_index+1), 0, action->command); | |
#else | |
[menu insertItem:newItem atIndex:qMax(1, before_index + 1)]; | |
#endif | |
index = before_index; | |
} else { | |
#ifndef QT_MAC_USE_COCOA | |
AppendMenuItemTextWithCFString(action->menu, 0, 0, action->command, &index); | |
#else | |
[menu addItem:newItem]; | |
#endif | |
} | |
#ifndef QT_MAC_USE_COCOA | |
SetMenuItemProperty(action->menu, index, kMenuCreatorQt, kMenuPropertyQAction, sizeof(action), | |
&action); | |
#else | |
[newItem setTag:long(static_cast<QAction *>(action->action))]; | |
#endif | |
syncAction(action); | |
} | |
void | |
QMenuBarPrivate::QMacMenuBarPrivate::syncAction(QMacMenuAction *action) | |
{ | |
if (!action || !menu) | |
return; | |
#ifndef QT_MAC_USE_COCOA | |
const int index = qt_mac_menu_find_action(action->menu, action); | |
#else | |
QMacCocoaAutoReleasePool pool; | |
NSMenuItem *item = action->menuItem; | |
#endif | |
OSMenuRef submenu = 0; | |
bool release_submenu = false; | |
if (action->action->menu()) { | |
if ((submenu = action->action->menu()->macMenu(apple_menu))) { | |
#ifndef QT_MAC_USE_COCOA | |
QWidget *caused = 0; | |
GetMenuItemProperty(action->menu, 0, kMenuCreatorQt, kMenuPropertyQWidget, sizeof(caused), 0, &caused); | |
SetMenuItemProperty(submenu, 0, kMenuCreatorQt, kMenuPropertyCausedQWidget, sizeof(caused), &caused); | |
#else | |
if ([submenu supermenu] && [submenu supermenu] != [item menu]) | |
return; | |
else | |
[item setSubmenu:submenu]; | |
#endif | |
} | |
#ifndef QT_MAC_USE_COCOA | |
} else { // create a submenu to act as menu | |
release_submenu = true; | |
CreateNewMenu(0, 0, &submenu); | |
#endif | |
} | |
if (submenu) { | |
bool visible = actualMenuItemVisibility(this, action); | |
#ifndef QT_MAC_USE_COCOA | |
SetMenuItemHierarchicalMenu(action->menu, index, submenu); | |
SetMenuTitleWithCFString(submenu, QCFString(qt_mac_removeMnemonics(action->action->text()))); | |
if (visible) | |
ChangeMenuAttributes(submenu, 0, kMenuAttrHidden); | |
else | |
ChangeMenuAttributes(submenu, kMenuAttrHidden, 0); | |
#else | |
[item setSubmenu: submenu]; | |
[submenu setTitle:qt_mac_QStringToNSString(qt_mac_removeMnemonics(action->action->text()))]; | |
syncNSMenuItemVisiblity(item, visible); | |
#endif | |
if (release_submenu) { //no pointers to it | |
#ifndef QT_MAC_USE_COCOA | |
ReleaseMenu(submenu); | |
#else | |
[submenu release]; | |
#endif | |
} | |
} else { | |
qWarning("QMenu: No OSMenuRef created for popup menu"); | |
} | |
} | |
void | |
QMenuBarPrivate::QMacMenuBarPrivate::removeAction(QMacMenuAction *action) | |
{ | |
if (!action || !menu) | |
return; | |
#ifndef QT_MAC_USE_COCOA | |
DeleteMenuItem(action->menu, qt_mac_menu_find_action(action->menu, action)); | |
#else | |
QMacCocoaAutoReleasePool pool; | |
[action->menu removeItem:action->menuItem]; | |
#endif | |
actionItems.removeAll(action); | |
} | |
bool QMenuBarPrivate::macWidgetHasNativeMenubar(QWidget *widget) | |
{ | |
// This function is different from q->isNativeMenuBar(), as | |
// it returns true only if a native menu bar is actually | |
// _created_. | |
if (!widget) | |
return false; | |
return menubars()->contains(widget->window()); | |
} | |
void | |
QMenuBarPrivate::macCreateMenuBar(QWidget *parent) | |
{ | |
Q_Q(QMenuBar); | |
static int dontUseNativeMenuBar = -1; | |
// We call the isNativeMenuBar function here | |
// because that will make sure that local overrides | |
// are dealt with correctly. q->isNativeMenuBar() will, if not | |
// overridden, depend on the attribute Qt::AA_DontUseNativeMenuBar: | |
bool qt_mac_no_native_menubar = !q->isNativeMenuBar(); | |
if (qt_mac_no_native_menubar == false && dontUseNativeMenuBar < 0) { | |
// The menubar is set to be native. Let's check (one time only | |
// for all menubars) if this is OK with the rest of the environment. | |
// As a result, Qt::AA_DontUseNativeMenuBar is set. NB: the application | |
// might still choose to not respect, or change, this flag. | |
bool isPlugin = QApplication::testAttribute(Qt::AA_MacPluginApplication); | |
bool environmentSaysNo = !qgetenv("QT_MAC_NO_NATIVE_MENUBAR").isEmpty(); | |
dontUseNativeMenuBar = isPlugin || environmentSaysNo; | |
QApplication::instance()->setAttribute(Qt::AA_DontUseNativeMenuBar, dontUseNativeMenuBar); | |
qt_mac_no_native_menubar = !q->isNativeMenuBar(); | |
} | |
if (qt_mac_no_native_menubar == false) { | |
// INVARIANT: Use native menubar. | |
extern void qt_event_request_menubarupdate(); //qapplication_mac.cpp | |
qt_event_request_menubarupdate(); | |
if (!parent && !fallback) { | |
fallback = q; | |
mac_menubar = new QMacMenuBarPrivate; | |
} else if (parent && parent->isWindow()) { | |
menubars()->insert(q->window(), q); | |
mac_menubar = new QMacMenuBarPrivate; | |
} | |
} | |
} | |
void QMenuBarPrivate::macDestroyMenuBar() | |
{ | |
Q_Q(QMenuBar); | |
QMacCocoaAutoReleasePool pool; | |
if (fallback == q) | |
fallback = 0; | |
delete mac_menubar; | |
QWidget *tlw = q->window(); | |
menubars()->remove(tlw); | |
mac_menubar = 0; | |
if (!qt_mac_current_menubar.qmenubar || qt_mac_current_menubar.qmenubar == q) { | |
#ifdef QT_MAC_USE_COCOA | |
QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader(); | |
[loader removeActionsFromAppMenu]; | |
#else | |
cancelAllMenuTracking(); | |
#endif | |
extern void qt_event_request_menubarupdate(); //qapplication_mac.cpp | |
qt_event_request_menubarupdate(); | |
} | |
} | |
OSMenuRef QMenuBarPrivate::macMenu() | |
{ | |
Q_Q(QMenuBar); | |
if (!q->isNativeMenuBar() || !mac_menubar) { | |
return 0; | |
} else if (!mac_menubar->menu) { | |
mac_menubar->menu = qt_mac_create_menu(q); | |
ProcessSerialNumber mine, front; | |
if (GetCurrentProcess(&mine) == noErr && GetFrontProcess(&front) == noErr) { | |
if (!qt_mac_no_menubar_merge && !mac_menubar->apple_menu) { | |
mac_menubar->apple_menu = qt_mac_create_menu(q); | |
#ifndef QT_MAC_USE_COCOA | |
MenuItemIndex index; | |
AppendMenuItemTextWithCFString(mac_menubar->menu, 0, 0, 0, &index); | |
SetMenuTitleWithCFString(mac_menubar->apple_menu, QCFString(QString(QChar(0x14)))); | |
SetMenuItemHierarchicalMenu(mac_menubar->menu, index, mac_menubar->apple_menu); | |
SetMenuItemProperty(mac_menubar->apple_menu, 0, kMenuCreatorQt, kMenuPropertyQWidget, sizeof(q), &q); | |
#else | |
[mac_menubar->apple_menu setTitle:qt_mac_QStringToNSString(QString(QChar(0x14)))]; | |
NSMenuItem *apple_menuItem = [[NSMenuItem alloc] init]; | |
[apple_menuItem setSubmenu:mac_menubar->menu]; | |
[mac_menubar->apple_menu addItem:apple_menuItem]; | |
[apple_menuItem release]; | |
#endif | |
} | |
if (mac_menubar->apple_menu) { | |
#ifndef QT_MAC_USE_COCOA | |
SetMenuItemProperty(mac_menubar->menu, 0, kMenuCreatorQt, kMenuPropertyMergeMenu, | |
sizeof(mac_menubar->apple_menu), &mac_menubar->apple_menu); | |
#else | |
QMenuPrivate::mergeMenuHash.insert(mac_menubar->menu, mac_menubar->apple_menu); | |
#endif | |
} | |
QList<QAction*> items = q->actions(); | |
for(int i = 0; i < items.count(); i++) | |
mac_menubar->addAction(items[i]); | |
} | |
} | |
return mac_menubar->menu; | |
} | |
/*! | |
\internal | |
This function will return the OSMenuRef used to create the native menu bar | |
bindings. This OSMenuRef is then set as the root menu for the Menu | |
Manager. | |
\warning This function is not portable. | |
\sa QMenu::macMenu() | |
*/ | |
OSMenuRef QMenuBar::macMenu() { return d_func()->macMenu(); } | |
/* ! | |
\internal | |
Ancestor function that crosses windows (QWidget::isAncestorOf | |
only considers widgets within the same window). | |
*/ | |
static bool qt_mac_is_ancestor(QWidget* possibleAncestor, QWidget *child) | |
{ | |
if (!possibleAncestor) | |
return false; | |
QWidget * current = child->parentWidget(); | |
while (current != 0) { | |
if (current == possibleAncestor) | |
return true; | |
current = current->parentWidget(); | |
} | |
return false; | |
} | |
/* ! | |
\internal | |
Returns true if the entries of menuBar should be disabled, | |
based on the modality type of modalWidget. | |
*/ | |
static bool qt_mac_should_disable_menu(QMenuBar *menuBar) | |
{ | |
QWidget *modalWidget = qApp->activeModalWidget(); | |
if (!modalWidget) | |
return false; | |
if (menuBar && menuBar == menubars()->value(modalWidget)) | |
// The menu bar is owned by the modal widget. | |
// In that case we should enable it: | |
return false; | |
// When there is an application modal window on screen, the entries of | |
// the menubar should be disabled. The exception in Qt is that if the | |
// modal window is the only window on screen, then we enable the menu bar. | |
QWidget *w = modalWidget; | |
QWidgetList topLevelWidgets = QApplication::topLevelWidgets(); | |
while (w) { | |
if (w->isVisible() && w->windowModality() == Qt::ApplicationModal) { | |
for (int i=0; i<topLevelWidgets.size(); ++i) { | |
QWidget *top = topLevelWidgets.at(i); | |
if (w != top && top->isVisible()) { | |
// INVARIANT: we found another visible window | |
// on screen other than our modalWidget. We therefore | |
// disable the menu bar to follow normal modality logic: | |
return true; | |
} | |
} | |
// INVARIANT: We have only one window on screen that happends | |
// to be application modal. We choose to enable the menu bar | |
// in that case to e.g. enable the quit menu item. | |
return false; | |
} | |
w = w->parentWidget(); | |
} | |
// INVARIANT: modalWidget is window modal. Disable menu entries | |
// if the menu bar belongs to an ancestor of modalWidget. If menuBar | |
// is nil, we understand it as the default menu bar set by the nib: | |
return menuBar ? qt_mac_is_ancestor(menuBar->parentWidget(), modalWidget) : false; | |
} | |
static QWidget *findWindowThatShouldDisplayMenubar() | |
{ | |
QWidget *w = qApp->activeWindow(); | |
if (!w) { | |
// We have no active window on screen. Try to | |
// find a window from the list of top levels: | |
QWidgetList tlws = QApplication::topLevelWidgets(); | |
for(int i = 0; i < tlws.size(); ++i) { | |
QWidget *tlw = tlws.at(i); | |
if ((tlw->isVisible() && tlw->windowType() != Qt::Tool && | |
tlw->windowType() != Qt::Popup)) { | |
w = tlw; | |
break; | |
} | |
} | |
} | |
return w; | |
} | |
static QMenuBar *findMenubarForWindow(QWidget *w) | |
{ | |
QMenuBar *mb = 0; | |
if (w) { | |
mb = menubars()->value(w); | |
#ifndef QT_NO_MAINWINDOW | |
QDockWidget *dw = qobject_cast<QDockWidget *>(w); | |
if (!mb && dw) { | |
QMainWindow *mw = qobject_cast<QMainWindow *>(dw->parentWidget()); | |
if (mw && (mb = menubars()->value(mw))) | |
w = mw; | |
} | |
#endif | |
while(w && !mb) | |
mb = menubars()->value((w = w->parentWidget())); | |
} | |
if (!mb) { | |
// We could not find a menu bar for the window. Lets | |
// check if we have a global (parentless) menu bar instead: | |
mb = fallback; | |
} | |
return mb; | |
} | |
void qt_mac_clear_menubar() | |
{ | |
if (QApplication::testAttribute(Qt::AA_MacPluginApplication)) | |
return; | |
#ifndef QT_MAC_USE_COCOA | |
MenuRef clear_menu = 0; | |
if (CreateNewMenu(0, 0, &clear_menu) == noErr) { | |
SetRootMenu(clear_menu); | |
ReleaseMenu(clear_menu); | |
} else { | |
qWarning("QMenu: Internal error at %s:%d", __FILE__, __LINE__); | |
} | |
ClearMenuBar(); | |
qt_mac_command_set_enabled(0, kHICommandPreferences, false); | |
InvalMenuBar(); | |
#else | |
QMacCocoaAutoReleasePool pool; | |
QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader(); | |
NSMenu *menu = [loader menu]; | |
[loader ensureAppMenuInMenu:menu]; | |
[NSApp setMainMenu:menu]; | |
const bool modal = qt_mac_should_disable_menu(0); | |
if (qt_mac_current_menubar.qmenubar || modal != qt_mac_current_menubar.modal) | |
qt_mac_set_modal_state(menu, modal); | |
qt_mac_current_menubar.qmenubar = 0; | |
qt_mac_current_menubar.modal = modal; | |
#endif | |
} | |
/*! | |
\internal | |
This function will update the current menu bar and set it as the | |
active menu bar in the Menu Manager. | |
\warning This function is not portable. | |
\sa QMenu::macMenu(), QMenuBar::macMenu() | |
*/ | |
bool QMenuBar::macUpdateMenuBar() | |
{ | |
#ifdef QT_MAC_USE_COCOA | |
QMacCocoaAutoReleasePool pool; | |
if (!qt_cocoaPostMessage(getMenuLoader(), @selector(qtUpdateMenubar))) | |
return QMenuBarPrivate::macUpdateMenuBarImmediatly(); | |
return true; | |
#else | |
return QMenuBarPrivate::macUpdateMenuBarImmediatly(); | |
#endif | |
} | |
bool QMenuBarPrivate::macUpdateMenuBarImmediatly() | |
{ | |
bool ret = false; | |
cancelAllMenuTracking(); | |
QWidget *w = findWindowThatShouldDisplayMenubar(); | |
QMenuBar *mb = findMenubarForWindow(w); | |
extern bool qt_mac_app_fullscreen; //qapplication_mac.mm | |
// We need to see if we are in full screen mode, if so we need to | |
// switch the full screen mode to be able to show or hide the menubar. | |
if(w && mb) { | |
// This case means we are creating a menubar, check if full screen | |
if(w->isFullScreen()) { | |
// Ok, switch to showing the menubar when hovering over it. | |
SetSystemUIMode(kUIModeAllHidden, kUIOptionAutoShowMenuBar); | |
qt_mac_app_fullscreen = true; | |
} | |
} else if(w) { | |
// Removing a menubar | |
if(w->isFullScreen()) { | |
// Ok, switch to not showing the menubar when hovering on it | |
SetSystemUIMode(kUIModeAllHidden, 0); | |
qt_mac_app_fullscreen = true; | |
} | |
} | |
if (mb && mb->isNativeMenuBar()) { | |
bool modal = QApplicationPrivate::modalState(); | |
#ifdef QT_MAC_USE_COCOA | |
QMacCocoaAutoReleasePool pool; | |
#endif | |
if (OSMenuRef menu = mb->macMenu()) { | |
#ifndef QT_MAC_USE_COCOA | |
SetRootMenu(menu); | |
#else | |
QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader(); | |
[loader ensureAppMenuInMenu:menu]; | |
[NSApp setMainMenu:menu]; | |
syncMenuBarItemsVisiblity(mb->d_func()->mac_menubar); | |
if (OSMenuRef tmpMerge = QMenuPrivate::mergeMenuHash.value(menu)) { | |
if (QMenuMergeList *mergeList | |
= QMenuPrivate::mergeMenuItemsHash.value(tmpMerge)) { | |
const int mergeListSize = mergeList->size(); | |
for (int i = 0; i < mergeListSize; ++i) { | |
const QMenuMergeItem &mergeItem = mergeList->at(i); | |
// Ideally we would call QMenuPrivate::syncAction, but that requires finding | |
// the original QMen and likely doing more work than we need. | |
// For example, enabled is handled below. | |
[mergeItem.menuItem setTag:reinterpret_cast<long>( | |
static_cast<QAction *>(mergeItem.action->action))]; | |
[mergeItem.menuItem setHidden:!(mergeItem.action->action->isVisible())]; | |
} | |
} | |
} | |
#endif | |
// Check if menu is modally shaddowed and should be disabled: | |
modal = qt_mac_should_disable_menu(mb); | |
if (mb != qt_mac_current_menubar.qmenubar || modal != qt_mac_current_menubar.modal) | |
qt_mac_set_modal_state(menu, modal); | |
} | |
qt_mac_current_menubar.qmenubar = mb; | |
qt_mac_current_menubar.modal = modal; | |
ret = true; | |
} else if (qt_mac_current_menubar.qmenubar && qt_mac_current_menubar.qmenubar->isNativeMenuBar()) { | |
// INVARIANT: The currently active menu bar (if any) is not native. But we do have a | |
// native menu bar from before. So we need to decide whether or not is should be enabled: | |
const bool modal = qt_mac_should_disable_menu(qt_mac_current_menubar.qmenubar); | |
if (modal != qt_mac_current_menubar.modal) { | |
ret = true; | |
if (OSMenuRef menu = qt_mac_current_menubar.qmenubar->macMenu()) { | |
#ifndef QT_MAC_USE_COCOA | |
SetRootMenu(menu); | |
#else | |
QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader(); | |
[loader ensureAppMenuInMenu:menu]; | |
[NSApp setMainMenu:menu]; | |
syncMenuBarItemsVisiblity(qt_mac_current_menubar.qmenubar->d_func()->mac_menubar); | |
#endif | |
qt_mac_set_modal_state(menu, modal); | |
} | |
qt_mac_current_menubar.modal = modal; | |
} | |
} | |
if (!ret) { | |
qt_mac_clear_menubar(); | |
} | |
return ret; | |
} | |
QHash<OSMenuRef, OSMenuRef> QMenuPrivate::mergeMenuHash; | |
QHash<OSMenuRef, QMenuMergeList*> QMenuPrivate::mergeMenuItemsHash; | |
bool QMenuPrivate::QMacMenuPrivate::merged(const QAction *action) const | |
{ | |
#ifndef QT_MAC_USE_COCOA | |
MenuRef merge = 0; | |
GetMenuItemProperty(menu, 0, kMenuCreatorQt, kMenuPropertyMergeMenu, | |
sizeof(merge), 0, &merge); | |
if (merge) { | |
QMenuMergeList *list = 0; | |
if (GetMenuItemProperty(merge, 0, kMenuCreatorQt, kMenuPropertyMergeList, | |
sizeof(list), 0, &list) == noErr && list) { | |
for(int i = 0; i < list->size(); ++i) { | |
QMenuMergeItem item = list->at(i); | |
if (item.action->action == action) | |
return true; | |
} | |
} | |
} | |
#else | |
if (OSMenuRef merge = mergeMenuHash.value(menu)) { | |
if (QMenuMergeList *list = mergeMenuItemsHash.value(merge)) { | |
for(int i = 0; i < list->size(); ++i) { | |
const QMenuMergeItem &item = list->at(i); | |
if (item.action->action == action) | |
return true; | |
} | |
} | |
} | |
#endif | |
return false; | |
} | |
//creation of the OSMenuRef | |
static OSMenuRef qt_mac_create_menu(QWidget *w) | |
{ | |
OSMenuRef ret; | |
#ifndef QT_MAC_USE_COCOA | |
ret = 0; | |
if (CreateNewMenu(0, 0, &ret) == noErr) { | |
qt_mac_create_menu_event_handler(); | |
SetMenuItemProperty(ret, 0, kMenuCreatorQt, kMenuPropertyQWidget, sizeof(w), &w); | |
// kEventMenuMatchKey is only sent to the menu itself and not to | |
// the application, install a separate handler for that event. | |
EventHandlerRef eventHandlerRef; | |
InstallMenuEventHandler(ret, qt_mac_menu_event, | |
GetEventTypeCount(menu_menu_events), | |
menu_menu_events, 0, &eventHandlerRef); | |
menu_eventHandlers_hash()->insert(ret, eventHandlerRef); | |
} else { | |
qWarning("QMenu: Internal error"); | |
} | |
#else | |
if (QMenu *qmenu = qobject_cast<QMenu *>(w)){ | |
ret = [[QT_MANGLE_NAMESPACE(QCocoaMenu) alloc] initWithQMenu:qmenu]; | |
} else { | |
ret = [[NSMenu alloc] init]; | |
} | |
#endif | |
return ret; | |
} | |
QT_END_NAMESPACE | |