From 4af513212d9ca9ed88e18bddaabd90006aca8541 Mon Sep 17 00:00:00 2001 From: Norwegian Rock Cat Date: Fri, 8 May 2009 13:18:05 +0200 Subject: Add a AA_MacDontSwapCtrlAndMeta application attribute. This is to help undo the some magic that is in the Qt/Mac port. Qt automatically flips the Meta and Control keys on Mac. This is a "feature" that makes porting older programs that don't use standard shortcuts easier as Ctrl and Command usually map to the same shortcuts in the application. The upshot of this is that I need to strip the text() out of key events if they contain the Control or Meta modifier. This causes much headache for anyone writing a terminal emulator. Though they would still have to write special code because the keys are swapped anyway. This allows people to write the terminal emulator where hitting the Control key will really send a Control key modifier. We've also done the extra work to ensure that standard shortcuts work correctly regardless of what the value of the attribute is. That is, if you specify QKeySequence::Cut for a shortcut you can always hit Command+X and things will work. --- doc/src/qnamespace.qdoc | 8 +++ src/corelib/global/qnamespace.h | 1 + src/gui/kernel/qevent.cpp | 11 ++++ src/gui/kernel/qkeymapper_mac.cpp | 17 ++++++ src/gui/kernel/qkeysequence.cpp | 104 ++++++++++++++++++++++++++++-------- src/gui/kernel/qkeysequence.h | 4 +- src/gui/widgets/qmenu_mac.mm | 108 +++++++++++++++++--------------------- 7 files changed, 170 insertions(+), 83 deletions(-) diff --git a/doc/src/qnamespace.qdoc b/doc/src/qnamespace.qdoc index ad59b8d..fc4310b 100644 --- a/doc/src/qnamespace.qdoc +++ b/doc/src/qnamespace.qdoc @@ -157,6 +157,14 @@ set to true won't be used as a native menubar (e.g, the menubar at the top of the main screen on Mac OS X or at the bottom in Windows CE). + \value AA_MacDontSwapCtrlAndMeta On Mac OS X by default, Qt swaps the + Control and Meta (Command) keys (i.e., whenever Control is pressed, Qt + sends Meta and whenever Meta is pressed Control is sent. When this + attribute is true, Qt will not do the flip. QKeySequence::StandardShortcuts + will also flip accordingly (i.e., QKeySequence::Copy will be + Command+C on the keyboard regardless of the value set, though what is output for + QKeySequence::toString(QKeySequence::PortableText) will be different). + \omitvalue AA_AttributeCount */ diff --git a/src/corelib/global/qnamespace.h b/src/corelib/global/qnamespace.h index 4873b17..3367581 100644 --- a/src/corelib/global/qnamespace.h +++ b/src/corelib/global/qnamespace.h @@ -504,6 +504,7 @@ public: AA_DontCreateNativeWidgetSiblings = 4, AA_MacPluginApplication = 5, AA_DontUseNativeMenuBar = 6, + AA_MacDontSwapCtrlAndMeta = 7, // Add new attributes before this line AA_AttributeCount diff --git a/src/gui/kernel/qevent.cpp b/src/gui/kernel/qevent.cpp index 8c7e47d..a4d5035 100644 --- a/src/gui/kernel/qevent.cpp +++ b/src/gui/kernel/qevent.cpp @@ -860,6 +860,17 @@ bool QKeyEvent::matches(QKeySequence::StandardKey matchKey) const uint searchkey = (modifiers() | key()) & ~(Qt::KeypadModifier); //The keypad modifier should not make a difference uint platform = QApplicationPrivate::currentPlatform(); +#ifdef Q_WS_MAC + if (qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)) { + uint oldSearchKey = searchkey; + searchkey &= ~(Qt::ControlModifier | Qt::MetaModifier); + if (oldSearchKey & Qt::ControlModifier) + searchkey |= Qt::MetaModifier; + if (oldSearchKey & Qt::MetaModifier) + searchkey |= Qt::ControlModifier; + } +#endif + uint N = QKeySequencePrivate::numberOfKeyBindings; int first = 0; int last = N - 1; diff --git a/src/gui/kernel/qkeymapper_mac.cpp b/src/gui/kernel/qkeymapper_mac.cpp index 39abc5e..01b2c13 100644 --- a/src/gui/kernel/qkeymapper_mac.cpp +++ b/src/gui/kernel/qkeymapper_mac.cpp @@ -161,6 +161,14 @@ Qt::KeyboardModifiers qt_mac_get_modifiers(int keys) ret |= Qt::KeyboardModifier(qt_mac_modifier_symbols[i].qt_code); } } + if (qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)) { + Qt::KeyboardModifiers oldModifiers = ret; + ret &= ~(Qt::MetaModifier | Qt::ControlModifier); + if (oldModifiers & Qt::ControlModifier) + ret |= Qt::MetaModifier; + if (oldModifiers & Qt::MetaModifier) + ret |= Qt::ControlModifier; + } return ret; } static int qt_mac_get_mac_modifiers(Qt::KeyboardModifiers keys) @@ -177,6 +185,15 @@ static int qt_mac_get_mac_modifiers(Qt::KeyboardModifiers keys) ret |= qt_mac_modifier_symbols[i].mac_code; } } + + if (qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)) { + int oldModifiers = ret; + ret &= ~(controlKeyBit | cmdKeyBit); + if (oldModifiers & controlKeyBit) + ret |= cmdKeyBit; + if (oldModifiers & cmdKeyBit) + ret |= controlKeyBit; + } return ret; } void qt_mac_send_modifiers_changed(quint32 modifiers, QObject *object) diff --git a/src/gui/kernel/qkeysequence.cpp b/src/gui/kernel/qkeysequence.cpp index 9e607db..d1cb572 100644 --- a/src/gui/kernel/qkeysequence.cpp +++ b/src/gui/kernel/qkeysequence.cpp @@ -105,20 +105,39 @@ static bool operator<(int key, const MacSpecialKey &entry) static const MacSpecialKey * const MacSpecialKeyEntriesEnd = entries + NumEntries; -static QChar macSymbolForQtKey(int key) +QChar qt_macSymbolForQtKey(int key) { const MacSpecialKey *i = qBinaryFind(entries, MacSpecialKeyEntriesEnd, key); if (i == MacSpecialKeyEntriesEnd) return QChar(); - return QChar(i->macSymbol); + ushort macSymbol = i->macSymbol; + if (qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta) + && (macSymbol == kControlUnicode || macSymbol == kCommandUnicode)) { + if (macSymbol == kControlUnicode) + macSymbol = kCommandUnicode; + else + macSymbol = kControlUnicode; + } + + return QChar(macSymbol); } static int qtkeyForMacSymbol(const QChar ch) { + const ushort unicode = ch.unicode(); for (int i = 0; i < NumEntries; ++i) { const MacSpecialKey &entry = entries[i]; - if (entry.macSymbol == ch.unicode()) - return entry.key; + if (entry.macSymbol == unicode) { + int key = entry.key; + if (qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta) + && (unicode == kControlUnicode || unicode == kCommandUnicode)) { + if (unicode == kControlUnicode) + key = Qt::Key_Control; + else + key = Qt::Key_Meta; + } + return key; + } } return -1; } @@ -788,6 +807,21 @@ QKeySequence::QKeySequence(const QKeySequence& keysequence) d->ref.ref(); } +#ifdef Q_WS_MAC +static inline int maybeSwapShortcut(int shortcut) +{ + if (qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)) { + uint oldshortcut = shortcut; + shortcut &= ~(Qt::CTRL | Qt::META); + if (oldshortcut & Qt::CTRL) + shortcut |= Qt::META; + if (oldshortcut & Qt::META) + shortcut |= Qt::CTRL; + } + return shortcut; +} +#endif + /*! \since 4.2 @@ -804,10 +838,16 @@ QList QKeySequence::keyBindings(StandardKey key) for (uint i = 0; i < QKeySequencePrivate::numberOfKeyBindings ; ++i) { QKeyBinding keyBinding = QKeySequencePrivate::keyBindings[i]; if (keyBinding.standardKey == key && (keyBinding.platform & platform)) { - if (keyBinding.priority > 0) - list.prepend(QKeySequence(QKeySequencePrivate::keyBindings[i].shortcut)); - else - list.append(QKeySequence(QKeySequencePrivate::keyBindings[i].shortcut)); + uint shortcut = +#ifdef Q_WS_MAC + maybeSwapShortcut(QKeySequencePrivate::keyBindings[i].shortcut); +#else + QKeySequencePrivate::keyBindings[i].shortcut; +#endif + if (keyBinding.priority > 0) + list.prepend(QKeySequence(shortcut)); + else + list.append(QKeySequence(shortcut)); } } return list; @@ -975,9 +1015,16 @@ int QKeySequencePrivate::decodeString(const QString &str, QKeySequence::Sequence gmodifs = globalModifs(); if (gmodifs->isEmpty()) { #ifdef Q_WS_MAC - *gmodifs << QModifKeyName(Qt::CTRL, QChar(kCommandUnicode)); + const bool dontSwap = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta); + if (dontSwap) + *gmodifs << QModifKeyName(Qt::META, QChar(kCommandUnicode)); + else + *gmodifs << QModifKeyName(Qt::CTRL, QChar(kCommandUnicode)); *gmodifs << QModifKeyName(Qt::ALT, QChar(kOptionUnicode)); - *gmodifs << QModifKeyName(Qt::META, QChar(kControlUnicode)); + if (dontSwap) + *gmodifs << QModifKeyName(Qt::CTRL, QChar(kControlUnicode)); + else + *gmodifs << QModifKeyName(Qt::META, QChar(kControlUnicode)); *gmodifs << QModifKeyName(Qt::SHIFT, QChar(kShiftUnicode)); #endif *gmodifs << QModifKeyName(Qt::CTRL, QLatin1String("ctrl+")) @@ -1075,8 +1122,6 @@ int QKeySequencePrivate::decodeString(const QString &str, QKeySequence::Sequence if (found) break; } -#ifdef Q_WS_MAC -#endif } return ret; } @@ -1105,15 +1150,30 @@ QString QKeySequencePrivate::encodeString(int key, QKeySequence::SequenceFormat QString s; #if defined(Q_WS_MAC) if (nativeText) { - // On MAC the order is Meta, Alt, Shift, Control. - if ((key & Qt::META) == Qt::META) - s += macSymbolForQtKey(Qt::Key_Meta); - if ((key & Qt::ALT) == Qt::ALT) - s += macSymbolForQtKey(Qt::Key_Alt); - if ((key & Qt::SHIFT) == Qt::SHIFT) - s += macSymbolForQtKey(Qt::Key_Shift); - if ((key & Qt::CTRL) == Qt::CTRL) - s += macSymbolForQtKey(Qt::Key_Control); + // On Mac OS X the order (by default) is Meta, Alt, Shift, Control. + // If the AA_MacDontSwapCtrlAndMeta is enabled, then the order + // is Ctrl, Alt, Shift, Meta. The macSymbolForQtKey does this swap + // for us, which means that we have to adjust our order here. + // The upshot is a lot more infrastructure to keep the number of + // if tests down and the code relatively clean. + static const int ModifierOrder[] = { Qt::META, Qt::ALT, Qt::SHIFT, Qt::CTRL, 0 }; + static const int QtKeyOrder[] = { Qt::Key_Meta, Qt::Key_Alt, Qt::Key_Shift, Qt::Key_Control, 0 }; + static const int DontSwapModifierOrder[] = { Qt::CTRL, Qt::ALT, Qt::SHIFT, Qt::META, 0 }; + static const int DontSwapQtKeyOrder[] = { Qt::Key_Control, Qt::Key_Alt, Qt::Key_Shift, Qt::Key_Meta, 0 }; + const int *modifierOrder; + const int *qtkeyOrder; + if (qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)) { + modifierOrder = DontSwapModifierOrder; + qtkeyOrder = DontSwapQtKeyOrder; + } else { + modifierOrder = ModifierOrder; + qtkeyOrder = QtKeyOrder; + } + + for (int i = 0; modifierOrder[i] != 0; ++i) { + if (key & modifierOrder[i]) + s += qt_macSymbolForQtKey(qtkeyOrder[i]); + } } else #endif { @@ -1146,7 +1206,7 @@ QString QKeySequencePrivate::encodeString(int key, QKeySequence::SequenceFormat int i=0; #if defined(Q_WS_MAC) if (nativeText) { - QChar ch = macSymbolForQtKey(key); + QChar ch = qt_macSymbolForQtKey(key); if (!ch.isNull()) p = ch; else diff --git a/src/gui/kernel/qkeysequence.h b/src/gui/kernel/qkeysequence.h index 1c4776f..f78e34a 100644 --- a/src/gui/kernel/qkeysequence.h +++ b/src/gui/kernel/qkeysequence.h @@ -136,7 +136,9 @@ public: DeleteEndOfLine, InsertParagraphSeparator, InsertLineSeparator, - SaveAs + SaveAs, + Preferences, + Quit }; QKeySequence(); diff --git a/src/gui/widgets/qmenu_mac.mm b/src/gui/widgets/qmenu_mac.mm index d2aad99..cce083f 100644 --- a/src/gui/widgets/qmenu_mac.mm +++ b/src/gui/widgets/qmenu_mac.mm @@ -142,6 +142,39 @@ static int qt_mac_CountMenuItems(OSMenuRef menu) return 0; } +static quint32 constructModifierMask(quint32 accel_key) +{ + quint32 ret = 0; + const bool dontSwap = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta); +#ifndef QT_MAC_USE_COCOA + if ((accel_key & Qt::ALT) == Qt::ALT) + ret |= kMenuOptionModifier; + if ((accel_key & Qt::SHIFT) == Qt::SHIFT) + ret |= kMenuShiftModifier; + if (dontSwap) { + if ((accel_key & Qt::META) != Qt::META) + ret |= kMenuNoCommandModifier; + if ((accel_key & Qt::CTRL) == Qt::CTRL) + ret |= kMenuControlModifier; + } else { + if ((accel_key & Qt::CTRL) != Qt::CTRL) + ret |= kMenuNoCommandModifier; + if ((accel_key & Qt::META) == Qt::META) + ret |= kMenuControlModifier; + } +#else + if ((accel_key & Qt::CTRL) == Qt::CTRL) + ret |= (dontSwap ? NSControlKeyMask : NSCommandKeyMask); + if ((accel_key & Qt::META) == Qt::META) + ret |= (dontSwap ? NSCommandKeyMask : NSControlKeyMask); + if ((accel_key & Qt::ALT) == Qt::ALT) + ret |= NSAlternateKeyMask; + if ((accel_key & Qt::SHIFT) == Qt::SHIFT) + ret |= NSShiftKeyMask; +#endif + return ret; +} + static bool actualMenuItemVisibility(const QMenuBarPrivate::QMacMenuBarPrivate *mbp, const QMacMenuAction *action) { @@ -525,15 +558,7 @@ static bool qt_mac_auto_apple_menu(MenuCommand cmd) static void qt_mac_get_accel(quint32 accel_key, quint32 *modif, quint32 *key) { if (modif) { - *modif = 0; - if ((accel_key & Qt::CTRL) != Qt::CTRL) - *modif |= kMenuNoCommandModifier; - if ((accel_key & Qt::META) == Qt::META) - *modif |= kMenuControlModifier; - if ((accel_key & Qt::ALT) == Qt::ALT) - *modif |= kMenuOptionModifier; - if ((accel_key & Qt::SHIFT) == Qt::SHIFT) - *modif |= kMenuShiftModifier; + *modif = constructModifierMask(accel_key); } accel_key &= ~(Qt::MODIFIER_MASK | Qt::UNICODE_ACCEL); @@ -928,14 +953,14 @@ static QKeySequence qt_mac_menu_merge_accel(QMacMenuAction *action) ret = action->action->shortcut(); #ifndef QT_MAC_USE_COCOA else if (action->command == kHICommandPreferences) - ret = QKeySequence(Qt::CTRL+Qt::Key_Comma); + ret = QKeySequence(QKeySequence::Preferences); else if (action->command == kHICommandQuit) - ret = QKeySequence(Qt::CTRL+Qt::Key_Q); + ret = QKeySequence(QKeySequence::Quit); #else else if (action->menuItem == [loader preferencesMenuItem]) - ret = QKeySequence(Qt::CTRL+Qt::Key_Comma); + ret = QKeySequence(QKeySequence::Preferences); else if (action->menuItem == [loader quitMenuItem]) - ret = QKeySequence(Qt::CTRL+Qt::Key_Q); + ret = QKeySequence(QKeySequence::Quit); #endif return ret; } @@ -1220,58 +1245,21 @@ QMenuPrivate::QMacMenuPrivate::addAction(QMacMenuAction *action, QMacMenuAction NSString *keySequenceToKeyEqivalent(const QKeySequence &accel) { quint32 accel_key = (accel[0] & ~(Qt::MODIFIER_MASK | Qt::UNICODE_ACCEL)); - unichar keyEquiv[1] = { 0 }; - if (accel_key == Qt::Key_Return) - keyEquiv[0] = kReturnCharCode; - else if (accel_key == Qt::Key_Enter) - keyEquiv[0] = kEnterCharCode; - else if (accel_key == Qt::Key_Tab) - keyEquiv[0] = kTabCharCode; - else if (accel_key == Qt::Key_Backspace) - keyEquiv[0] = kBackspaceCharCode; - else if (accel_key == Qt::Key_Delete) - keyEquiv[0] = NSDeleteFunctionKey; - else if (accel_key == Qt::Key_Escape) - keyEquiv[0] = kEscapeCharCode; - else if (accel_key == Qt::Key_PageUp) - keyEquiv[0] = NSPageUpFunctionKey; - else if (accel_key == Qt::Key_PageDown) - keyEquiv[0] = NSPageDownFunctionKey; - else if (accel_key == Qt::Key_Up) - keyEquiv[0] = NSUpArrowFunctionKey; - else if (accel_key == Qt::Key_Down) - keyEquiv[0] = NSDownArrowFunctionKey; - else if (accel_key == Qt::Key_Left) - keyEquiv[0] = NSLeftArrowFunctionKey; - else if (accel_key == Qt::Key_Right) - keyEquiv[0] = NSRightArrowFunctionKey; - else if (accel_key == Qt::Key_CapsLock) - keyEquiv[0] = kMenuCapsLockGlyph; // ### Cocoa has no equivalent - else if (accel_key >= Qt::Key_F1 && accel_key <= Qt::Key_F15) - keyEquiv[0] = (accel_key - Qt::Key_F1) + NSF1FunctionKey; - else if (accel_key == Qt::Key_Home) - keyEquiv[0] = NSHomeFunctionKey; - else if (accel_key == Qt::Key_End) - keyEquiv[0] = NSEndFunctionKey; - else - keyEquiv[0] = unichar(QChar(accel_key).toLower().unicode()); - return [NSString stringWithCharacters:keyEquiv length:1]; + extern QChar qt_macSymbolForQtKey(int key); // qkeysequence.cpp + QChar keyEquiv = qt_macSymbolForQtKey(accel_key); + if (keyEquiv.isNull()) { + if (accel_key >= Qt::Key_F1 && accel_key <= Qt::Key_F15) + keyEquiv = (accel_key - Qt::Key_F1) + NSF1FunctionKey; + else + keyEquiv = unichar(QChar(accel_key).toLower().unicode()); + } + return [NSString stringWithCharacters:&keyEquiv.unicode() length:1]; } // return the cocoa modifier mask for the QKeySequence (currently only looks at the first one). NSUInteger keySequenceModifierMask(const QKeySequence &accel) { - NSUInteger ret = 0; - quint32 accel_key = accel[0]; - if ((accel_key & Qt::CTRL) == Qt::CTRL) - ret |= NSCommandKeyMask; - if ((accel_key & Qt::META) == Qt::META) - ret |= NSControlKeyMask; - if ((accel_key & Qt::ALT) == Qt::ALT) - ret |= NSAlternateKeyMask; - if ((accel_key & Qt::SHIFT) == Qt::SHIFT) - ret |= NSShiftKeyMask; - return ret; + return constructModifierMask(accel[0]); } void -- cgit v0.12