summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJames Turner <james.turner@kdab.com>2012-05-04 14:16:05 +0100
committerQt by Nokia <qt-info@nokia.com>2012-05-19 10:18:21 +0200
commitb8246f08e49eb672974fd3d3d972a5ff13c1524d (patch)
tree509ab759670f0b24aa8d44ced0584fc2832f5e76
parent899f1d35a435fd499c73b29aabb6a609d496e5ed (diff)
Cocoa implementation of QPA menu interface.
Implement the QPA platform menu interface for Cocoa, including native menubar support and merging with the predefined menus created from the bundled .nib. Cleanup code previously used to maintain the menus, and add a manual test of the menus code. Change-Id: Ia99267ddb6485e18e05c540eb32c5aee6cbb85db Reviewed-by: Morten Johan Sørvig <morten.sorvig@nokia.com>
-rw-r--r--src/plugins/platforms/cocoa/cocoa.pro10
-rw-r--r--src/plugins/platforms/cocoa/qcocoaapplication.mm11
-rw-r--r--src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm2
-rw-r--r--src/plugins/platforms/cocoa/qcocoahelpers.h5
-rw-r--r--src/plugins/platforms/cocoa/qcocoahelpers.mm14
-rw-r--r--src/plugins/platforms/cocoa/qcocoamenu.h97
-rw-r--r--src/plugins/platforms/cocoa/qcocoamenu.mm365
-rw-r--r--src/plugins/platforms/cocoa/qcocoamenubar.h82
-rw-r--r--src/plugins/platforms/cocoa/qcocoamenubar.mm254
-rw-r--r--src/plugins/platforms/cocoa/qcocoamenuitem.h117
-rw-r--r--src/plugins/platforms/cocoa/qcocoamenuitem.mm358
-rw-r--r--src/plugins/platforms/cocoa/qcocoamenuloader.h5
-rw-r--r--src/plugins/platforms/cocoa/qcocoamenuloader.mm80
-rw-r--r--src/plugins/platforms/cocoa/qcocoanativeinterface.h5
-rw-r--r--src/plugins/platforms/cocoa/qcocoanativeinterface.mm11
-rw-r--r--src/plugins/platforms/cocoa/qcocoatheme.h4
-rw-r--r--src/plugins/platforms/cocoa/qcocoatheme.mm32
-rw-r--r--src/plugins/platforms/cocoa/qcocoawindow.h5
-rw-r--r--src/plugins/platforms/cocoa/qcocoawindow.mm14
-rw-r--r--src/plugins/platforms/cocoa/qt_menu.nib/classes.nib2
-rw-r--r--tests/manual/cocoa/menus/main.cpp201
-rw-r--r--tests/manual/cocoa/menus/menus.pro5
22 files changed, 1423 insertions, 256 deletions
diff --git a/src/plugins/platforms/cocoa/cocoa.pro b/src/plugins/platforms/cocoa/cocoa.pro
index 313b666793..467ce96e63 100644
--- a/src/plugins/platforms/cocoa/cocoa.pro
+++ b/src/plugins/platforms/cocoa/cocoa.pro
@@ -14,9 +14,12 @@ OBJECTIVE_SOURCES += main.mm \
qcocoaglcontext.mm \
qcocoanativeinterface.mm \
qcocoaeventdispatcher.mm \
- qcocoamenuloader.mm \
qcocoaapplicationdelegate.mm \
qcocoaapplication.mm \
+ qcocoamenu.mm \
+ qcocoamenuitem.mm \
+ qcocoamenubar.mm \
+ qcocoamenuloader.mm \
qcocoahelpers.mm \
qmultitouch_mac.mm \
qcocoaaccessibilityelement.mm \
@@ -46,9 +49,12 @@ HEADERS += qcocoaintegration.h \
qcocoaglcontext.h \
qcocoanativeinterface.h \
qcocoaeventdispatcher.h \
- qcocoamenuloader.h \
qcocoaapplicationdelegate.h \
qcocoaapplication.h \
+ qcocoamenu.h \
+ qcocoamenuitem.h \
+ qcocoamenubar.h \
+ qcocoamenuloader.h \
qcocoahelpers.h \
qmultitouch_mac_p.h \
qcocoaaccessibilityelement.h \
diff --git a/src/plugins/platforms/cocoa/qcocoaapplication.mm b/src/plugins/platforms/cocoa/qcocoaapplication.mm
index 2cecb296f4..90f09aa620 100644
--- a/src/plugins/platforms/cocoa/qcocoaapplication.mm
+++ b/src/plugins/platforms/cocoa/qcocoaapplication.mm
@@ -189,17 +189,6 @@ QT_USE_NAMESPACE
[super sendEvent:event];
}
-- (void)qtDispatcherToQAction:(id)sender
-{
- // Forward actions sent from the menu bar (e.g. quit) to the menu loader.
- // Having this method here means that we are the last stop in the responder
- // chain, and that we are able to handle menu actions even when no window is
- // visible on screen. Note: If Qt is used as a plugin, Qt will not use a
- // native menu bar. Hence, we will also not need to do any redirection etc. as
- // we do with sendEvent.
- [[NSApp QT_MANGLE_NAMESPACE(qt_qcocoamenuLoader)] qtDispatcherToQAction:sender];
-}
-
@end
QT_BEGIN_NAMESPACE
diff --git a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm
index ec086fe62c..3855563bab 100644
--- a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm
+++ b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm
@@ -355,7 +355,7 @@ static void cleanupCocoaApplicationDelegate()
- (void)qtDispatcherToQAction:(id)sender
{
Q_UNUSED(sender);
- [qtMenuLoader qtDispatcherToQAction:sender];
+ [qtMenuLoader qtDispatcherToQPAMenuItem:sender];
}
@end
diff --git a/src/plugins/platforms/cocoa/qcocoahelpers.h b/src/plugins/platforms/cocoa/qcocoahelpers.h
index 5113aca3f7..f8032603e5 100644
--- a/src/plugins/platforms/cocoa/qcocoahelpers.h
+++ b/src/plugins/platforms/cocoa/qcocoahelpers.h
@@ -115,6 +115,11 @@ inline NSPoint qt_mac_flipPoint(const QPointF &p)
NSRect qt_mac_flipRect(const QRect &rect, QWindow *window);
+// strip out '&' characters, and convert "&&" to a single '&', in menu
+// text - since menu text is sometimes decorated with these for Windows
+// accelerators.
+QString qt_mac_removeAmpersandEscapes(QString s);
+
QT_END_NAMESPACE
#endif //QCOCOAHELPERS_H
diff --git a/src/plugins/platforms/cocoa/qcocoahelpers.mm b/src/plugins/platforms/cocoa/qcocoahelpers.mm
index 8f8d7b84a6..c0b6f3abb6 100644
--- a/src/plugins/platforms/cocoa/qcocoahelpers.mm
+++ b/src/plugins/platforms/cocoa/qcocoahelpers.mm
@@ -581,4 +581,18 @@ CGFloat qt_mac_get_scalefactor()
return [[NSScreen mainScreen] userSpaceScaleFactor];
}
+QString qt_mac_removeAmpersandEscapes(QString s)
+{
+ int i = 0;
+ while (i < s.size()) {
+ ++i;
+ if (s.at(i-1) != QLatin1Char('&'))
+ continue;
+ if (i < s.size() && s.at(i) == QLatin1Char('&'))
+ ++i;
+ s.remove(i-1,1);
+ }
+ return s.trimmed();
+}
+
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/cocoa/qcocoamenu.h b/src/plugins/platforms/cocoa/qcocoamenu.h
index bf8bba1ddf..479d4b53c2 100644
--- a/src/plugins/platforms/cocoa/qcocoamenu.h
+++ b/src/plugins/platforms/cocoa/qcocoamenu.h
@@ -1,9 +1,10 @@
/****************************************************************************
**
** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Copyright (C) 2012 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author James Turner <james.turner@kdab.com>
** Contact: http://www.qt-project.org/
**
-** This file is part of the QtGui module of the Qt Toolkit.
+** This file is part of the plugins of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** GNU Lesser General Public License Usage
@@ -39,42 +40,66 @@
**
****************************************************************************/
-//
-// W A R N I N G
-// -------------
-//
-// This file is not part of the Qt API. It exists purely as an
-// implementation detail. This header file may change from version to
-// version without notice, or even be removed.
-//
-// We mean it.
-//
-
-#include "qmacdefines_mac.h"
-#import <Cocoa/Cocoa.h>
-
-QT_FORWARD_DECLARE_CLASS(QMenu)
-QT_FORWARD_DECLARE_CLASS(QAction)
-
-#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_5
-
-@protocol NSMenuDelegate <NSObject>
-- (void)menu:(NSMenu*)menu willHighlightItem:(NSMenuItem*)item;
-- (void)menuWillOpen:(NSMenu*)menu;
-- (void)menuDidClose:(NSMenu*)menu;
-- (BOOL)hasShortcut:(NSMenu *)menu forKey:(NSString *)key forModifiers:(NSUInteger)modifier
- whichItem:(NSMenuItem**)outItem;
-@end
+#ifndef QCOCOAMENU_H
+#define QCOCOAMENU_H
-#endif
+#include <QtCore/QList>
+#include <qpa/qplatformmenu.h>
+#include "qcocoamenuitem.h"
+
+@class NSMenuItem;
+@class NSMenu;
+@class NSObject;
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
-@interface QT_MANGLE_NAMESPACE(QNativeCocoaMenu) : NSMenu <NSMenuDelegate>
+class QCocoaMenu : public QPlatformMenu
{
- QMenu *qmenu;
- QAction *previousAction;
-}
-- (id)initWithQMenu:(QMenu*)menu;
-- (BOOL)menuHasKeyEquivalent:(NSMenu *)menu forEvent:(NSEvent *)event target:(id *)target action:(SEL *)action;
-- (NSInteger)indexOfItemWithTarget:(id)anObject andAction:(SEL)actionSelector;
-@end
+public:
+ QCocoaMenu();
+
+ inline virtual void setTag(quintptr tag)
+ { m_tag = tag; }
+ inline virtual quintptr tag() const
+ { return m_tag; }
+
+ void insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem *before);
+ void removeMenuItem(QPlatformMenuItem *menuItem);
+ void syncMenuItem(QPlatformMenuItem *menuItem);
+ void setEnabled(bool enabled);
+ void syncSeparatorsCollapsible(bool enable);
+
+ void syncModalState(bool modal);
+
+ virtual void setText(const QString &text);
+ void setParentItem(QCocoaMenuItem* item);
+
+ inline NSMenu *nsMenu() const
+ { return m_nativeMenu; }
+ inline NSMenuItem *nsMenuItem() const
+ { return m_nativeItem; }
+
+ virtual QPlatformMenuItem *menuItemAt(int position) const;
+ virtual QPlatformMenuItem *menuItemForTag(quintptr tag) const;
+
+ QList<QCocoaMenuItem *> merged() const;
+private:
+ QCocoaMenuItem *itemOrNull(int index) const;
+ void insertNative(QCocoaMenuItem *item, QCocoaMenuItem *beforeItem);
+
+ QList<QCocoaMenuItem *> m_menuItems;
+ NSMenu *m_nativeMenu;
+ NSMenuItem *m_nativeItem;
+ NSObject *m_delegate;
+ bool m_enabled;
+ quintptr m_tag;
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif
diff --git a/src/plugins/platforms/cocoa/qcocoamenu.mm b/src/plugins/platforms/cocoa/qcocoamenu.mm
index 00bbf49cca..9e466deb9a 100644
--- a/src/plugins/platforms/cocoa/qcocoamenu.mm
+++ b/src/plugins/platforms/cocoa/qcocoamenu.mm
@@ -1,9 +1,9 @@
/****************************************************************************
**
-** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Copyright (C) 2012 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author James Turner <james.turner@kdab.com>
** Contact: http://www.qt-project.org/
**
-** This file is part of the QtGui module of the Qt Toolkit.
+** This file is part of the plugins of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** GNU Lesser General Public License Usage
@@ -39,229 +39,244 @@
**
****************************************************************************/
-#include "qapplication.h"
-#include "qvarlengtharray.h"
-#import "qcocoamenu.h"
-#import "qcocoamenuloader.h"
-#import "qcocoaapplication.h"
+#include "qcocoamenu.h"
+
#include "qcocoahelpers.h"
-#include <private/qapplication_p.h>
-#include <private/qaction_p.h>
+#include "qcocoaautoreleasepool.h"
-QT_FORWARD_DECLARE_CLASS(QAction)
-QT_FORWARD_DECLARE_CLASS(QWidget)
-QT_FORWARD_DECLARE_CLASS(QApplication)
-QT_FORWARD_DECLARE_CLASS(QCoreApplication)
-QT_FORWARD_DECLARE_CLASS(QApplicationPrivate)
-QT_FORWARD_DECLARE_CLASS(QKeyEvent)
-QT_FORWARD_DECLARE_CLASS(QEvent)
+@interface QT_MANGLE_NAMESPACE(QCocoaMenuDelegate) : NSObject <NSMenuDelegate> {
+ QCocoaMenu *m_menu;
+}
-QT_BEGIN_NAMESPACE
-extern void qt_mac_menu_collapseSeparators(NSMenu *menu, bool collapse);
-void qt_mac_clear_status_text(QAction *action);
-extern void qt_mac_emit_menuSignals(QMenu *menu, bool show);
-extern void qt_mac_menu_emit_hovered(QMenu *menu, QAction *action);
-QT_END_NAMESPACE
+- (id) initWithMenu:(QCocoaMenu*) m;
-QT_USE_NAMESPACE
+@end
-@implementation QT_MANGLE_NAMESPACE(QNativeCocoaMenu)
+@implementation QT_MANGLE_NAMESPACE(QCocoaMenuDelegate)
-- (id)initWithQMenu:(QMenu*)menu
+- (id) initWithMenu:(QCocoaMenu*) m
{
- self = [super init];
- if (self) {
- qmenu = menu;
- previousAction = 0;
- [self setAutoenablesItems:NO];
- [self setDelegate:self];
- }
+ if ((self = [super init]))
+ m_menu = m;
+
return self;
}
-- (void)menu:(NSMenu*)menu willHighlightItem:(NSMenuItem*)item
+- (void) menuWillOpen:(NSMenu*)m
{
- Q_UNUSED(menu);
+ Q_UNUSED(m);
+ emit m_menu->aboutToShow();
+}
- if (!item) {
- if (previousAction) {
- qt_mac_clear_status_text(previousAction);
- previousAction = 0;
- }
- return;
- }
+- (void) menuDidClose:(NSMenu*)m
+{
+ Q_UNUSED(m);
+ // wrong, but it's the best we can do
+ emit m_menu->aboutToHide();
+}
- if (QAction *action = reinterpret_cast<QAction *>([item tag])) {
- QMenu *qtmenu = static_cast<QT_MANGLE_NAMESPACE(QNativeCocoaMenu) *>(menu)->qmenu;
- previousAction = action;
- action->activate(QAction::Hover);
- qt_mac_menu_emit_hovered(qtmenu, action);
- action->showStatusText(0); // 0 widget -> action's parent
- }
+- (void) itemFired:(NSMenuItem*) item
+{
+ QCocoaMenuItem *cocoaItem = reinterpret_cast<QCocoaMenuItem *>([item tag]);
+ cocoaItem->activated();
}
-- (void)menuWillOpen:(NSMenu*)menu
+- (BOOL)validateMenuItem:(NSMenuItem*)menuItem
{
- while (QWidget *popup
- = QApplication::activePopupWidget())
- popup->close();
- QMenu *qtmenu = static_cast<QT_MANGLE_NAMESPACE(QNativeCocoaMenu) *>(menu)->qmenu;
- qt_mac_emit_menuSignals(qtmenu, true);
- qt_mac_menu_collapseSeparators(menu, qtmenu->separatorsCollapsible());
+ if (![menuItem tag])
+ return YES;
+
+
+ QCocoaMenuItem* cocoaItem = reinterpret_cast<QCocoaMenuItem *>([menuItem tag]);
+ return cocoaItem->isEnabled();
}
-- (void)menuDidClose:(NSMenu*)menu
+@end
+
+QCocoaMenu::QCocoaMenu() :
+ m_enabled(true),
+ m_tag(0)
{
- qt_mac_emit_menuSignals(((QT_MANGLE_NAMESPACE(QNativeCocoaMenu) *)menu)->qmenu, false);
- if (previousAction) {
- qt_mac_clear_status_text(previousAction);
- previousAction = 0;
+ m_delegate = [[QCocoaMenuDelegate alloc] initWithMenu:this];
+ m_nativeItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
+ m_nativeMenu = [[NSMenu alloc] initWithTitle:@"Untitled"];
+ [m_nativeMenu setAutoenablesItems:YES];
+ m_nativeMenu.delegate = (QCocoaMenuDelegate *) m_delegate;
+ [m_nativeItem setSubmenu:m_nativeMenu];
+}
+
+void QCocoaMenu::setText(const QString &text)
+{
+ QString stripped = qt_mac_removeAmpersandEscapes(text);
+ [m_nativeMenu setTitle:QCFString::toNSString(stripped)];
+ [m_nativeItem setTitle:QCFString::toNSString(stripped)];
+}
+
+void QCocoaMenu::insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem *before)
+{
+ QCocoaMenuItem *cocoaItem = static_cast<QCocoaMenuItem *>(menuItem);
+ QCocoaMenuItem *beforeItem = static_cast<QCocoaMenuItem *>(before);
+
+ cocoaItem->sync();
+ if (beforeItem) {
+ int index = m_menuItems.indexOf(beforeItem);
+ // if a before item is supplied, it should be in the menu
+ Q_ASSERT(index >= 0);
+ m_menuItems.insert(index, cocoaItem);
+ } else {
+ m_menuItems.append(cocoaItem);
}
+
+ insertNative(cocoaItem, beforeItem);
}
-- (BOOL)hasShortcut:(NSMenu *)menu forKey:(NSString *)key forModifiers:(NSUInteger)modifier
- whichItem:(NSMenuItem**)outItem
+void QCocoaMenu::insertNative(QCocoaMenuItem *item, QCocoaMenuItem *beforeItem)
{
- for (NSMenuItem *item in [menu itemArray]) {
- if (![item isEnabled] || [item isHidden] || [item isSeparatorItem])
- continue;
- if ([item hasSubmenu]) {
- if ([self hasShortcut:[item submenu]
- forKey:key
- forModifiers:modifier whichItem:outItem]) {
- if (outItem)
- *outItem = item;
- return YES;
- }
- }
- NSString *menuKey = [item keyEquivalent];
- if (menuKey && NSOrderedSame == [menuKey compare:key]
- && (modifier == [item keyEquivalentModifierMask])) {
- if (outItem)
- *outItem = item;
- return YES;
- }
+ [item->nsItem() setTarget:m_delegate];
+ [item->nsItem() setAction:@selector(itemFired:)];
+
+ if (item->isMerged())
+ return;
+
+ // if the item we're inserting before is merged, skip along until
+ // we find a non-merged real item to insert ahead of.
+ while (beforeItem && beforeItem->isMerged()) {
+ beforeItem = itemOrNull(m_menuItems.indexOf(beforeItem) + 1);
+ }
+
+ if (beforeItem) {
+ Q_ASSERT(!beforeItem->isMerged());
+ NSUInteger nativeIndex = [m_nativeMenu indexOfItem:beforeItem->nsItem()];
+ [m_nativeMenu insertItem: item->nsItem() atIndex: nativeIndex];
+ } else {
+ [m_nativeMenu addItem: item->nsItem()];
}
- if (outItem)
- *outItem = 0;
- return NO;
}
-NSString *qt_mac_removePrivateUnicode(NSString* string)
+void QCocoaMenu::removeMenuItem(QPlatformMenuItem *menuItem)
{
- int len = [string length];
- if (len) {
- QVarLengthArray <unichar, 10> characters(len);
- bool changed = false;
- for (int i = 0; i<len; i++) {
- characters[i] = [string characterAtIndex:i];
- // check if they belong to key codes in private unicode range
- // currently we need to handle only the NSDeleteFunctionKey
- if (characters[i] == NSDeleteFunctionKey) {
- characters[i] = NSDeleteCharacter;
- changed = true;
- }
- }
- if (changed)
- return [NSString stringWithCharacters:characters.data() length:len];
+ QCocoaMenuItem *cocoaItem = static_cast<QCocoaMenuItem *>(menuItem);
+ Q_ASSERT(m_menuItems.contains(cocoaItem));
+ m_menuItems.removeOne(cocoaItem);
+ if (!cocoaItem->isMerged()) {
+ [m_nativeMenu removeItem: cocoaItem->nsItem()];
}
- return string;
}
-- (BOOL)menuHasKeyEquivalent:(NSMenu *)menu forEvent:(NSEvent *)event target:(id *)target action:(SEL *)action
+QCocoaMenuItem *QCocoaMenu::itemOrNull(int index) const
+{
+ if ((index < 0) || (index >= m_menuItems.size()))
+ return 0;
+
+ return m_menuItems.at(index);
+}
+
+void QCocoaMenu::syncMenuItem(QPlatformMenuItem *menuItem)
{
- // Check if the menu actually has a keysequence defined for this key event.
- // If it does, then we will first send the key sequence to the QWidget that has focus
- // since (in Qt's eyes) it needs to a chance at the key event first. If the widget
- // accepts the key event, we then return YES, but set the target and action to be nil,
- // which means that the action should not be triggered, and instead dispatch the event ourselves.
- // In every other case we return NO, which means that Cocoa can do as it pleases
- // (i.e., fire the menu action).
- NSMenuItem *whichItem;
- // Change the private unicode keys to the ones used in setting the "Key Equivalents"
- NSString *characters = qt_mac_removePrivateUnicode([event characters]);
- if ([self hasShortcut:menu
- forKey:characters
- // Interested only in Shift, Cmd, Ctrl & Alt Keys, so ignoring masks like, Caps lock, Num Lock ...
- forModifiers:([event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask | NSCommandKeyMask | NSAlternateKeyMask))
- whichItem:&whichItem]) {
- QWidget *widget = 0;
- QAction *qaction = 0;
- if (whichItem && [whichItem tag]) {
- qaction = reinterpret_cast<QAction *>([whichItem tag]);
+ QCocoaMenuItem *cocoaItem = static_cast<QCocoaMenuItem *>(menuItem);
+ Q_ASSERT(m_menuItems.contains(cocoaItem));
+
+ bool wasMerged = cocoaItem->isMerged();
+ NSMenuItem *oldItem = [m_nativeMenu itemWithTag:(NSInteger) cocoaItem];
+
+ if (cocoaItem->sync() != oldItem) {
+ // native item was changed for some reason
+ if (!wasMerged) {
+ [m_nativeMenu removeItem:oldItem];
}
- if (qApp->activePopupWidget())
- widget = (qApp->activePopupWidget()->focusWidget() ?
- qApp->activePopupWidget()->focusWidget() : qApp->activePopupWidget());
- else if (QApplicationPrivate::focus_widget)
- widget = QApplicationPrivate::focus_widget;
- // If we could not find any receivers, pass it to the active window
- if (!widget)
- widget = qApp->activeWindow();
- if (qaction && widget) {
- int key = qaction->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()) {
- qWarning("Unimplemented: qt_dispatchKeyEvent");
-#if 0
- qt_dispatchKeyEvent(event, widget);
-#endif
- *target = nil;
- *action = nil;
- return YES;
+
+ QCocoaMenuItem* beforeItem = itemOrNull(m_menuItems.indexOf(cocoaItem) + 1);
+ insertNative(cocoaItem, beforeItem);
+ }
+}
+
+void QCocoaMenu::syncSeparatorsCollapsible(bool enable)
+{
+ QCocoaAutoReleasePool pool;
+ if (enable) {
+ bool previousIsSeparator = true; // setting to true kills all the separators placed at the top.
+ NSMenuItem *previousItem = nil;
+
+ NSArray *itemArray = [m_nativeMenu itemArray];
+ for (unsigned int i = 0; i < [itemArray count]; ++i) {
+ NSMenuItem *item = reinterpret_cast<NSMenuItem *>([itemArray objectAtIndex:i]);
+ if ([item isSeparatorItem])
+ [item setHidden:previousIsSeparator];
+
+ if (![item isHidden]) {
+ previousItem = item;
+ previousIsSeparator = ([previousItem isSeparatorItem]);
}
}
+
+ // We now need to check the final item since we don't want any separators at the end of the list.
+ if (previousItem && previousIsSeparator)
+ [previousItem setHidden:YES];
+ } else {
+ foreach (QCocoaMenuItem *item, m_menuItems) {
+ if (!item->isSeparator())
+ continue;
+
+ // sync the visiblity directly
+ item->sync();
+ }
}
- return NO;
}
-- (NSInteger)indexOfItemWithTarget:(id)anObject andAction:(SEL)actionSelector
+void QCocoaMenu::setParentItem(QCocoaMenuItem *item)
{
- NSInteger index = [super indexOfItemWithTarget:anObject andAction:actionSelector];
- static SEL selForOFCP = NSSelectorFromString(@"orderFrontCharacterPalette:");
- if (index == -1 && selForOFCP == actionSelector) {
- // Check if the 'orderFrontCharacterPalette' SEL exists for QNativeCocoaMenuLoader object
- QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = [NSApp QT_MANGLE_NAMESPACE(qt_qcocoamenuLoader)];
- return [super indexOfItemWithTarget:loader andAction:actionSelector];
- }
- return index;
+ Q_UNUSED(item);
}
-@end
+void QCocoaMenu::setEnabled(bool enabled)
+{
+ m_enabled = enabled;
+}
-QT_BEGIN_NAMESPACE
-extern int qt_mac_menus_open_count; // qmenu_mac.mm
+QPlatformMenuItem *QCocoaMenu::menuItemAt(int position) const
+{
+ return m_menuItems.at(position);
+}
-void qt_mac_emit_menuSignals(QMenu *menu, bool show)
+QPlatformMenuItem *QCocoaMenu::menuItemForTag(quintptr tag) const
{
- if (!menu)
- return;
- int delta;
- if (show) {
- emit menu->aboutToShow();
- delta = 1;
- } else {
- emit menu->aboutToHide();
- delta = -1;
+ foreach (QCocoaMenuItem *item, m_menuItems) {
+ if (item->tag() == tag)
+ return item;
}
- qt_mac_menus_open_count += delta;
+
+ return 0;
}
-void qt_mac_clear_status_text(QAction *action)
+QList<QCocoaMenuItem *> QCocoaMenu::merged() const
{
- action->d_func()->showStatusText(0, QString());
+ QList<QCocoaMenuItem *> result;
+ foreach (QCocoaMenuItem *item, m_menuItems) {
+ if (item->menu()) { // recurse into submenus
+ result.append(item->menu()->merged());
+ continue;
+ }
+
+ if (item->isMerged())
+ result.append(item);
+ }
+
+ return result;
}
-void qt_mac_menu_emit_hovered(QMenu *menu, QAction *action)
+void QCocoaMenu::syncModalState(bool modal)
{
- emit menu->hovered(action);
-}
+ if (!m_enabled)
+ modal = true;
+ [m_nativeItem setEnabled:!modal];
-QT_END_NAMESPACE
+ foreach (QCocoaMenuItem *item, m_menuItems) {
+ if (item->menu()) { // recurse into submenus
+ item->menu()->syncModalState(modal);
+ continue;
+ }
+ item->syncModalState(modal);
+ }
+}
diff --git a/src/plugins/platforms/cocoa/qcocoamenubar.h b/src/plugins/platforms/cocoa/qcocoamenubar.h
new file mode 100644
index 0000000000..06e8eb6c9d
--- /dev/null
+++ b/src/plugins/platforms/cocoa/qcocoamenubar.h
@@ -0,0 +1,82 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Copyright (C) 2012 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author James Turner <james.turner@kdab.com>
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the plugins 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$
+**
+****************************************************************************/
+
+#ifndef QCOCOAMENUBAR_H
+#define QCOCOAMENUBAR_H
+
+#include <QtCore/QList>
+#include <qpa/qplatformmenu.h>
+#include "qcocoamenu.h"
+
+@class NSMenu;
+class QCocoaWindow;
+
+class QCocoaMenuBar : public QPlatformMenuBar
+{
+public:
+ QCocoaMenuBar();
+ virtual ~QCocoaMenuBar();
+
+ virtual void insertMenu(QPlatformMenu *menu, QPlatformMenu* before);
+ virtual void removeMenu(QPlatformMenu *menu);
+ virtual void syncMenu(QPlatformMenuItem *menuItem);
+ virtual void handleReparent(QWindow *newParentWindow);
+ virtual QPlatformMenu *menuForTag(quintptr tag) const;
+
+ inline NSMenu *nsMenu() const
+ { return m_nativeMenu; }
+
+ static void updateMenuBarImmediately();
+
+ QList<QCocoaMenuItem*> merged() const;
+private:
+ static QCocoaWindow *findWindowForMenubar();
+ static QCocoaMenuBar *findGlobalMenubar();
+
+ bool shouldDisable(QCocoaWindow *active) const;
+
+ QList<QCocoaMenu*> m_menus;
+ NSMenu *m_nativeMenu;
+ QCocoaWindow *m_window;
+};
+
+#endif
diff --git a/src/plugins/platforms/cocoa/qcocoamenubar.mm b/src/plugins/platforms/cocoa/qcocoamenubar.mm
new file mode 100644
index 0000000000..fc403ba504
--- /dev/null
+++ b/src/plugins/platforms/cocoa/qcocoamenubar.mm
@@ -0,0 +1,254 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author James Turner <james.turner@kdab.com>
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the plugins 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 <Cocoa/Cocoa.h>
+
+#include "qcocoamenubar.h"
+#include "qcocoawindow.h"
+#include "qcocoamenuloader.h"
+#include "qcocoaapplication.h" // for custom application category
+#include "qcocoaautoreleasepool.h"
+
+#include <QtGui/QGuiApplication>
+#include <QtCore/QDebug>
+
+static QList<QCocoaMenuBar*> static_menubars;
+
+static inline QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *getMenuLoader()
+{
+ return [NSApp QT_MANGLE_NAMESPACE(qt_qcocoamenuLoader)];
+}
+
+
+QCocoaMenuBar::QCocoaMenuBar() :
+ m_window(0)
+{
+ static_menubars.append(this);
+
+ m_nativeMenu = [[NSMenu alloc] init];
+#ifdef QT_COCOA_ENABLE_MENU_DEBUG
+ qDebug() << "Construct QCocoaMenuBar" << this << m_nativeMenu;
+#endif
+}
+
+QCocoaMenuBar::~QCocoaMenuBar()
+{
+#ifdef QT_COCOA_ENABLE_MENU_DEBUG
+ qDebug() << "~QCocoaMenuBar" << this;
+#endif
+ [m_nativeMenu release];
+ static_menubars.removeOne(this);
+
+ if (m_window)
+ m_window->setMenubar(0);
+}
+
+void QCocoaMenuBar::insertMenu(QPlatformMenu *platformMenu, QPlatformMenu *before)
+{
+ QCocoaMenu *menu = static_cast<QCocoaMenu *>(platformMenu);
+ QCocoaMenu *beforeMenu = static_cast<QCocoaMenu *>(before);
+#ifdef QT_COCOA_ENABLE_MENU_DEBUG
+ qDebug() << "QCocoaMenuBar" << this << "insertMenu" << menu << "before" << before;
+#endif
+
+ Q_ASSERT(!m_menus.contains(menu));
+ if (beforeMenu) {
+ Q_ASSERT(m_menus.contains(beforeMenu));
+ m_menus.insert(m_menus.indexOf(beforeMenu), menu);
+ NSUInteger nativeIndex = [m_nativeMenu indexOfItem:beforeMenu->nsMenuItem()];
+ [m_nativeMenu insertItem: menu->nsMenuItem() atIndex: nativeIndex];
+ } else {
+ m_menus.append(menu);
+ [m_nativeMenu addItem: menu->nsMenuItem()];
+ }
+
+ [m_nativeMenu setSubmenu: menu->nsMenu() forItem: menu->nsMenuItem()];
+}
+
+void QCocoaMenuBar::removeMenu(QPlatformMenu *platformMenu)
+{
+ QCocoaMenu *menu = static_cast<QCocoaMenu *>(platformMenu);
+ Q_ASSERT(m_menus.contains(menu));
+ m_menus.removeOne(menu);
+
+ NSUInteger realIndex = [m_nativeMenu indexOfItem:menu->nsMenuItem()];
+ [m_nativeMenu removeItemAtIndex: realIndex];
+}
+
+void QCocoaMenuBar::syncMenu(QPlatformMenuItem *menuItem)
+{
+ Q_UNUSED(menuItem);
+}
+
+void QCocoaMenuBar::handleReparent(QWindow *newParentWindow)
+{
+#ifdef QT_COCOA_ENABLE_MENU_DEBUG
+ qDebug() << "QCocoaMenuBar" << this << "handleReparent" << newParentWindow;
+#endif
+
+ if (m_window)
+ m_window->setMenubar(NULL);
+
+ if (newParentWindow == NULL) {
+ m_window = NULL;
+ } else {
+ m_window = static_cast<QCocoaWindow*>(newParentWindow->handle());
+ m_window->setMenubar(this);
+ }
+
+ updateMenuBarImmediately();
+}
+
+QCocoaWindow *QCocoaMenuBar::findWindowForMenubar()
+{
+ if (qApp->focusWindow())
+ return static_cast<QCocoaWindow*>(qApp->focusWindow()->handle());
+
+ return NULL;
+}
+
+QCocoaMenuBar *QCocoaMenuBar::findGlobalMenubar()
+{
+ foreach (QCocoaMenuBar *mb, static_menubars) {
+ if (mb->m_window == NULL)
+ return mb;
+ }
+
+ return NULL;
+}
+
+void QCocoaMenuBar::updateMenuBarImmediately()
+{
+ QCocoaAutoReleasePool pool;
+ QCocoaMenuBar *mb = findGlobalMenubar();
+ QCocoaWindow *cw = findWindowForMenubar();
+ if (cw && cw->menubar())
+ mb = cw->menubar();
+
+ if (!mb)
+ return;
+
+#ifdef QT_COCOA_ENABLE_MENU_DEBUG
+ qDebug() << "QCocoaMenuBar" << "updateMenuBarImmediately" << cw;
+#endif
+ bool disableForModal = mb->shouldDisable(cw);
+ // force a sync?
+ foreach (QCocoaMenu *m, mb->m_menus) {
+ mb->syncMenu(m);
+ m->syncModalState(disableForModal);
+ }
+
+ QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
+ [loader ensureAppMenuInMenu:mb->nsMenu()];
+
+ NSMutableSet *mergedItems = [[NSMutableSet setWithCapacity:0] retain];
+ foreach (QCocoaMenuItem *m, mb->merged()) {
+ [mergedItems addObject:m->nsItem()];
+ m->syncMerged();
+ }
+
+ // hide+disable all mergeable items we're not currently using
+ for (NSMenuItem *mergeable in [loader mergeable]) {
+ if (![mergedItems containsObject:mergeable]) {
+ [mergeable setHidden:YES];
+ [mergeable setEnabled:NO];
+ }
+ }
+
+ [mergedItems release];
+ [NSApp setMainMenu:mb->nsMenu()];
+ [loader qtTranslateApplicationMenu];
+}
+
+QList<QCocoaMenuItem*> QCocoaMenuBar::merged() const
+{
+ QList<QCocoaMenuItem*> r;
+ foreach (QCocoaMenu* menu, m_menus)
+ r.append(menu->merged());
+
+ return r;
+}
+
+bool QCocoaMenuBar::shouldDisable(QCocoaWindow *active) const
+{
+ if (active && (active->window()->windowModality() == Qt::NonModal))
+ return false;
+
+ if (m_window == active) {
+ // modal window owns us, we should be enabled!
+ return false;
+ }
+
+ QWindowList topWindows(qApp->topLevelWindows());
+ // 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.
+ foreach (QWindow *w, topWindows) {
+ if (w->isVisible() && w->windowModality() == Qt::ApplicationModal) {
+ // check for other visible windows
+ foreach (QWindow *other, topWindows) {
+ if ((w != other) && (other->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;
+ }
+ }
+
+ return true;
+}
+
+QPlatformMenu *QCocoaMenuBar::menuForTag(quintptr tag) const
+{
+ foreach (QCocoaMenu *menu, m_menus) {
+ if (menu->tag() == tag)
+ return menu;
+ }
+
+ return 0;
+}
diff --git a/src/plugins/platforms/cocoa/qcocoamenuitem.h b/src/plugins/platforms/cocoa/qcocoamenuitem.h
new file mode 100644
index 0000000000..4a063d5965
--- /dev/null
+++ b/src/plugins/platforms/cocoa/qcocoamenuitem.h
@@ -0,0 +1,117 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Copyright (C) 2012 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author James Turner <james.turner@kdab.com>
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the plugins 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$
+**
+****************************************************************************/
+
+#ifndef QCOCOAMENUITEM_H
+#define QCOCOAMENUITEM_H
+
+#include <qpa/qplatformmenu.h>
+#include <QtGui/QImage>
+
+//#define QT_COCOA_ENABLE_MENU_DEBUG
+
+@class NSMenuItem;
+@class NSMenu;
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+class QCocoaMenu;
+
+class QCocoaMenuItem : public QPlatformMenuItem
+{
+public:
+ QCocoaMenuItem();
+ virtual ~QCocoaMenuItem();
+
+ inline virtual void setTag(quintptr tag)
+ { m_tag = tag; }
+ inline virtual quintptr tag() const
+ { return m_tag; }
+
+ void setText(const QString &text);
+ void setIcon(const QImage &icon);
+ void setMenu(QPlatformMenu *menu);
+ void setVisible(bool isVisible);
+ void setIsSeparator(bool isSeparator);
+ void setFont(const QFont &font);
+ void setRole(MenuRole role);
+ void setShortcut(const QKeySequence& shortcut);
+ void setChecked(bool isChecked);
+ void setEnabled(bool isEnabled);
+
+ inline QString text() const { return m_text; }
+ inline NSMenuItem * nsItem() { return m_native; }
+ NSMenuItem *sync();
+
+ void syncMerged();
+ void syncModalState(bool modal);
+
+ inline bool isMerged() const { return m_merged; }
+ inline bool isEnabled() const { return m_enabled; }
+ inline bool isSeparator() const { return m_isSeparator; }
+
+ QCocoaMenu *menu() const { return m_menu; }
+private:
+ QString mergeText();
+ QKeySequence mergeAccel();
+
+ NSMenuItem *m_native;
+ QString m_text;
+ QImage m_icon;
+ QCocoaMenu *m_menu;
+ bool m_isVisible;
+ bool m_enabled;
+ bool m_isSeparator;
+ QFont m_font;
+ MenuRole m_role;
+ QKeySequence m_shortcut;
+ bool m_checked;
+ bool m_merged;
+ quintptr m_tag;
+};
+
+QT_END_HEADER
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/plugins/platforms/cocoa/qcocoamenuitem.mm b/src/plugins/platforms/cocoa/qcocoamenuitem.mm
new file mode 100644
index 0000000000..150e94ab07
--- /dev/null
+++ b/src/plugins/platforms/cocoa/qcocoamenuitem.mm
@@ -0,0 +1,358 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author James Turner <james.turner@kdab.com>
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the plugins 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 "qcocoamenuitem.h"
+
+#include "qcocoamenu.h"
+#include "qcocoahelpers.h"
+#include "qcocoaautoreleasepool.h"
+#include "qt_mac_p.h"
+#include "qcocoaapplication.h" // for custom application category
+#include "qcocoamenuloader.h"
+
+#include <QtCore/QDebug>
+
+static inline QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *getMenuLoader()
+{
+ return [NSApp QT_MANGLE_NAMESPACE(qt_qcocoamenuLoader)];
+}
+
+
+static quint32 constructModifierMask(quint32 accel_key)
+{
+ quint32 ret = 0;
+ const bool dontSwap = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta);
+ 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;
+ return ret;
+}
+
+// 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));
+ QChar cocoa_key = qt_mac_qtKey2CocoaKey(Qt::Key(accel_key));
+ if (cocoa_key.isNull())
+ cocoa_key = QChar(accel_key).toLower().unicode();
+ return [NSString stringWithCharacters:&cocoa_key.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]);
+}
+
+QCocoaMenuItem::QCocoaMenuItem() :
+ m_native(NULL),
+ m_menu(NULL),
+ m_isVisible(true),
+ m_enabled(true),
+ m_isSeparator(false),
+ m_role(NoRole),
+ m_checked(false),
+ m_merged(false),
+ m_tag(0)
+{
+}
+
+QCocoaMenuItem::~QCocoaMenuItem()
+{
+ if (m_merged) {
+ [m_native setHidden:YES];
+ }
+
+ [m_native release];
+}
+
+void QCocoaMenuItem::setText(const QString &text)
+{
+ m_text = qt_mac_removeAmpersandEscapes(text);
+}
+
+void QCocoaMenuItem::setIcon(const QImage &icon)
+{
+ m_icon = icon;
+}
+
+void QCocoaMenuItem::setMenu(QPlatformMenu *menu)
+{
+ if (menu == m_menu)
+ return;
+
+ QCocoaAutoReleasePool pool;
+ m_menu = static_cast<QCocoaMenu *>(menu);
+ if (m_menu) {
+ m_menu->setParentItem(this);
+ } else {
+ // we previously had a menu, but no longer
+ // clear out our item so the nexy sync() call builds a new one
+ [m_native release];
+ m_native = nil;
+ }
+}
+
+void QCocoaMenuItem::setVisible(bool isVisible)
+{
+ m_isVisible = isVisible;
+}
+
+void QCocoaMenuItem::setIsSeparator(bool isSeparator)
+{
+ m_isSeparator = isSeparator;
+}
+
+void QCocoaMenuItem::setFont(const QFont &font)
+{
+ m_font = font;
+}
+
+void QCocoaMenuItem::setRole(MenuRole role)
+{
+ m_role = role;
+}
+
+void QCocoaMenuItem::setShortcut(const QKeySequence& shortcut)
+{
+ m_shortcut = shortcut;
+}
+
+void QCocoaMenuItem::setChecked(bool isChecked)
+{
+ m_checked = isChecked;
+}
+
+void QCocoaMenuItem::setEnabled(bool enabled)
+{
+ m_enabled = enabled;
+}
+
+NSMenuItem *QCocoaMenuItem::sync()
+{
+ if (m_isSeparator != [m_native isSeparatorItem]) {
+ [m_native release];
+ if (m_isSeparator) {
+ m_native = [[NSMenuItem separatorItem] retain];
+ [m_native setTag:reinterpret_cast<NSInteger>(this)];
+ } else
+ m_native = nil;
+ }
+
+ if (m_menu) {
+ if (m_native != m_menu->nsMenuItem()) {
+ [m_native release];
+ m_native = [m_menu->nsMenuItem() retain];
+ [m_native setTag:reinterpret_cast<NSInteger>(this)];
+ }
+ }
+
+ if ((m_role != NoRole) || m_merged) {
+ NSMenuItem *mergeItem = nil;
+ QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
+ switch (m_role) {
+ case ApplicationSpecificRole:
+ mergeItem = [loader appSpecificMenuItem];
+ break;
+ case AboutRole:
+ mergeItem = [loader aboutMenuItem];
+ break;
+ case AboutQtRole:
+ mergeItem = [loader aboutQtMenuItem];
+ break;
+ case QuitRole:
+ mergeItem = [loader quitMenuItem];
+ break;
+ case PreferencesRole:
+ mergeItem = [loader preferencesMenuItem];
+ break;
+ case TextHeuristicRole: {
+ QString aboutString = tr("About").toLower();
+
+ if (m_text.startsWith(aboutString) || m_text.endsWith(aboutString)) {
+ if (m_text.indexOf(QRegExp(QString::fromLatin1("qt$"), Qt::CaseInsensitive)) == -1)
+ mergeItem = [loader aboutMenuItem];
+ else
+ mergeItem = [loader aboutQtMenuItem];
+
+ m_merged = true;
+ } else if (m_text.startsWith(tr("Config").toLower())
+ || m_text.startsWith(tr("Preference").toLower())
+ || m_text.startsWith(tr("Options").toLower())
+ || m_text.startsWith(tr("Setting").toLower())
+ || m_text.startsWith(tr("Setup").toLower())) {
+ mergeItem = [loader preferencesMenuItem];
+ } else if (m_text.startsWith(tr("Quit").toLower())
+ || m_text.startsWith(tr("Exit").toLower())) {
+ mergeItem = [loader quitMenuItem];
+ }
+ break;
+ }
+
+ default:
+ qWarning() << Q_FUNC_INFO << "unsupported role" << (int) m_role;
+ }
+
+ if (mergeItem) {
+ m_merged = true;
+ [m_native release];
+ m_native = mergeItem;
+ [m_native retain]; // balance out release!
+ [m_native setTag:reinterpret_cast<NSInteger>(this)];
+ } else if (m_merged) {
+ // was previously merged, but no longer
+ [m_native release];
+ m_native = nil; // create item below
+ m_merged = false;
+ }
+ }
+
+ if (!m_native) {
+ m_native = [[NSMenuItem alloc] initWithTitle:QCFString::toNSString(m_text)
+ action:nil
+ keyEquivalent:@""];
+ [m_native retain];
+ [m_native setTag:reinterpret_cast<NSInteger>(this)];
+ }
+
+// [m_native setHidden:YES];
+// [m_native setHidden:NO];
+ [m_native setHidden: !m_isVisible];
+
+ QString text = m_text;
+ QKeySequence accel = m_shortcut;
+
+ {
+ int st = text.lastIndexOf(QLatin1Char('\t'));
+ if (st != -1) {
+ accel = QKeySequence(text.right(text.length()-(st+1)));
+ text.remove(st, text.length()-st);
+ }
+ }
+
+ text = mergeText();
+ accel = mergeAccel();
+
+ // 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);
+ // Cocoa Font and title
+ if (m_font.resolve()) {
+ NSFont *customMenuFont = [NSFont fontWithName:QCFString::toNSString(m_font.family())
+ size:m_font.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:QCFString::toNSString(finalString)
+ attributes:attributes] autorelease];
+ [m_native setAttributedTitle: str];
+ } else {
+ [m_native setTitle: QCFString::toNSString(finalString)];
+ }
+
+ if (accel.count() == 1) {
+ [m_native setKeyEquivalent:keySequenceToKeyEqivalent(accel)];
+ [m_native setKeyEquivalentModifierMask:keySequenceModifierMask(accel)];
+ } else {
+ [m_native setKeyEquivalent:@""];
+ [m_native setKeyEquivalentModifierMask:NSCommandKeyMask];
+ }
+
+ if (!m_icon.isNull()) {
+ NSImage *img = qt_mac_cgimage_to_nsimage(qt_mac_image_to_cgimage(m_icon));
+ [m_native setImage: img];
+ }
+
+ [m_native setState:m_checked ? NSOnState : NSOffState];
+ return m_native;
+}
+
+QString QCocoaMenuItem::mergeText()
+{
+ extern QString qt_mac_applicationmenu_string(int type);
+ QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
+ if (m_native == [loader aboutMenuItem]) {
+ return qt_mac_applicationmenu_string(6).arg(qt_mac_applicationName());
+ } else if (m_native== [loader aboutQtMenuItem]) {
+ if (m_text == QString("About Qt"))
+ return tr("About Qt");
+ else
+ return m_text;
+ } else if (m_native == [loader preferencesMenuItem]) {
+ return qt_mac_applicationmenu_string(4);
+ } else if (m_native == [loader quitMenuItem]) {
+ return qt_mac_applicationmenu_string(5).arg(qt_mac_applicationName());
+ }
+ return m_text;
+}
+
+QKeySequence QCocoaMenuItem::mergeAccel()
+{
+ QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
+ if (m_native == [loader preferencesMenuItem])
+ return QKeySequence(QKeySequence::Preferences);
+ else if (m_native == [loader quitMenuItem])
+ return QKeySequence(QKeySequence::Quit);
+
+ return m_shortcut;
+}
+
+void QCocoaMenuItem::syncMerged()
+{
+ Q_ASSERT(m_merged);
+ [m_native setTag:reinterpret_cast<NSInteger>(this)];
+ [m_native setHidden: !m_isVisible];
+}
+
+void QCocoaMenuItem::syncModalState(bool modal)
+{
+ if (modal)
+ [m_native setEnabled:NO];
+ else
+ [m_native setEnabled:m_enabled];
+}
diff --git a/src/plugins/platforms/cocoa/qcocoamenuloader.h b/src/plugins/platforms/cocoa/qcocoamenuloader.h
index b0d61fa541..fe9d0e0af5 100644
--- a/src/plugins/platforms/cocoa/qcocoamenuloader.h
+++ b/src/plugins/platforms/cocoa/qcocoamenuloader.h
@@ -85,10 +85,11 @@
- (IBAction)hideOtherApplications:(id)sender;
- (IBAction)unhideAllApplications:(id)sender;
- (IBAction)hide:(id)sender;
-- (IBAction)qtDispatcherToQAction:(id)sender;
-- (void)qtUpdateMenubar;
+- (IBAction)qtDispatcherToQPAMenuItem:(id)sender;
- (void)orderFrontCharacterPalette:(id)sender;
- (BOOL)validateMenuItem:(NSMenuItem*)menuItem;
+- (void)qtTranslateApplicationMenu;
+- (NSArray *)mergeable;
@end
void qt_mac_loadMenuNib(QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *qtMenuLoader);
diff --git a/src/plugins/platforms/cocoa/qcocoamenuloader.mm b/src/plugins/platforms/cocoa/qcocoamenuloader.mm
index 7854a83253..45ac878ea4 100644
--- a/src/plugins/platforms/cocoa/qcocoamenuloader.mm
+++ b/src/plugins/platforms/cocoa/qcocoamenuloader.mm
@@ -42,6 +42,8 @@
#include "qcocoamenuloader.h"
#include "qcocoahelpers.h"
+#include "qcocoamenubar.h"
+#include "qcocoamenuitem.h"
#include <QtCore/private/qcore_mac_p.h>
#include <QtCore/qcoreapplication.h>
@@ -52,14 +54,33 @@
QT_FORWARD_DECLARE_CLASS(QCFString)
QT_FORWARD_DECLARE_CLASS(QString)
-#ifndef QT_NO_TRANSLATION
- QT_BEGIN_NAMESPACE
- extern QString qt_mac_applicationmenu_string(int type);
- QT_END_NAMESPACE
-#endif
QT_USE_NAMESPACE
+#ifndef QT_NO_TRANSLATION
+static const char *application_menu_strings[] = {
+ QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","Services"),
+ QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","Hide %1"),
+ QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","Hide Others"),
+ QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","Show All"),
+ QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","Preferences..."),
+ QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","Quit %1"),
+ QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","About %1")
+ };
+
+QString qt_mac_applicationmenu_string(int type)
+{
+ QString menuString = QString::fromLatin1(application_menu_strings[type]);
+ QString translated = qApp->translate("QMenuBar", application_menu_strings[type]);
+ if (translated != menuString) {
+ return translated;
+ } else {
+ return qApp->translate("MAC_APPLICATION_MENU",
+ application_menu_strings[type]);
+ }
+}
+#endif
+
/*
Loads and instantiates the main app menu from the menu nib file(s).
@@ -127,6 +148,11 @@ void qt_mac_loadMenuNib(QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *qtMenuLoader)
// They should get synced back in.
[preferencesItem setEnabled:NO];
[preferencesItem setHidden:YES];
+
+ // should set this in the NIB
+ [preferencesItem setTarget: self];
+ [preferencesItem setAction: @selector(qtDispatcherToQPAMenuItem:)];
+
[aboutItem setEnabled:NO];
[aboutItem setHidden:YES];
}
@@ -269,19 +295,10 @@ void qt_mac_loadMenuNib(QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *qtMenuLoader)
[NSApp hide:sender];
}
-- (void)qtUpdateMenubar
-{
-#if 0
- QCocoaMenuBar::macUpdateMenuBarImmediatly();
-#endif
-}
-
- (void)qtTranslateApplicationMenu
{
- qDebug() << "qtTranslateApplicationMenu";
-#if 0
- //#ifndef QT_NO_TRANSLATION
+#ifndef QT_NO_TRANSLATION
[servicesItem setTitle: QCFString::toNSString(qt_mac_applicationmenu_string(0))];
[hideItem setTitle: QCFString::toNSString(qt_mac_applicationmenu_string(1).arg(qt_mac_applicationName()))];
[hideAllOthersItem setTitle: QCFString::toNSString(qt_mac_applicationmenu_string(2))];
@@ -292,27 +309,27 @@ void qt_mac_loadMenuNib(QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *qtMenuLoader)
#endif
}
-- (IBAction)qtDispatcherToQAction:(id)sender
+- (IBAction)qtDispatcherToQPAMenuItem:(id)sender
{
-#if 0
- //
- //QScopedLoopLevelCounter loopLevelCounter(QApplicationPrivate::instance()->threadData);
NSMenuItem *item = static_cast<NSMenuItem *>(sender);
- if (QAction *action = reinterpret_cast<QAction *>([item tag])) {
- action->trigger();
- } else if (item == quitItem) {
+ if (item == quitItem) {
// We got here because someone was once the quitItem, but it has been
// abandoned (e.g., the menubar was deleted). In the meantime, just do
// normal QApplication::quit().
qApp->quit();
+ return;
+ }
+
+ if ([item tag]) {
+ QCocoaMenuItem *cocoaItem = reinterpret_cast<QCocoaMenuItem *>([item tag]);
+ cocoaItem->activated();
}
-#endif
}
- - (void)orderFrontCharacterPalette:(id)sender
- {
- [NSApp orderFrontCharacterPalette:sender];
- }
+- (void)orderFrontCharacterPalette:(id)sender
+{
+ [NSApp orderFrontCharacterPalette:sender];
+}
- (BOOL)validateMenuItem:(NSMenuItem*)menuItem
{
@@ -320,9 +337,18 @@ void qt_mac_loadMenuNib(QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *qtMenuLoader)
|| [menuItem action] == @selector(hideOtherApplications:)
|| [menuItem action] == @selector(unhideAllApplications:)) {
return [NSApp validateMenuItem:menuItem];
+ } else if ([menuItem tag]) {
+ QCocoaMenuItem *cocoaItem = reinterpret_cast<QCocoaMenuItem *>([menuItem tag]);
+ return cocoaItem->isEnabled();
} else {
return [menuItem isEnabled];
}
}
+- (NSArray*) mergeable
+{
+ // don't include the quitItem here, since we want it always visible and enabled regardless
+ return [NSArray arrayWithObjects:preferencesItem, aboutItem, aboutQtItem, lastAppSpecificItem, nil];
+}
+
@end
diff --git a/src/plugins/platforms/cocoa/qcocoanativeinterface.h b/src/plugins/platforms/cocoa/qcocoanativeinterface.h
index 0346eafec4..fd0b10358f 100644
--- a/src/plugins/platforms/cocoa/qcocoanativeinterface.h
+++ b/src/plugins/platforms/cocoa/qcocoanativeinterface.h
@@ -53,8 +53,13 @@ class QCocoaNativeInterface : public QPlatformNativeInterface
{
Q_OBJECT
public:
+ QCocoaNativeInterface();
+
void *nativeResourceForWindow(const QByteArray &resourceString, QWindow *window);
+public Q_SLOTS:
+ void onAppFocusWindowChanged(QWindow *window);
+
private:
/*
"Virtual" function to create the platform printer support
diff --git a/src/plugins/platforms/cocoa/qcocoanativeinterface.mm b/src/plugins/platforms/cocoa/qcocoanativeinterface.mm
index df9ae23606..06d1f9ee0f 100644
--- a/src/plugins/platforms/cocoa/qcocoanativeinterface.mm
+++ b/src/plugins/platforms/cocoa/qcocoanativeinterface.mm
@@ -43,6 +43,7 @@
#include "qcocoaglcontext.h"
#include "qcocoawindow.h"
#include "qcocoaprintersupport.h"
+#include "qcocoamenubar.h"
#include <qbytearray.h>
#include <qwindow.h>
@@ -50,12 +51,17 @@
#include "qsurfaceformat.h"
#include <qpa/qplatformopenglcontext.h>
#include "qopenglcontext.h"
+#include "qguiapplication.h"
#include <qdebug.h>
#include "qprintengine_mac_p.h"
QT_BEGIN_NAMESPACE
+QCocoaNativeInterface::QCocoaNativeInterface()
+{
+}
+
void *QCocoaNativeInterface::nativeResourceForWindow(const QByteArray &resourceString, QWindow *window)
{
if (!window->handle()) {
@@ -84,4 +90,9 @@ void *QCocoaNativeInterface::NSPrintInfoForPrintEngine(QPrintEngine *printEngine
return macPrintEngine->d_func()->printInfo;
}
+void QCocoaNativeInterface::onAppFocusWindowChanged(QWindow *window)
+{
+ QCocoaMenuBar::updateMenuBarImmediately();
+}
+
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/cocoa/qcocoatheme.h b/src/plugins/platforms/cocoa/qcocoatheme.h
index d126050f98..3c071d44c3 100644
--- a/src/plugins/platforms/cocoa/qcocoatheme.h
+++ b/src/plugins/platforms/cocoa/qcocoatheme.h
@@ -54,6 +54,10 @@ public:
QCocoaTheme();
~QCocoaTheme();
+ virtual QPlatformMenuItem* createPlatformMenuItem() const;
+ virtual QPlatformMenu* createPlatformMenu() const;
+ virtual QPlatformMenuBar* createPlatformMenuBar() const;
+
bool usePlatformNativeDialog(DialogType dialogType) const;
QPlatformDialogHelper *createPlatformDialogHelper(DialogType dialogType) const;
diff --git a/src/plugins/platforms/cocoa/qcocoatheme.mm b/src/plugins/platforms/cocoa/qcocoatheme.mm
index cfb146ad45..2f630c7763 100644
--- a/src/plugins/platforms/cocoa/qcocoatheme.mm
+++ b/src/plugins/platforms/cocoa/qcocoatheme.mm
@@ -41,12 +41,19 @@
#include "qcocoatheme.h"
-#include <QVariant>
+#include <QtCore/QVariant>
#include "qcocoacolordialoghelper.h"
#include "qcocoafiledialoghelper.h"
#include "qcocoafontdialoghelper.h"
#include "qcocoasystemsettings.h"
+#include "qcocoamenuitem.h"
+#include "qcocoamenu.h"
+#include "qcocoamenubar.h"
+
+#include <QtGui/private/qguiapplication_p.h>
+#include <QtGui/QPlatformIntegration>
+#include <QtGui/QPlatformNativeInterface>
QT_BEGIN_NAMESPACE
@@ -133,4 +140,27 @@ QVariant QCocoaTheme::themeHint(ThemeHint hint) const
return QPlatformTheme::themeHint(hint);
}
+QPlatformMenuItem *QCocoaTheme::createPlatformMenuItem() const
+{
+ return new QCocoaMenuItem();
+}
+
+QPlatformMenu *QCocoaTheme::createPlatformMenu() const
+{
+ return new QCocoaMenu();
+}
+
+QPlatformMenuBar *QCocoaTheme::createPlatformMenuBar() const
+{
+ static bool haveMenubar = false;
+ if (!haveMenubar) {
+ haveMenubar = true;
+ QObject::connect(qGuiApp, SIGNAL(focusWindowChanged(QWindow*)),
+ QGuiApplicationPrivate::platformIntegration()->nativeInterface(),
+ SLOT(onAppFocusWindowChanged(QWindow*)));
+ }
+
+ return new QCocoaMenuBar();
+}
+
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/cocoa/qcocoawindow.h b/src/plugins/platforms/cocoa/qcocoawindow.h
index f2d6ac67bb..debc8c42a2 100644
--- a/src/plugins/platforms/cocoa/qcocoawindow.h
+++ b/src/plugins/platforms/cocoa/qcocoawindow.h
@@ -85,6 +85,8 @@ QT_BEGIN_NAMESPACE
// See the qt_on_cocoa manual tests for a working example, located
// in tests/manual/cocoa at the time of writing.
+class QCocoaMenuBar;
+
class QCocoaWindow : public QPlatformWindow
{
public:
@@ -120,6 +122,8 @@ public:
bool setWindowModified(bool modified) Q_DECL_OVERRIDE;
+ void setMenubar(QCocoaMenuBar *mb);
+ QCocoaMenuBar *menubar() const;
protected:
// NSWindow handling. The QCocoaWindow/QNSView can either be displayed
// in an existing NSWindow or in one created by Qt.
@@ -145,6 +149,7 @@ public: // for QNSView
bool m_inConstructor;
QCocoaGLContext *m_glContext;
+ QCocoaMenuBar *m_menubar;
bool m_hasModalSession;
};
diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm
index 5480b32083..880292b24a 100644
--- a/src/plugins/platforms/cocoa/qcocoawindow.mm
+++ b/src/plugins/platforms/cocoa/qcocoawindow.mm
@@ -100,8 +100,12 @@ QCocoaWindow::QCocoaWindow(QWindow *tlw)
, m_synchedWindowState(Qt::WindowActive)
, m_inConstructor(true)
, m_glContext(0)
+ , m_menubar(0)
, m_hasModalSession(false)
{
+#ifdef QT_COCOA_ENABLE_WINDOW_DEBUG
+ qDebug() << "QCocoaWindow::QCocoaWindow" << this;
+#endif
QCocoaAutoReleasePool pool;
m_contentView = [[QNSView alloc] initWithQWindow:tlw platformWindow:this];
@@ -577,3 +581,13 @@ bool QCocoaWindow::setWindowModified(bool modified)
[m_nsWindow setDocumentEdited:(modified?YES:NO)];
return true;
}
+
+void QCocoaWindow::setMenubar(QCocoaMenuBar *mb)
+{
+ m_menubar = mb;
+}
+
+QCocoaMenuBar *QCocoaWindow::menubar() const
+{
+ return m_menubar;
+}
diff --git a/src/plugins/platforms/cocoa/qt_menu.nib/classes.nib b/src/plugins/platforms/cocoa/qt_menu.nib/classes.nib
index 0031e0e4e5..78941153c2 100644
--- a/src/plugins/platforms/cocoa/qt_menu.nib/classes.nib
+++ b/src/plugins/platforms/cocoa/qt_menu.nib/classes.nib
@@ -13,7 +13,7 @@
<string>id</string>
<key>orderFrontStandardAboutPanel</key>
<string>id</string>
- <key>qtDispatcherToQAction</key>
+ <key>qtDispatcherToQPAMenuItem</key>
<string>id</string>
<key>terminate</key>
<string>id</string>
diff --git a/tests/manual/cocoa/menus/main.cpp b/tests/manual/cocoa/menus/main.cpp
new file mode 100644
index 0000000000..d695c6c138
--- /dev/null
+++ b/tests/manual/cocoa/menus/main.cpp
@@ -0,0 +1,201 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 KDAB
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the plugins 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 <QtGui>
+#include <QtWidgets>
+
+
+class Responder : public QObject
+{
+ Q_OBJECT
+
+public:
+ Responder(QObject *pr) :
+ QObject(pr)
+ {
+ }
+
+public slots:
+
+
+ void toggleChecked(bool b)
+ {
+ QAction *a = qobject_cast<QAction *>(sender());
+ }
+
+ void showModalDialog()
+ {
+ QMessageBox::information(NULL, "Something", "Something happened. Modally.");
+ }
+
+ void doPreferences()
+ {
+ qDebug() << "show preferences";
+ }
+
+ void aboutToShowSubmenu()
+ {
+ QMenu* m = (QMenu*) sender();
+ qDebug() << "will show" << m;
+
+ m->clear();
+
+ for (int i=0; i<10; ++i) {
+ m->addAction(QString("Recent File %1").arg(i + 1));
+ }
+ }
+};
+
+void createWindow1()
+{
+
+ QMainWindow *window = new QMainWindow;
+ QMenu *menu = new QMenu("TestMenu", window);
+
+ window->menuBar()->addMenu(menu);
+
+ Responder *r = new Responder(window);
+
+ QAction *a = menu->addAction("TestMenuItem1");
+ a->setShortcut( Qt::Key_A | Qt::SHIFT | Qt::CTRL );
+ QObject::connect(a, SIGNAL(triggered()),
+ r, SLOT(showModalDialog()));
+
+
+ menu->addAction("T&estMenuItem2");
+ a = menu->addAction("Preferences");
+ a->setMenuRole(QAction::PreferencesRole);
+ QObject::connect(a, SIGNAL(triggered()),
+ r, SLOT(doPreferences()));
+
+ a = menu->addAction("TestMenuItem4");
+ a->setShortcut( Qt::Key_W | Qt::CTRL);
+
+ QMenu *menu2 = new QMenu("SecondMenu", window);
+ window->menuBar()->addMenu(menu2);
+
+ menu2->addAction("Yellow");
+ a = menu2->addAction("Mau&ve");
+
+ QFont f;
+ f.setPointSize(9);
+ a->setFont(f);
+
+ menu2->addAction("Taupe");
+
+ QMenu *submenu1 = new QMenu("Submenu", window);
+ submenu1->addAction("Sub Item 1");
+ submenu1->addAction("Sub Item 2");
+ submenu1->addAction("Sub Item 3");
+ menu2->addMenu(submenu1);
+
+ QMenu *submenu2 = new QMenu("Deeper", window);
+ submenu2->addAction("Sub Sub Item 1");
+ submenu2->addAction("Sub Sub Item 2");
+ submenu2->addAction("Sub Sub Item 3");
+ submenu1->addMenu(submenu2);
+
+ QMenu *menu3 = new QMenu("A Third Menu", window);
+
+ menu3->addAction("Eins");
+
+ QMenu *submenu3 = new QMenu("Dynamic", window);
+ QObject::connect(submenu3, SIGNAL(aboutToShow()), r, SLOT(aboutToShowSubmenu()));
+ menu3->addMenu(submenu3);
+
+ a = menu3->addAction("Zwei");
+ a->setShortcut( Qt::Key_3 | Qt::ALT);
+ a = menu3->addAction("About Drei...");
+ a->setMenuRole(QAction::AboutRole);
+
+ window->menuBar()->addMenu(menu3);
+
+ QAction *checkableAction = new QAction("Thing Enabled", window);
+ checkableAction->setCheckable(true);
+ checkableAction->setChecked(true);
+ QObject::connect(checkableAction, SIGNAL(triggered(bool)),
+ r, SLOT(toggleChecked(bool)));
+
+ menu2->addAction(checkableAction);
+
+ window->show();
+
+}
+
+void createWindow2()
+{
+ QMainWindow *window = new QMainWindow;
+ QMenu *menu = new QMenu("Nuts", window);
+
+ window->menuBar()->addMenu(menu);
+
+ menu->addAction("Peanuts");
+ menu->addAction("Walnuts");
+
+ QMenu *menu2 = new QMenu("Colours", window);
+ window->menuBar()->addMenu(menu2);
+
+ menu2->addAction("Pink");
+ menu2->addAction("Yellow");
+ menu2->addAction("Grape");
+
+ QMenu *menu3 = new QMenu("Edit", window);
+ menu3->addAction("Cut");
+ menu3->addAction("Copy boring way");
+ menu3->addAction("Copy awesomely");
+ menu3->addAction("Paste");
+
+ window->menuBar()->addMenu(menu3);
+ window->show();
+}
+
+int main(int argc, char **argv) {
+ QApplication app(argc, argv);
+
+ app.setApplicationName("Banana");
+
+ createWindow1();
+ createWindow2();
+
+ return app.exec();
+}
+
+#include "main.moc"
diff --git a/tests/manual/cocoa/menus/menus.pro b/tests/manual/cocoa/menus/menus.pro
new file mode 100644
index 0000000000..0ccb25feb9
--- /dev/null
+++ b/tests/manual/cocoa/menus/menus.pro
@@ -0,0 +1,5 @@
+TEMPLATE = app
+
+SOURCES += main.cpp
+QT += gui widgets
+CONFIG -=app_bundle