/**************************************************************************** ** ** Copyright (C) 2018 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$ ** ****************************************************************************/ // This file is included from qnsview.mm, and only used to organize the code @implementation QNSView (KeysAPI) + (Qt::KeyboardModifiers)convertKeyModifiers:(ulong)modifierFlags { const bool dontSwapCtrlAndMeta = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta); Qt::KeyboardModifiers qtMods =Qt::NoModifier; if (modifierFlags & NSEventModifierFlagShift) qtMods |= Qt::ShiftModifier; if (modifierFlags & NSEventModifierFlagControl) qtMods |= dontSwapCtrlAndMeta ? Qt::ControlModifier : Qt::MetaModifier; if (modifierFlags & NSEventModifierFlagOption) qtMods |= Qt::AltModifier; if (modifierFlags & NSEventModifierFlagCommand) qtMods |= dontSwapCtrlAndMeta ? Qt::MetaModifier : Qt::ControlModifier; if (modifierFlags & NSEventModifierFlagNumericPad) qtMods |= Qt::KeypadModifier; return qtMods; } @end @implementation QNSView (Keys) - (int)convertKeyCode:(QChar)keyChar { return qt_mac_cocoaKey2QtKey(keyChar); } - (bool)handleKeyEvent:(NSEvent *)nsevent eventType:(int)eventType { ulong timestamp = [nsevent timestamp] * 1000; ulong nativeModifiers = [nsevent modifierFlags]; Qt::KeyboardModifiers modifiers = [QNSView convertKeyModifiers: nativeModifiers]; NSString *charactersIgnoringModifiers = [nsevent charactersIgnoringModifiers]; NSString *characters = [nsevent characters]; if (m_inputSource != characters) { [m_inputSource release]; m_inputSource = [characters retain]; } // There is no way to get the scan code from carbon/cocoa. But we cannot // use the value 0, since it indicates that the event originates from somewhere // else than the keyboard. quint32 nativeScanCode = 1; quint32 nativeVirtualKey = [nsevent keyCode]; QChar ch = QChar::ReplacementCharacter; int keyCode = Qt::Key_unknown; // If a dead key occurs as a result of pressing a key combination then // characters will have 0 length, but charactersIgnoringModifiers will // have a valid character in it. This enables key combinations such as // ALT+E to be used as a shortcut with an English keyboard even though // pressing ALT+E will give a dead key while doing normal text input. if ([characters length] != 0 || [charactersIgnoringModifiers length] != 0) { auto ctrlOrMetaModifier = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta) ? Qt::ControlModifier : Qt::MetaModifier; if (((modifiers & ctrlOrMetaModifier) || (modifiers & Qt::AltModifier)) && ([charactersIgnoringModifiers length] != 0)) ch = QChar([charactersIgnoringModifiers characterAtIndex:0]); else if ([characters length] != 0) ch = QChar([characters characterAtIndex:0]); keyCode = [self convertKeyCode:ch]; } // we will send a key event unless the input method sets m_sendKeyEvent to false m_sendKeyEvent = true; QString text; // ignore text for the U+F700-U+F8FF range. This is used by Cocoa when // delivering function keys (e.g. arrow keys, backspace, F1-F35, etc.) if (!(modifiers & (Qt::ControlModifier | Qt::MetaModifier)) && (ch.unicode() < 0xf700 || ch.unicode() > 0xf8ff)) text = QString::fromNSString(characters); QWindow *window = [self topLevelWindow]; // Popups implicitly grab key events; forward to the active popup if there is one. // This allows popups to e.g. intercept shortcuts and close the popup in response. if (QCocoaWindow *popup = QCocoaIntegration::instance()->activePopupWindow()) { if (!popup->window()->flags().testFlag(Qt::ToolTip)) window = popup->window(); } if (eventType == QEvent::KeyPress) { if (m_composingText.isEmpty()) { m_sendKeyEvent = !QWindowSystemInterface::handleShortcutEvent(window, timestamp, keyCode, modifiers, nativeScanCode, nativeVirtualKey, nativeModifiers, text, [nsevent isARepeat], 1); // Handling a shortcut may result in closing the window if (!m_platformWindow) return true; } QObject *fo = m_platformWindow->window()->focusObject(); if (m_sendKeyEvent && fo) { QInputMethodQueryEvent queryEvent(Qt::ImEnabled | Qt::ImHints); if (QCoreApplication::sendEvent(fo, &queryEvent)) { bool imEnabled = queryEvent.value(Qt::ImEnabled).toBool(); Qt::InputMethodHints hints = static_cast(queryEvent.value(Qt::ImHints).toUInt()); if (imEnabled && !(hints & Qt::ImhDigitsOnly || hints & Qt::ImhFormattedNumbersOnly || hints & Qt::ImhHiddenText)) { // pass the key event to the input method. note that m_sendKeyEvent may be set to false during this call m_currentlyInterpretedKeyEvent = nsevent; [self interpretKeyEvents:@[nsevent]]; m_currentlyInterpretedKeyEvent = 0; } } } if (m_resendKeyEvent) m_sendKeyEvent = true; } bool accepted = true; if (m_sendKeyEvent && m_composingText.isEmpty()) { QWindowSystemInterface::handleExtendedKeyEvent(window, timestamp, QEvent::Type(eventType), keyCode, modifiers, nativeScanCode, nativeVirtualKey, nativeModifiers, text, [nsevent isARepeat], 1, false); accepted = QWindowSystemInterface::flushWindowSystemEvents(); } m_sendKeyEvent = false; m_resendKeyEvent = false; return accepted; } - (void)keyDown:(NSEvent *)nsevent { if ([self isTransparentForUserInput]) return [super keyDown:nsevent]; const bool accepted = [self handleKeyEvent:nsevent eventType:int(QEvent::KeyPress)]; // When Qt is used to implement a plugin for a native application we // want to propagate unhandled events to other native views. However, // Qt does not always set the accepted state correctly (in particular // for return key events), so do this for plugin applications only // to prevent incorrect forwarding in the general case. const bool shouldPropagate = QCoreApplication::testAttribute(Qt::AA_PluginApplication) && !accepted; // Track keyDown acceptance/forward state for later acceptance of the keyUp. if (!shouldPropagate) m_acceptedKeyDowns.insert([nsevent keyCode]); if (shouldPropagate) [super keyDown:nsevent]; } - (void)keyUp:(NSEvent *)nsevent { if ([self isTransparentForUserInput]) return [super keyUp:nsevent]; const bool keyUpAccepted = [self handleKeyEvent:nsevent eventType:int(QEvent::KeyRelease)]; // Propagate the keyUp if neither Qt accepted it nor the corresponding KeyDown was // accepted. Qt text controls wil often not use and ignore keyUp events, but we // want to avoid propagating unmatched keyUps. const bool keyDownAccepted = m_acceptedKeyDowns.remove([nsevent keyCode]); if (!keyUpAccepted && !keyDownAccepted) [super keyUp:nsevent]; } - (void)cancelOperation:(id)sender { Q_UNUSED(sender); NSEvent *currentEvent = [NSApp currentEvent]; if (!currentEvent || currentEvent.type != NSEventTypeKeyDown) return; // Handling the key event may recurse back here through interpretKeyEvents // (when IM is enabled), so we need to guard against that. if (currentEvent == m_currentlyInterpretedKeyEvent) return; // Send Command+Key_Period and Escape as normal keypresses so that // the key sequence is delivered through Qt. That way clients can // intercept the shortcut and override its effect. [self handleKeyEvent:currentEvent eventType:int(QEvent::KeyPress)]; } - (void)flagsChanged:(NSEvent *)nsevent { ulong timestamp = [nsevent timestamp] * 1000; ulong modifiers = [nsevent modifierFlags]; Qt::KeyboardModifiers qmodifiers = [QNSView convertKeyModifiers:modifiers]; // calculate the delta and remember the current modifiers for next time static ulong m_lastKnownModifiers; ulong lastKnownModifiers = m_lastKnownModifiers; ulong delta = lastKnownModifiers ^ modifiers; m_lastKnownModifiers = modifiers; struct qt_mac_enum_mapper { ulong mac_mask; Qt::Key qt_code; }; static qt_mac_enum_mapper modifier_key_symbols[] = { { NSEventModifierFlagShift, Qt::Key_Shift }, { NSEventModifierFlagControl, Qt::Key_Meta }, { NSEventModifierFlagCommand, Qt::Key_Control }, { NSEventModifierFlagOption, Qt::Key_Alt }, { NSEventModifierFlagCapsLock, Qt::Key_CapsLock }, { 0ul, Qt::Key_unknown } }; for (int i = 0; modifier_key_symbols[i].mac_mask != 0u; ++i) { uint mac_mask = modifier_key_symbols[i].mac_mask; if ((delta & mac_mask) == 0u) continue; Qt::Key qtCode = modifier_key_symbols[i].qt_code; if (qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)) { if (qtCode == Qt::Key_Meta) qtCode = Qt::Key_Control; else if (qtCode == Qt::Key_Control) qtCode = Qt::Key_Meta; } QWindowSystemInterface::handleKeyEvent(m_platformWindow->window(), timestamp, (lastKnownModifiers & mac_mask) ? QEvent::KeyRelease : QEvent::KeyPress, qtCode, qmodifiers ^ [QNSView convertKeyModifiers:mac_mask]); } } @end