diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/plugins/platforms/cocoa/cocoa.pro | 2 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoamenu.h | 7 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoamenu.mm | 222 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoansmenu.h | 84 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoansmenu.mm | 276 |
5 files changed, 373 insertions, 218 deletions
diff --git a/src/plugins/platforms/cocoa/cocoa.pro b/src/plugins/platforms/cocoa/cocoa.pro index 5354bdafbc..2694cb3607 100644 --- a/src/plugins/platforms/cocoa/cocoa.pro +++ b/src/plugins/platforms/cocoa/cocoa.pro @@ -14,6 +14,7 @@ SOURCES += main.mm \ qcocoaeventdispatcher.mm \ qcocoaapplicationdelegate.mm \ qcocoaapplication.mm \ + qcocoansmenu.mm \ qcocoamenu.mm \ qcocoamenuitem.mm \ qcocoamenubar.mm \ @@ -47,6 +48,7 @@ HEADERS += qcocoaintegration.h \ qcocoaeventdispatcher.h \ qcocoaapplicationdelegate.h \ qcocoaapplication.h \ + qcocoansmenu.h \ qcocoamenu.h \ qcocoamenuitem.h \ qcocoamenubar.h \ diff --git a/src/plugins/platforms/cocoa/qcocoamenu.h b/src/plugins/platforms/cocoa/qcocoamenu.h index f5fa93cbb5..2b4cf0ef98 100644 --- a/src/plugins/platforms/cocoa/qcocoamenu.h +++ b/src/plugins/platforms/cocoa/qcocoamenu.h @@ -45,6 +45,8 @@ #include <qpa/qplatformmenu.h> #include "qcocoamenuitem.h" +Q_FORWARD_DECLARE_OBJC_CLASS(QT_MANGLE_NAMESPACE(QCocoaNSMenu)); + QT_BEGIN_NAMESPACE class QCocoaMenuBar; @@ -74,8 +76,7 @@ public: void setMinimumWidth(int width) override; void setFont(const QFont &font) override; - inline NSMenu *nsMenu() const - { return m_nativeMenu; } + NSMenu *nsMenu() const; inline bool isVisible() const { return m_visible; } @@ -101,7 +102,7 @@ private: void scheduleUpdate(); QList<QCocoaMenuItem *> m_menuItems; - NSMenu *m_nativeMenu; + QT_MANGLE_NAMESPACE(QCocoaNSMenu) *m_nativeMenu; NSMenuItem *m_attachedItem; int m_updateTimer; bool m_enabled:1; diff --git a/src/plugins/platforms/cocoa/qcocoamenu.mm b/src/plugins/platforms/cocoa/qcocoamenu.mm index a54284dbae..7a550b163b 100644 --- a/src/plugins/platforms/cocoa/qcocoamenu.mm +++ b/src/plugins/platforms/cocoa/qcocoamenu.mm @@ -38,222 +38,15 @@ ****************************************************************************/ #include "qcocoamenu.h" +#include "qcocoansmenu.h" #include "qcocoahelpers.h" #include <QtCore/QtDebug> -#include <QtCore/qmetaobject.h> -#include <QtCore/private/qthread_p.h> -#include <QtGui/private/qguiapplication_p.h> #include "qcocoaapplication.h" #include "qcocoamenuloader.h" #include "qcocoamenubar.h" #include "qcocoawindow.h" -#import "qnsview.h" - -QT_BEGIN_NAMESPACE - -NSString *qt_mac_removePrivateUnicode(NSString* string) -{ - 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]; - } - return string; -} - -QT_END_NAMESPACE - -@interface QT_MANGLE_NAMESPACE(QCocoaMenuDelegate) : NSObject <NSMenuDelegate> { - QCocoaMenu *m_menu; -} - -- (id) initWithMenu:(QCocoaMenu*) m; -- (NSMenuItem *)findItem:(NSMenu *)menu forKey:(NSString *)key forModifiers:(NSUInteger)modifier; - -@end - -QT_NAMESPACE_ALIAS_OBJC_CLASS(QCocoaMenuDelegate); - -@implementation QCocoaMenuDelegate - -- (id) initWithMenu:(QCocoaMenu*) m -{ - if ((self = [super init])) - m_menu = m; - - return self; -} - -- (NSInteger)numberOfItemsInMenu:(NSMenu *)menu -{ - Q_ASSERT(m_menu->nsMenu() == menu); - return menu.numberOfItems; -} - -- (BOOL)menu:(NSMenu *)menu updateItem:(NSMenuItem *)item atIndex:(NSInteger)index shouldCancel:(BOOL)shouldCancel -{ - Q_UNUSED(index); - Q_ASSERT(m_menu->nsMenu() == menu); - if (shouldCancel) { - // TODO detach all submenus - return NO; - } - - QCocoaMenuItem *menuItem = reinterpret_cast<QCocoaMenuItem *>(item.tag); - if (m_menu->items().contains(menuItem)) { - if (QCocoaMenu *itemSubmenu = menuItem->menu()) - itemSubmenu->setAttachedItem(item); - } - return YES; -} - -- (void)menu:(NSMenu*)menu willHighlightItem:(NSMenuItem*)item -{ - Q_UNUSED(menu); - if (item && [item tag]) { - QCocoaMenuItem *cocoaItem = reinterpret_cast<QCocoaMenuItem *>([item tag]); - cocoaItem->hovered(); - } -} - -- (void) menuWillOpen:(NSMenu*)m -{ - Q_UNUSED(m); - m_menu->setIsOpen(true); - emit m_menu->aboutToShow(); -} - -- (void) menuDidClose:(NSMenu*)m -{ - Q_UNUSED(m); - m_menu->setIsOpen(false); - // wrong, but it's the best we can do - emit m_menu->aboutToHide(); -} - -- (void) itemFired:(NSMenuItem*) item -{ - QCocoaMenuItem *cocoaItem = reinterpret_cast<QCocoaMenuItem *>([item tag]); - // Menu-holding items also get a target to play nicely - // with NSMenuValidation but should not trigger. - if (cocoaItem->menu()) - return; - QScopedScopeLevelCounter scopeLevelCounter(QGuiApplicationPrivate::instance()->threadData); - QGuiApplicationPrivate::modifier_buttons = [QNSView convertKeyModifiers:[NSEvent modifierFlags]]; - static QMetaMethod activatedSignal = QMetaMethod::fromSignal(&QCocoaMenuItem::activated); - activatedSignal.invoke(cocoaItem, Qt::QueuedConnection); -} - -- (BOOL)validateMenuItem:(NSMenuItem*)menuItem -{ - QCocoaMenuItem *cocoaItem = reinterpret_cast<QCocoaMenuItem *>(menuItem.tag); - // Menu-holding items are always enabled, as it's conventional in Cocoa - if (!cocoaItem || cocoaItem->menu()) - return YES; - - return cocoaItem->isEnabled(); -} - -- (BOOL)menuHasKeyEquivalent:(NSMenu *)menu forEvent:(NSEvent *)event target:(id *)target action:(SEL *)action -{ - /* - 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 (QEvent::ShortcutOverride). - 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). - */ - - // Change the private unicode keys to the ones used in setting the "Key Equivalents" - NSString *characters = qt_mac_removePrivateUnicode([event characters]); - // Interested only in Shift, Cmd, Ctrl & Alt Keys, so ignoring masks like, Caps lock, Num Lock ... - const NSUInteger mask = NSShiftKeyMask | NSControlKeyMask | NSCommandKeyMask | NSAlternateKeyMask; - if (NSMenuItem *menuItem = [self findItem:menu forKey:characters forModifiers:([event modifierFlags] & mask)]) { - if (!menuItem.target) { - // This item was modified by QCocoaMenuBar::redirectKnownMenuItemsToFirstResponder - // and it looks like we're running a modal session for NSOpenPanel/NSSavePanel. - // QCocoaFileDialogHelper is actually the only place we use this and we run NSOpenPanel modal - // (modal sheet, window modal, application modal). - // Whatever the current first responder is, let's give it a chance - // and do not touch the Qt's focusObject (which is different from some native view - // having a focus inside NSSave/OpenPanel. - *target = nil; - *action = menuItem.action; - return YES; - } - - QObject *object = qApp->focusObject(); - if (object) { - QChar ch; - int keyCode; - ulong nativeModifiers = [event modifierFlags]; - Qt::KeyboardModifiers modifiers = [QNSView convertKeyModifiers: nativeModifiers]; - NSString *charactersIgnoringModifiers = [event charactersIgnoringModifiers]; - NSString *characters = [event characters]; - - if ([charactersIgnoringModifiers length] > 0) { // convert the first character into a key code - if ((modifiers & Qt::ControlModifier) && ([characters length] != 0)) { - ch = QChar([characters characterAtIndex:0]); - } else { - ch = QChar([charactersIgnoringModifiers characterAtIndex:0]); - } - keyCode = qt_mac_cocoaKey2QtKey(ch); - } else { - // might be a dead key - ch = QChar::ReplacementCharacter; - keyCode = Qt::Key_unknown; - } - - QKeyEvent accel_ev(QEvent::ShortcutOverride, (keyCode & (~Qt::KeyboardModifierMask)), - Qt::KeyboardModifiers(modifiers & Qt::KeyboardModifierMask)); - accel_ev.ignore(); - QCoreApplication::sendEvent(object, &accel_ev); - if (accel_ev.isAccepted()) { - [[NSApp keyWindow] sendEvent: event]; - *target = nil; - *action = nil; - return YES; - } - } - } - return NO; -} - -- (NSMenuItem *)findItem:(NSMenu *)menu forKey:(NSString *)key forModifiers:(NSUInteger)modifier -{ - for (NSMenuItem *item in [menu itemArray]) { - if (![item isEnabled] || [item isHidden] || [item isSeparatorItem]) - continue; - if ([item hasSubmenu]) { - if (NSMenuItem *nested = [self findItem:[item submenu] forKey:key forModifiers:modifier]) - return nested; - } - - NSString *menuKey = [item keyEquivalent]; - if (menuKey - && NSOrderedSame == [menuKey compare:key] - && modifier == [item keyEquivalentModifierMask]) - return item; - } - return nil; -} - -@end QT_BEGIN_NAMESPACE @@ -267,9 +60,7 @@ QCocoaMenu::QCocoaMenu() : { QMacAutoReleasePool pool; - m_nativeMenu = [[NSMenu alloc] initWithTitle:@"Untitled"]; - [m_nativeMenu setAutoenablesItems:YES]; - m_nativeMenu.delegate = [[QCocoaMenuDelegate alloc] initWithMenu:this]; + m_nativeMenu = [[QCocoaNSMenu alloc] initWithQPAMenu:this]; } QCocoaMenu::~QCocoaMenu() @@ -279,10 +70,6 @@ QCocoaMenu::~QCocoaMenu() item->setMenuParent(0); } - QMacAutoReleasePool pool; - NSObject *delegate = m_nativeMenu.delegate; - m_nativeMenu.delegate = nil; - [delegate release]; [m_nativeMenu release]; } @@ -307,6 +94,11 @@ void QCocoaMenu::setFont(const QFont &font) } } +NSMenu *QCocoaMenu::nsMenu() const +{ + return static_cast<NSMenu *>(m_nativeMenu); +} + void QCocoaMenu::insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem *before) { QMacAutoReleasePool pool; diff --git a/src/plugins/platforms/cocoa/qcocoansmenu.h b/src/plugins/platforms/cocoa/qcocoansmenu.h new file mode 100644 index 0000000000..89843535e6 --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoansmenu.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCOCOANSMENU_H +#define QCOCOANSMENU_H + +// +// 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. +// + +#import <AppKit/AppKit.h> + +#include <QtCore/qpointer.h> +#include <qcocoahelpers.h> + +QT_FORWARD_DECLARE_CLASS(QCocoaMenu); +typedef QPointer<QCocoaMenu> QCocoaMenuPointer; + + +@interface QT_MANGLE_NAMESPACE(QCocoaNSMenuDelegate) : NSObject <NSMenuDelegate> + ++ (instancetype)sharedMenuDelegate; + +- (NSMenuItem *)findItem:(NSMenu *)menu forKey:(NSString *)key forModifiers:(NSUInteger)modifier; + +- (BOOL)validateMenuItem:(NSMenuItem *)item; // NSMenuValidation + +@end + +@interface QT_MANGLE_NAMESPACE(QCocoaNSMenu) : NSMenu + +@property (readonly, nonatomic) QCocoaMenuPointer qpaMenu; + +- (instancetype)initWithQPAMenu:(QCocoaMenu *)menu; + +@end + +QT_NAMESPACE_ALIAS_OBJC_CLASS(QCocoaNSMenu); +QT_NAMESPACE_ALIAS_OBJC_CLASS(QCocoaNSMenuDelegate); + +#endif // QCOCOANSMENU_H diff --git a/src/plugins/platforms/cocoa/qcocoansmenu.mm b/src/plugins/platforms/cocoa/qcocoansmenu.mm new file mode 100644 index 0000000000..2ff207b325 --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoansmenu.mm @@ -0,0 +1,276 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#import "qcocoansmenu.h" +#include "qcocoamenu.h" +#include "qcocoamenuitem.h" +#import "qnsview.h" + +#include <QtCore/qmetaobject.h> +#include <QtCore/private/qthread_p.h> +#include <QtGui/private/qguiapplication_p.h> + +static NSString *qt_mac_removePrivateUnicode(NSString* string) +{ + 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]; + } + return string; +} + +@implementation QCocoaNSMenu + +- (instancetype)initWithQPAMenu:(QCocoaMenu *)menu +{ + if ((self = [super initWithTitle:@"Untitled"])) { + _qpaMenu = menu; + self.autoenablesItems = YES; + self.delegate = [QCocoaNSMenuDelegate sharedMenuDelegate]; + } + + return self; +} + +@end + +#define CHECK_MENU_CLASS(menu) Q_ASSERT([menu isMemberOfClass:[QCocoaNSMenu class]]) + +@implementation QCocoaNSMenuDelegate + ++ (instancetype)sharedMenuDelegate +{ + static QCocoaNSMenuDelegate *shared = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + shared = [[self alloc] init]; + atexit_b(^{ + [shared release]; + shared = nil; + }); + }); + return shared; +} + +- (NSInteger)numberOfItemsInMenu:(NSMenu *)menu +{ + CHECK_MENU_CLASS(menu); + return menu.numberOfItems; +} + +- (BOOL)menu:(NSMenu *)menu updateItem:(NSMenuItem *)item atIndex:(NSInteger)index shouldCancel:(BOOL)shouldCancel +{ + Q_UNUSED(index); + CHECK_MENU_CLASS(menu); + + if (shouldCancel) + return NO; + + const auto &qpaMenu = static_cast<QCocoaNSMenu *>(menu).qpaMenu; + if (qpaMenu.isNull()) + return YES; + + auto *menuItem = reinterpret_cast<QCocoaMenuItem *>(item.tag); + if (qpaMenu->items().contains(menuItem)) { + if (QCocoaMenu *itemSubmenu = menuItem->menu()) + itemSubmenu->setAttachedItem(item); + } + + return YES; +} + +- (void)menu:(NSMenu *)menu willHighlightItem:(NSMenuItem *)item +{ + CHECK_MENU_CLASS(menu); + auto *qpaItem = reinterpret_cast<QCocoaMenuItem *>(item.tag); + if (qpaItem) + qpaItem->hovered(); +} + +- (void)menuWillOpen:(NSMenu *)menu +{ + CHECK_MENU_CLASS(menu); + const auto &qpaMenu = static_cast<QCocoaNSMenu *>(menu).qpaMenu; + if (qpaMenu.isNull()) + return; + + qpaMenu->setIsOpen(true); + emit qpaMenu->aboutToShow(); +} + +- (void)menuDidClose:(NSMenu *)menu +{ + CHECK_MENU_CLASS(menu); + const auto &qpaMenu = static_cast<QCocoaNSMenu *>(menu).qpaMenu; + if (qpaMenu.isNull()) + return; + + qpaMenu->setIsOpen(false); + // wrong, but it's the best we can do + emit qpaMenu->aboutToHide(); +} + +- (void)itemFired:(NSMenuItem *)item +{ + auto *qpaItem = reinterpret_cast<QCocoaMenuItem *>(item.tag); + // Menu-holding items also get a target to play nicely + // with NSMenuValidation but should not trigger. + if (!qpaItem || qpaItem->menu()) + return; + + QScopedScopeLevelCounter scopeLevelCounter(QGuiApplicationPrivate::instance()->threadData); + QGuiApplicationPrivate::modifier_buttons = [QNSView convertKeyModifiers:[NSEvent modifierFlags]]; + + static QMetaMethod activatedSignal = QMetaMethod::fromSignal(&QCocoaMenuItem::activated); + activatedSignal.invoke(qpaItem, Qt::QueuedConnection); +} + +- (BOOL)validateMenuItem:(NSMenuItem*)item +{ + auto *qpaItem = reinterpret_cast<QCocoaMenuItem *>(item.tag); + // Menu-holding items are always enabled, as it's conventional in Cocoa + if (!qpaItem || qpaItem->menu()) + return YES; + + return qpaItem->isEnabled(); +} + +- (BOOL)menuHasKeyEquivalent:(NSMenu *)menu forEvent:(NSEvent *)event target:(id *)target action:(SEL *)action +{ + /* + 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 (QEvent::ShortcutOverride). + 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). + */ + + CHECK_MENU_CLASS(menu); + + // Change the private unicode keys to the ones used in setting the "Key Equivalents" + NSString *characters = qt_mac_removePrivateUnicode([event characters]); + // Interested only in Shift, Cmd, Ctrl & Alt Keys, so ignoring masks like, Caps lock, Num Lock ... + const NSUInteger mask = NSShiftKeyMask | NSControlKeyMask | NSCommandKeyMask | NSAlternateKeyMask; + if (NSMenuItem *menuItem = [self findItem:menu forKey:characters forModifiers:([event modifierFlags] & mask)]) { + if (!menuItem.target) { + // This item was modified by QCocoaMenuBar::redirectKnownMenuItemsToFirstResponder + // and it looks like we're running a modal session for NSOpenPanel/NSSavePanel. + // QCocoaFileDialogHelper is actually the only place we use this and we run NSOpenPanel modal + // (modal sheet, window modal, application modal). + // Whatever the current first responder is, let's give it a chance + // and do not touch the Qt's focusObject (which is different from some native view + // having a focus inside NSSave/OpenPanel. + *target = nil; + *action = menuItem.action; + return YES; + } + + QObject *object = qApp->focusObject(); + if (object) { + QChar ch; + int keyCode; + ulong nativeModifiers = [event modifierFlags]; + Qt::KeyboardModifiers modifiers = [QNSView convertKeyModifiers: nativeModifiers]; + NSString *charactersIgnoringModifiers = [event charactersIgnoringModifiers]; + NSString *characters = [event characters]; + + if ([charactersIgnoringModifiers length] > 0) { // convert the first character into a key code + if ((modifiers & Qt::ControlModifier) && ([characters length] != 0)) { + ch = QChar([characters characterAtIndex:0]); + } else { + ch = QChar([charactersIgnoringModifiers characterAtIndex:0]); + } + keyCode = qt_mac_cocoaKey2QtKey(ch); + } else { + // might be a dead key + ch = QChar::ReplacementCharacter; + keyCode = Qt::Key_unknown; + } + + QKeyEvent accel_ev(QEvent::ShortcutOverride, (keyCode & (~Qt::KeyboardModifierMask)), + Qt::KeyboardModifiers(modifiers & Qt::KeyboardModifierMask)); + accel_ev.ignore(); + QCoreApplication::sendEvent(object, &accel_ev); + if (accel_ev.isAccepted()) { + [[NSApp keyWindow] sendEvent: event]; + *target = nil; + *action = nil; + return YES; + } + } + } + return NO; +} + +- (NSMenuItem *)findItem:(NSMenu *)menu forKey:(NSString *)key forModifiers:(NSUInteger)modifier +{ + for (NSMenuItem *item in [menu itemArray]) { + if (![item isEnabled] || [item isHidden] || [item isSeparatorItem]) + continue; + if ([item hasSubmenu]) { + if (NSMenuItem *nested = [self findItem:[item submenu] forKey:key forModifiers:modifier]) + return nested; + } + + NSString *menuKey = [item keyEquivalent]; + if (menuKey + && NSOrderedSame == [menuKey compare:key] + && modifier == [item keyEquivalentModifierMask]) + return item; + } + return nil; +} + +@end + +#undef CHECK_MENU_CLASS |