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 <gabriel.dedietrich@digia.com>
This commit is contained in:
Frederik Gladhorn 2013-04-24 15:41:49 +02:00 committed by The Qt Project
parent 8f125985db
commit 4ca105b9bb
3 changed files with 104 additions and 9 deletions

View File

@ -50,6 +50,27 @@
#include "qcocoawindow.h" #include "qcocoawindow.h"
#import "qnsview.h" #import "qnsview.h"
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;
}
static inline QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *getMenuLoader() static inline QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *getMenuLoader()
{ {
return [NSApp QT_MANGLE_NAMESPACE(qt_qcocoamenuLoader)]; return [NSApp QT_MANGLE_NAMESPACE(qt_qcocoamenuLoader)];
@ -101,6 +122,80 @@ static inline QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *getMenuLoader()
return cocoaItem->isEnabled(); 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]);
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 @end
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE

View File

@ -107,7 +107,7 @@ QT_END_NAMESPACE
- (void)handleFrameStrutMouseEvent:(NSEvent *)theEvent; - (void)handleFrameStrutMouseEvent:(NSEvent *)theEvent;
- (int) convertKeyCode : (QChar)keyCode; - (int) convertKeyCode : (QChar)keyCode;
- (Qt::KeyboardModifiers) convertKeyModifiers : (ulong)modifierFlags; + (Qt::KeyboardModifiers) convertKeyModifiers : (ulong)modifierFlags;
- (void)handleKeyEvent:(NSEvent *)theEvent eventType:(int)eventType; - (void)handleKeyEvent:(NSEvent *)theEvent eventType:(int)eventType;
- (void)keyDown:(NSEvent *)theEvent; - (void)keyDown:(NSEvent *)theEvent;
- (void)keyUp:(NSEvent *)theEvent; - (void)keyUp:(NSEvent *)theEvent;

View File

@ -494,7 +494,7 @@ static QTouchDevice *touchDevice = 0;
QCocoaDrag* nativeDrag = static_cast<QCocoaDrag *>(QGuiApplicationPrivate::platformIntegration()->drag()); QCocoaDrag* nativeDrag = static_cast<QCocoaDrag *>(QGuiApplicationPrivate::platformIntegration()->drag());
nativeDrag->setLastMouseEvent(theEvent, self); 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); QWindowSystemInterface::handleMouseEvent(m_window, timestamp, qtWindowPoint, qtScreenPoint, m_buttons, keyboardModifiers);
} }
@ -556,7 +556,7 @@ static QTouchDevice *touchDevice = 0;
[inputManager handleMouseEvent:theEvent]; [inputManager handleMouseEvent:theEvent];
} }
} else { } else {
if ([self convertKeyModifiers:[theEvent modifierFlags]] & Qt::MetaModifier) { if ([QNSView convertKeyModifiers:[theEvent modifierFlags]] & Qt::MetaModifier) {
m_buttons |= Qt::RightButton; m_buttons |= Qt::RightButton;
m_sendUpAsRightButton = true; m_sendUpAsRightButton = true;
} else { } else {
@ -826,7 +826,7 @@ static QTouchDevice *touchDevice = 0;
if ([theEvent respondsToSelector:@selector(scrollingDeltaX)]) { if ([theEvent respondsToSelector:@selector(scrollingDeltaX)]) {
NSEventPhase phase = [theEvent phase]; NSEventPhase phase = [theEvent phase];
if (phase == NSEventPhaseBegan) { 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); QWindowSystemInterface::handleWheelEvent(m_window, qt_timestamp, qt_windowPoint, qt_screenPoint, pixelDelta, angleDelta, currentWheelModifiers);
@ -838,7 +838,7 @@ static QTouchDevice *touchDevice = 0;
#endif #endif
{ {
QWindowSystemInterface::handleWheelEvent(m_window, qt_timestamp, qt_windowPoint, qt_screenPoint, pixelDelta, angleDelta, QWindowSystemInterface::handleWheelEvent(m_window, qt_timestamp, qt_windowPoint, qt_screenPoint, pixelDelta, angleDelta,
[self convertKeyModifiers:[theEvent modifierFlags]]); [QNSView convertKeyModifiers:[theEvent modifierFlags]]);
} }
} }
#endif //QT_NO_WHEELEVENT #endif //QT_NO_WHEELEVENT
@ -848,7 +848,7 @@ static QTouchDevice *touchDevice = 0;
return qt_mac_cocoaKey2QtKey(keyChar); return qt_mac_cocoaKey2QtKey(keyChar);
} }
- (Qt::KeyboardModifiers) convertKeyModifiers : (ulong)modifierFlags + (Qt::KeyboardModifiers) convertKeyModifiers : (ulong)modifierFlags
{ {
Qt::KeyboardModifiers qtMods =Qt::NoModifier; Qt::KeyboardModifiers qtMods =Qt::NoModifier;
if (modifierFlags & NSShiftKeyMask) if (modifierFlags & NSShiftKeyMask)
@ -868,7 +868,7 @@ static QTouchDevice *touchDevice = 0;
{ {
ulong timestamp = [nsevent timestamp] * 1000; ulong timestamp = [nsevent timestamp] * 1000;
ulong nativeModifiers = [nsevent modifierFlags]; ulong nativeModifiers = [nsevent modifierFlags];
Qt::KeyboardModifiers modifiers = [self convertKeyModifiers: nativeModifiers]; Qt::KeyboardModifiers modifiers = [QNSView convertKeyModifiers: nativeModifiers];
NSString *charactersIgnoringModifiers = [nsevent charactersIgnoringModifiers]; NSString *charactersIgnoringModifiers = [nsevent charactersIgnoringModifiers];
NSString *characters = [nsevent characters]; NSString *characters = [nsevent characters];
@ -948,7 +948,7 @@ static QTouchDevice *touchDevice = 0;
{ {
ulong timestamp = [nsevent timestamp] * 1000; ulong timestamp = [nsevent timestamp] * 1000;
ulong modifiers = [nsevent modifierFlags]; 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 // calculate the delta and remember the current modifiers for next time
static ulong m_lastKnownModifiers; static ulong m_lastKnownModifiers;
@ -1278,7 +1278,7 @@ static QTouchDevice *touchDevice = 0;
Qt::DropActions qtAllowed = qt_mac_mapNSDragOperations([sender draggingSourceOperationMask]); Qt::DropActions qtAllowed = qt_mac_mapNSDragOperations([sender draggingSourceOperationMask]);
// update these so selecting move/copy/link works // 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()); QPlatformDragQtResponse response(false, Qt::IgnoreAction, QRect());
if ([sender draggingSource] != nil) { if ([sender draggingSource] != nil) {