From 4ca105b9bbf5c1e9b539da9522c630574eba3471 Mon Sep 17 00:00:00 2001 From: Frederik Gladhorn Date: Wed, 24 Apr 2013 15:41:49 +0200 Subject: [PATCH] Fix shortcut override for menus Since we use native Cocoa menus, we cannot rely on the normal shortcut handling. Shortcuts can be overridden by the currently focused object in Qt. In order to make that possible we need to send a QShortcutOverride event before accepting any key event. For menus the key event goes from the NSApp directly to the menu, so the shortcutOverride would not work. This is mostly an adaptation of the Qt 4 code. Task-number: QTBUG-30695 Change-Id: Icb4979309d2d6f9606eb9c8abc4130dc79926593 Reviewed-by: Gabriel de Dietrich --- src/plugins/platforms/cocoa/qcocoamenu.mm | 95 +++++++++++++++++++++++ src/plugins/platforms/cocoa/qnsview.h | 2 +- src/plugins/platforms/cocoa/qnsview.mm | 16 ++-- 3 files changed, 104 insertions(+), 9 deletions(-) diff --git a/src/plugins/platforms/cocoa/qcocoamenu.mm b/src/plugins/platforms/cocoa/qcocoamenu.mm index 9020aef600..59172a24c6 100644 --- a/src/plugins/platforms/cocoa/qcocoamenu.mm +++ b/src/plugins/platforms/cocoa/qcocoamenu.mm @@ -50,6 +50,27 @@ #include "qcocoawindow.h" #import "qnsview.h" +NSString *qt_mac_removePrivateUnicode(NSString* string) +{ + int len = [string length]; + if (len) { + QVarLengthArray characters(len); + bool changed = false; + for (int i = 0; iisEnabled(); } +- (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]); + 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)) + ]) { + 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(keyCode & 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; +} + +- (BOOL)hasShortcut:(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] + && [self hasShortcut:[item submenu] forKey:key forModifiers:modifier]) + return YES; + + NSString *menuKey = [item keyEquivalent]; + if (menuKey + && NSOrderedSame == [menuKey compare:key] + && modifier == [item keyEquivalentModifierMask]) + return YES; + } + return NO; +} + @end QT_BEGIN_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qnsview.h b/src/plugins/platforms/cocoa/qnsview.h index 68145ec914..67b16b4b32 100644 --- a/src/plugins/platforms/cocoa/qnsview.h +++ b/src/plugins/platforms/cocoa/qnsview.h @@ -107,7 +107,7 @@ QT_END_NAMESPACE - (void)handleFrameStrutMouseEvent:(NSEvent *)theEvent; - (int) convertKeyCode : (QChar)keyCode; -- (Qt::KeyboardModifiers) convertKeyModifiers : (ulong)modifierFlags; ++ (Qt::KeyboardModifiers) convertKeyModifiers : (ulong)modifierFlags; - (void)handleKeyEvent:(NSEvent *)theEvent eventType:(int)eventType; - (void)keyDown:(NSEvent *)theEvent; - (void)keyUp:(NSEvent *)theEvent; diff --git a/src/plugins/platforms/cocoa/qnsview.mm b/src/plugins/platforms/cocoa/qnsview.mm index 52e2d781ee..d1dd939600 100644 --- a/src/plugins/platforms/cocoa/qnsview.mm +++ b/src/plugins/platforms/cocoa/qnsview.mm @@ -494,7 +494,7 @@ static QTouchDevice *touchDevice = 0; QCocoaDrag* nativeDrag = static_cast(QGuiApplicationPrivate::platformIntegration()->drag()); nativeDrag->setLastMouseEvent(theEvent, self); - Qt::KeyboardModifiers keyboardModifiers = [self convertKeyModifiers:[theEvent modifierFlags]]; + Qt::KeyboardModifiers keyboardModifiers = [QNSView convertKeyModifiers:[theEvent modifierFlags]]; QWindowSystemInterface::handleMouseEvent(m_window, timestamp, qtWindowPoint, qtScreenPoint, m_buttons, keyboardModifiers); } @@ -556,7 +556,7 @@ static QTouchDevice *touchDevice = 0; [inputManager handleMouseEvent:theEvent]; } } else { - if ([self convertKeyModifiers:[theEvent modifierFlags]] & Qt::MetaModifier) { + if ([QNSView convertKeyModifiers:[theEvent modifierFlags]] & Qt::MetaModifier) { m_buttons |= Qt::RightButton; m_sendUpAsRightButton = true; } else { @@ -826,7 +826,7 @@ static QTouchDevice *touchDevice = 0; if ([theEvent respondsToSelector:@selector(scrollingDeltaX)]) { NSEventPhase phase = [theEvent phase]; if (phase == NSEventPhaseBegan) { - currentWheelModifiers = [self convertKeyModifiers:[theEvent modifierFlags]]; + currentWheelModifiers = [QNSView convertKeyModifiers:[theEvent modifierFlags]]; } QWindowSystemInterface::handleWheelEvent(m_window, qt_timestamp, qt_windowPoint, qt_screenPoint, pixelDelta, angleDelta, currentWheelModifiers); @@ -838,7 +838,7 @@ static QTouchDevice *touchDevice = 0; #endif { QWindowSystemInterface::handleWheelEvent(m_window, qt_timestamp, qt_windowPoint, qt_screenPoint, pixelDelta, angleDelta, - [self convertKeyModifiers:[theEvent modifierFlags]]); + [QNSView convertKeyModifiers:[theEvent modifierFlags]]); } } #endif //QT_NO_WHEELEVENT @@ -848,7 +848,7 @@ static QTouchDevice *touchDevice = 0; return qt_mac_cocoaKey2QtKey(keyChar); } -- (Qt::KeyboardModifiers) convertKeyModifiers : (ulong)modifierFlags ++ (Qt::KeyboardModifiers) convertKeyModifiers : (ulong)modifierFlags { Qt::KeyboardModifiers qtMods =Qt::NoModifier; if (modifierFlags & NSShiftKeyMask) @@ -868,7 +868,7 @@ static QTouchDevice *touchDevice = 0; { ulong timestamp = [nsevent timestamp] * 1000; ulong nativeModifiers = [nsevent modifierFlags]; - Qt::KeyboardModifiers modifiers = [self convertKeyModifiers: nativeModifiers]; + Qt::KeyboardModifiers modifiers = [QNSView convertKeyModifiers: nativeModifiers]; NSString *charactersIgnoringModifiers = [nsevent charactersIgnoringModifiers]; NSString *characters = [nsevent characters]; @@ -948,7 +948,7 @@ static QTouchDevice *touchDevice = 0; { ulong timestamp = [nsevent timestamp] * 1000; ulong modifiers = [nsevent modifierFlags]; - Qt::KeyboardModifiers qmodifiers = [self convertKeyModifiers:modifiers]; + Qt::KeyboardModifiers qmodifiers = [QNSView convertKeyModifiers:modifiers]; // calculate the delta and remember the current modifiers for next time static ulong m_lastKnownModifiers; @@ -1278,7 +1278,7 @@ static QTouchDevice *touchDevice = 0; Qt::DropActions qtAllowed = qt_mac_mapNSDragOperations([sender draggingSourceOperationMask]); // update these so selecting move/copy/link works - QGuiApplicationPrivate::modifier_buttons = [self convertKeyModifiers: [[NSApp currentEvent] modifierFlags]]; + QGuiApplicationPrivate::modifier_buttons = [QNSView convertKeyModifiers: [[NSApp currentEvent] modifierFlags]]; QPlatformDragQtResponse response(false, Qt::IgnoreAction, QRect()); if ([sender draggingSource] != nil) {