/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the test suite of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ** 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 General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** 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-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE class QMainWindow; class QTextEdit; QT_END_NAMESPACE class TestEdit : public QTextEdit { Q_OBJECT public: TestEdit(QWidget *parent, const char *name) : QTextEdit(parent) { setObjectName(name); } protected: bool event(QEvent *e) override { // Make testedit allow any Ctrl+Key as shortcut if (e->type() == QEvent::ShortcutOverride) { QKeyEvent *ke = static_cast(e); if (ke->modifiers() == Qt::ControlModifier && ke->key() > Qt::Key_Any && ke->key() < Qt::Key_ydiaeresis) { ke->ignore(); return true; } } // If keypress not processed as normal, check for // Ctrl+Key event, and input custom string for // result comparison. if (e->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast(e); if (ke->modifiers() && ke->key() > Qt::Key_Any && ke->key() < Qt::Key_ydiaeresis) { const QChar c = QLatin1Char(char(ke->key())); if (ke->modifiers() == Qt::ControlModifier) insertPlainText(QLatin1String("')); else if (ke->modifiers() == Qt::AltModifier) insertPlainText(QLatin1String("')); else if (ke->modifiers() == Qt::ShiftModifier) insertPlainText(QLatin1String("')); return true; } } return QTextEdit::event(e); } }; class MainWindow : public QMainWindow { public: MainWindow(); TestEdit *testEdit() const { return m_testEdit; } private: TestEdit *m_testEdit; }; class tst_QShortcut : public QObject { Q_OBJECT public: enum Action { SetupAccel, TestAccel, ClearAll, TestEnd }; Q_ENUM(Action) enum Widget { NoWidget, TriggerSlot1, TriggerSlot2, TriggerSlot3, TriggerSlot4, TriggerSlot5, TriggerSlot6, TriggerSlot7, }; Q_ENUM(Widget) enum Result { NoResult, Slot1Triggered, Slot2Triggered, Slot3Triggered, Slot4Triggered, Slot5Triggered, Slot6Triggered, Slot7Triggered, SentKeyEvent, Ambiguous } currentResult = NoResult; Q_ENUM(Result) public slots: void slotTrig1() { currentResult = Slot1Triggered; } void slotTrig2() { currentResult = Slot2Triggered; } void slotTrig3() { currentResult = Slot3Triggered; } void slotTrig4() { currentResult = Slot4Triggered; } void slotTrig5() { currentResult = Slot5Triggered; } void slotTrig6() { currentResult = Slot6Triggered; } void slotTrig7() { currentResult = Slot7Triggered; } void ambigSlot1() { currentResult = Ambiguous; ambigResult = Slot1Triggered; } void ambigSlot2() { currentResult = Ambiguous; ambigResult = Slot2Triggered; } void ambigSlot3() { currentResult = Ambiguous; ambigResult = Slot3Triggered; } void ambigSlot4() { currentResult = Ambiguous; ambigResult = Slot4Triggered; } void ambigSlot5() { currentResult = Ambiguous; ambigResult = Slot5Triggered; } void ambigSlot6() { currentResult = Ambiguous; ambigResult = Slot6Triggered; } void ambigSlot7() { currentResult = Ambiguous; ambigResult = Slot7Triggered; } private slots: void cleanup(); void pmf_connect(); void number_data(); void number(); void text_data(); void text(); void disabledItems(); void ambiguousItems(); void ambiguousRotation(); void keypressConsumption(); void unicodeCompare(); void context(); void duplicatedShortcutOverride(); void shortcutToFocusProxy(); void deleteLater(); void keys(); protected: static Qt::KeyboardModifiers toButtons( int key ); void defElements(); QShortcut *setupShortcut(QWidget *parent, const QString &name, const QKeySequence &ks, Qt::ShortcutContext context = Qt::WindowShortcut); QShortcut *setupShortcut(QWidget *parent, const QString &name, Widget testWidget, const QKeySequence &ks, Qt::ShortcutContext context = Qt::WindowShortcut); static void sendKeyEvents(QWidget *w, int k1, QChar c1 = {}, int k2 = 0, QChar c2 = {}, int k3 = 0, QChar c3 = {}, int k4 = 0, QChar c4 = {}); void sendKeyEvents(QWidget *w, QKeyCombination k1, QChar c1 = {}) { sendKeyEvents(w, k1.toCombined(), c1); } void testElement(); Result ambigResult; QScopedPointer mainWindow; }; MainWindow::MainWindow() { setWindowFlags(Qt::X11BypassWindowManagerHint); m_testEdit = new TestEdit(this, "test_edit"); setCentralWidget(m_testEdit); setFixedSize(200, 200); } void tst_QShortcut::cleanup() { QVERIFY(QApplication::topLevelWidgets().size() <= 1); // The data driven tests keep a widget around } void tst_QShortcut::pmf_connect() { class MyObject : public QObject { public: using QObject::QObject; void onActivated() { ++activated; } void onAmbiguous() { ++ambiguous; } void reset() { activated = 0; ambiguous = 0; } int activated = 0; int ambiguous = 0; } myObject; QWidget parent; auto runCheck = [&myObject](QShortcut *sc, int activated, int ambiguous) { myObject.reset(); sc->activated(); sc->activatedAmbiguously(); delete sc; QCOMPARE(myObject.activated, activated); QCOMPARE(myObject.ambiguous, ambiguous); }; runCheck(new QShortcut(QKeySequence(), &parent, [&myObject]() { ++myObject.activated; }), 1, 0); runCheck(new QShortcut(QKeySequence(), &parent, &myObject, &MyObject::onActivated), 1, 0); runCheck(new QShortcut(QKeySequence(), &parent, &myObject, &MyObject::onActivated, &MyObject::onAmbiguous), 1, 1); runCheck(new QShortcut(QKeySequence(), &parent, &myObject, &MyObject::onActivated, &myObject, &MyObject::onAmbiguous), 1, 1); } Qt::KeyboardModifiers tst_QShortcut::toButtons( int key ) { Qt::KeyboardModifiers result = Qt::NoModifier; if ( key & Qt::SHIFT ) result |= Qt::ShiftModifier; if ( key & Qt::CTRL ) result |= Qt::ControlModifier; if ( key & Qt::META ) result |= Qt::MetaModifier; if ( key & Qt::ALT ) result |= Qt::AltModifier; return result; } void tst_QShortcut::defElements() { QTest::addColumn("action"); QTest::addColumn("testWidget"); QTest::addColumn("txt"); QTest::addColumn("k1"); QTest::addColumn("c1"); QTest::addColumn("k2"); QTest::addColumn("c2"); QTest::addColumn("k3"); QTest::addColumn("c3"); QTest::addColumn("k4"); QTest::addColumn("c4"); QTest::addColumn("result"); } void tst_QShortcut::number() { // We expect a failure on these tests, until QtTestKeyboard is // fixed to do real platform dependent keyboard simulations if (QTest::currentDataTag() == QString("N006a:Shift+Tab - [BackTab]") || QTest::currentDataTag() == QString("N006b:Shift+Tab - [Shift+BackTab]")) QEXPECT_FAIL("", "FLAW IN QTESTKEYBOARD: Keyboard events not passed through " "platform dependent key handling code", Continue); testElement(); } void tst_QShortcut::text() { testElement(); } QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED // ------------------------------------------------------------------ // Number Elements -------------------------------------------------- // ------------------------------------------------------------------ void tst_QShortcut::number_data() { defElements(); // Clear all QTest::newRow("N00 - clear") << ClearAll << NoWidget <setObjectName(name); name = QLatin1String("pushbutton-2"); m_pb2 = new QPushButton(name, this); m_pb2->setObjectName(name); auto hLayout = new QHBoxLayout(this); hLayout->addWidget(m_pb1); hLayout->addWidget(m_pb2); hLayout->addStretch(); } // ------------------------------------------------------------------ // Disabled Elements ------------------------------------------------ // ------------------------------------------------------------------ void tst_QShortcut::disabledItems() { if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) QSKIP("Wayland: This fails. Figure out why."); ButtonWidget mainW; mainW.setWindowTitle(QTest::currentTestFunction()); mainW.show(); mainW.activateWindow(); QVERIFY(QTest::qWaitForWindowActive(&mainW)); /* Testing Disabled Shortcuts Qt::Key_M on slot1 Shift + Qt::Key_M on slot1 Qt::Key_M on slot2 (disabled) Shift + Qt::Key_M on slot2 (disabled) */ // Setup two identical shortcuts on different pushbuttons auto pb1 = mainW.pushButton1(); auto pb2 = mainW.pushButton2(); const QKeyCombination shiftM = Qt::SHIFT | Qt::Key_M; QShortcut *cut1 = setupShortcut(pb1, "shortcut1-pb1", TriggerSlot1, QKeySequence(Qt::Key_M)); QShortcut *cut2 = setupShortcut(pb1, "shortcut2-pb1", TriggerSlot1, QKeySequence(shiftM)); QShortcut *cut3 = setupShortcut(pb2, "shortcut3-pb2", TriggerSlot2, QKeySequence(Qt::Key_M)); QShortcut *cut4 = setupShortcut(pb2, "shortcut4-pb2", TriggerSlot2, QKeySequence(shiftM)); cut3->setEnabled(false); cut4->setEnabled(false); currentResult = NoResult; sendKeyEvents(&mainW, Qt::Key_M, 'm'); QCOMPARE(currentResult, Slot1Triggered); currentResult = NoResult; sendKeyEvents(&mainW, shiftM, 'M'); QCOMPARE(currentResult, Slot1Triggered); cut2->setEnabled(false); cut4->setEnabled(true); /* Testing Disabled Shortcuts Qt::Key_M on slot1 Shift + Qt::Key_M on slot1 (disabled) Qt::Key_M on slot2 (disabled) Shift + Qt::Key_M on slot2 */ currentResult = NoResult; sendKeyEvents(&mainW, Qt::Key_M, 'm' ); QCOMPARE( currentResult, Slot1Triggered ); currentResult = NoResult; sendKeyEvents(&mainW, shiftM, 'M' ); QCOMPARE( currentResult, Slot2Triggered ); /* Testing Disabled Accel Qt::Key_F5 on slot1 Shift + Qt::Key_F5 on slot2 (disabled) */ qDeleteAll(mainW.findChildren()); const QKeyCombination shiftF5 = Qt::SHIFT | Qt::Key_F5; cut1 = setupShortcut(pb1, "shortcut1-pb1", TriggerSlot1, QKeySequence(Qt::Key_F5)); cut4 = setupShortcut(pb2, "shortcut4-pb2", TriggerSlot2, QKeySequence(shiftF5)); cut1->setEnabled(true); cut4->setEnabled(false); currentResult = NoResult; sendKeyEvents(&mainW, Qt::Key_F5); QCOMPARE( currentResult, Slot1Triggered ); currentResult = NoResult; sendKeyEvents(&mainW, shiftF5); QCOMPARE( currentResult, NoResult ); } // ------------------------------------------------------------------ // Ambiguous Elements ----------------------------------------------- // ------------------------------------------------------------------ void tst_QShortcut::ambiguousRotation() { if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) QSKIP("Wayland: This fails. Figure out why."); MainWindow mainW; const QString name = QLatin1String(QTest::currentTestFunction()); mainW.setWindowTitle(name); mainW.show(); mainW.activateWindow(); QVERIFY(QTest::qWaitForWindowActive(&mainW)); /* Testing Shortcut rotation scheme Ctrl + Qt::Key_A on slot1 (disabled) Ctrl + Qt::Key_A on slot2 (disabled) Ctrl + Qt::Key_A on slot3 Ctrl + Qt::Key_A on slot4 Ctrl + Qt::Key_A on slot5 (disabled) Ctrl + Qt::Key_A on slot6 Ctrl + Qt::Key_A on slot7 (disabled) */ const QKeyCombination ctrlA = Qt::CTRL | Qt::Key_A; QKeySequence ctrlA_Sequence(ctrlA); QShortcut *cut1 = setupShortcut(&mainW, name, TriggerSlot1, ctrlA_Sequence); QShortcut *cut2 = setupShortcut(&mainW, name, TriggerSlot2, ctrlA_Sequence); QShortcut *cut3 = setupShortcut(&mainW, name, TriggerSlot3, ctrlA_Sequence); QShortcut *cut4 = setupShortcut(&mainW, name, TriggerSlot4, ctrlA_Sequence); QShortcut *cut5 = setupShortcut(&mainW, name, TriggerSlot5, ctrlA_Sequence); QShortcut *cut6 = setupShortcut(&mainW, name, TriggerSlot6, ctrlA_Sequence); QShortcut *cut7 = setupShortcut(&mainW, name, TriggerSlot7, ctrlA_Sequence); cut1->setEnabled(false); cut2->setEnabled(false); cut5->setEnabled(false); cut7->setEnabled(false); // Test proper rotation // Start on first // Go to last // Go back to first // Continue... currentResult = NoResult; ambigResult = NoResult; sendKeyEvents(&mainW, ctrlA); QCOMPARE(currentResult, Ambiguous); QCOMPARE(ambigResult, Slot3Triggered); currentResult = NoResult; ambigResult = NoResult; sendKeyEvents(&mainW, ctrlA); QCOMPARE(currentResult, Ambiguous); QCOMPARE(ambigResult, Slot4Triggered); currentResult = NoResult; ambigResult = NoResult; sendKeyEvents(&mainW, ctrlA); QCOMPARE(currentResult, Ambiguous); QCOMPARE(ambigResult, Slot6Triggered); currentResult = NoResult; ambigResult = NoResult; sendKeyEvents(&mainW, ctrlA); QCOMPARE(currentResult, Ambiguous); QCOMPARE(ambigResult, Slot3Triggered); currentResult = NoResult; ambigResult = NoResult; sendKeyEvents(&mainW, ctrlA); QCOMPARE(currentResult, Ambiguous); QCOMPARE(ambigResult, Slot4Triggered); currentResult = NoResult; ambigResult = NoResult; sendKeyEvents(&mainW, ctrlA); QCOMPARE(currentResult, Ambiguous); QCOMPARE(ambigResult, Slot6Triggered); /* Testing Shortcut rotation scheme Ctrl + Qt::Key_A on slot1 Ctrl + Qt::Key_A on slot2 Ctrl + Qt::Key_A on slot3 (disabled) Ctrl + Qt::Key_A on slot4 (disabled) Ctrl + Qt::Key_A on slot5 Ctrl + Qt::Key_A on slot6 (disabled) Ctrl + Qt::Key_A on slot7 */ cut1->setEnabled(true); cut2->setEnabled(true); cut5->setEnabled(true); cut7->setEnabled(true); cut3->setEnabled(false); cut4->setEnabled(false); cut6->setEnabled(false); currentResult = NoResult; ambigResult = NoResult; sendKeyEvents(&mainW, ctrlA); QCOMPARE(currentResult, Ambiguous); QCOMPARE(ambigResult, Slot1Triggered); currentResult = NoResult; ambigResult = NoResult; sendKeyEvents(&mainW, ctrlA); QCOMPARE(currentResult, Ambiguous); QCOMPARE(ambigResult, Slot2Triggered); currentResult = NoResult; ambigResult = NoResult; sendKeyEvents(&mainW, ctrlA); QCOMPARE(currentResult, Ambiguous); QCOMPARE(ambigResult, Slot5Triggered); currentResult = NoResult; ambigResult = NoResult; sendKeyEvents(&mainW, ctrlA); QCOMPARE(currentResult, Ambiguous); QCOMPARE(ambigResult, Slot7Triggered); currentResult = NoResult; ambigResult = NoResult; sendKeyEvents(&mainW, ctrlA); QCOMPARE(currentResult, Ambiguous); QCOMPARE(ambigResult, Slot1Triggered); currentResult = NoResult; ambigResult = NoResult; sendKeyEvents(&mainW, ctrlA); QCOMPARE(currentResult, Ambiguous); QCOMPARE(ambigResult, Slot2Triggered); currentResult = NoResult; ambigResult = NoResult; sendKeyEvents(&mainW, ctrlA); QCOMPARE(currentResult, Ambiguous); QCOMPARE(ambigResult, Slot5Triggered); currentResult = NoResult; ambigResult = NoResult; sendKeyEvents(&mainW, ctrlA); QCOMPARE(currentResult, Ambiguous); QCOMPARE(ambigResult, Slot7Triggered); } void tst_QShortcut::ambiguousItems() { if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) QSKIP("Wayland: This fails. Figure out why."); ButtonWidget mainW; mainW.setWindowTitle(QTest::currentTestFunction()); mainW.show(); mainW.activateWindow(); QVERIFY(QTest::qWaitForWindowActive(&mainW)); /* Testing Ambiguous Shortcuts Qt::Key_M on Pushbutton 1 Qt::Key_M on Pushbutton 2 */ // Setup two identical shortcuts on different pushbuttons auto pb1 = mainW.pushButton1(); auto pb2 = mainW.pushButton2(); setupShortcut(pb1, "shortcut1-pb1", TriggerSlot1, QKeySequence(Qt::Key_M)); setupShortcut(pb2, "shortcut2-pb2", TriggerSlot2, QKeySequence(Qt::Key_M)); currentResult = NoResult; sendKeyEvents(&mainW, Qt::Key_M, 'm' ); QCOMPARE( currentResult, Ambiguous ); QCOMPARE( ambigResult, Slot1Triggered ); currentResult = NoResult; sendKeyEvents(&mainW, Qt::Key_M, 'm' ); QCOMPARE( currentResult, Ambiguous ); QCOMPARE( ambigResult, Slot2Triggered ); currentResult = NoResult; sendKeyEvents(&mainW, Qt::Key_M, 'm' ); QCOMPARE( currentResult, Ambiguous ); QCOMPARE( ambigResult, Slot1Triggered ); } // ------------------------------------------------------------------ // Unicode and non-unicode Elements --------------------------------- // ------------------------------------------------------------------ void tst_QShortcut::unicodeCompare() { if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) QSKIP("Wayland: This fails. Figure out why."); ButtonWidget mainW; mainW.setWindowTitle(QTest::currentTestFunction()); mainW.show(); mainW.activateWindow(); QVERIFY(QTest::qWaitForWindowActive(&mainW)); /* Testing Unicode/non-Unicode Shortcuts Qt::Key_M on Pushbutton 1 Qt::Key_M on Pushbutton 2 */ auto pb1 = mainW.pushButton1(); auto pb2 = mainW.pushButton2(); QKeySequence ks1("Ctrl+M"); // Unicode QKeySequence ks2(Qt::CTRL | Qt::Key_M); // non-Unicode setupShortcut(pb1, "shortcut1-pb1", TriggerSlot1, ks1); setupShortcut(pb2, "shortcut2-pb2", TriggerSlot2, ks2); currentResult = NoResult; sendKeyEvents(&mainW, Qt::CTRL | Qt::Key_M); QCOMPARE( currentResult, Ambiguous ); // They _are_ ambiguous, so the QKeySequence operator== // should indicate the same QVERIFY( ks1 == ks2 ); QVERIFY( !(ks1 != ks2) ); } // ------------------------------------------------------------------ // Keypress consumption verification -------------------------------- // ------------------------------------------------------------------ void tst_QShortcut::keypressConsumption() { if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) QSKIP("Wayland: This fails. Figure out why."); MainWindow mainW; mainW.setWindowTitle(QTest::currentTestFunction()); mainW.show(); mainW.activateWindow(); QVERIFY(QTest::qWaitForWindowActive(&mainW)); auto edit = mainW.testEdit(); QKeyCombination ctrlI = Qt::CTRL | Qt::Key_I; QShortcut *cut1 = setupShortcut(edit, "shortcut1-line", TriggerSlot1, QKeySequence(ctrlI, QKeyCombination(Qt::Key_A))); QShortcut *cut2 = setupShortcut(edit, "shortcut1-line", TriggerSlot2, QKeySequence(ctrlI, QKeyCombination(Qt::Key_B))); currentResult = NoResult; ambigResult = NoResult; sendKeyEvents(edit, ctrlI); // Send key to edit QCOMPARE( currentResult, NoResult ); QCOMPARE( ambigResult, NoResult ); QCOMPARE(edit->toPlainText(), QString()); // Make sure next keypress is eaten (failing multiple keysequence) sendKeyEvents(edit, Qt::Key_C, 'c'); // Send key to edit QCOMPARE( currentResult, NoResult ); QCOMPARE( ambigResult, NoResult ); QCOMPARE(edit->toPlainText(), QString()); // Next keypress should be normal sendKeyEvents(edit, Qt::Key_C, 'c'); // Send key to edit QCOMPARE( currentResult, NoResult ); QCOMPARE( ambigResult, NoResult ); QCOMPARE(edit->toPlainText(), QString("c")); currentResult = NoResult; ambigResult = NoResult; edit->clear(); QCOMPARE(edit->toPlainText().size(), 0); cut1->setEnabled(false); cut2->setEnabled(false); // Make sure keypresses is passed on, since all multiple keysequences // with Ctrl+I are disabled sendKeyEvents(edit, Qt::CTRL | Qt::Key_I); // Send key to edit QCOMPARE( currentResult, NoResult ); QCOMPARE( ambigResult, NoResult ); QVERIFY(edit->toPlainText().endsWith("")); sendKeyEvents(edit, Qt::Key_A, 'a'); // Send key to edit QCOMPARE( currentResult, NoResult ); QCOMPARE( ambigResult, NoResult ); QVERIFY(edit->toPlainText().endsWith("a")); qDeleteAll(mainW.findChildren()); edit->clear(); QCOMPARE(edit->toPlainText().size(), 0); auto cut = setupShortcut(edit, QLatin1String("first"), QKeySequence(Qt::CTRL | Qt::Key_A)); connect(cut, &QShortcut::activated, edit, [this, edit] () { this->sendKeyEvents(edit, Qt::CTRL | Qt::Key_B); this->currentResult = tst_QShortcut::SentKeyEvent; }); // Verify reentrancy when a non-shortcut is triggered as part // of shortcut processing. currentResult = NoResult; ambigResult = NoResult; sendKeyEvents(edit, Qt::CTRL | Qt::Key_A); QCOMPARE(currentResult, SentKeyEvent); QCOMPARE(ambigResult, NoResult); QCOMPARE(edit->toPlainText(), QLatin1String("")); } // ------------------------------------------------------------------ // Context Validation ----------------------------------------------- // ------------------------------------------------------------------ void tst_QShortcut::context() { const QString name = QLatin1String(QTest::currentTestFunction()); MainWindow mainW; mainW.setWindowTitle(name + QLatin1String("_Helper")); mainW.show(); auto edit = mainW.testEdit(); QWidget myBox; myBox.setWindowTitle(name); auto other1 = new TestEdit(&myBox, "test_edit_other1"); auto other2 = new TestEdit(&myBox, "test_edit_other2"); auto layout = new QHBoxLayout(&myBox); layout->addWidget(other1); layout->addWidget(other2); myBox.show(); QVERIFY(QTest::qWaitForWindowExposed(&myBox)); setupShortcut(other1, "ActiveWindow", TriggerSlot1, QKeySequence("Alt+1"), Qt::WindowShortcut); setupShortcut(other2, "Focus", TriggerSlot2, QKeySequence("Alt+2"), Qt::WidgetShortcut); setupShortcut(edit, "Application", TriggerSlot3, QKeySequence("Alt+3"), Qt::ApplicationShortcut); currentResult = NoResult; ambigResult = NoResult; edit->clear(); other1->clear(); other2->clear(); // edit doesn't have focus, so ActiveWindow context should work // ..but Focus context shouldn't.. // Changing focus to edit should make focus context work // Application context should always work // Focus on 'other1' edit, so Active Window context should trigger other1->activateWindow(); // <--- QApplication::setActiveWindow(other1); QCOMPARE(QApplication::activeWindow(), other1->window()); QCOMPARE(QApplication::focusWidget(), static_cast(other1)); currentResult = NoResult; ambigResult = NoResult; edit->clear(); other1->clear(); other2->clear(); QCOMPARE(QApplication::focusWidget(), static_cast(other1)); sendKeyEvents(other1, Qt::ALT | Qt::Key_1); QCOMPARE(currentResult, Slot1Triggered); QCOMPARE(ambigResult, NoResult); QCOMPARE(edit->toPlainText(), QString()); QCOMPARE(other1->toPlainText(), QString()); QCOMPARE(other2->toPlainText(), QString()); // ..but not Focus context on 'other2'.. currentResult = NoResult; ambigResult = NoResult; edit->clear(); other1->clear(); other2->clear(); sendKeyEvents(other1, Qt::ALT | Qt::Key_2); QCOMPARE(currentResult, NoResult); QCOMPARE(ambigResult, NoResult); QCOMPARE(edit->toPlainText(), QString()); QCOMPARE(other1->toPlainText(), QString("")); QCOMPARE(other2->toPlainText(), QString()); // ..however, application global context on 'edit' should.. currentResult = NoResult; ambigResult = NoResult; edit->clear(); other1->clear(); other2->clear(); sendKeyEvents(other1, Qt::ALT | Qt::Key_3); QCOMPARE(currentResult, Slot3Triggered); QCOMPARE(ambigResult, NoResult); QCOMPARE(edit->toPlainText(), QString()); QCOMPARE(other1->toPlainText(), QString()); QCOMPARE(other2->toPlainText(), QString()); // Changing focus to 'other2' should make the Focus context there work other2->activateWindow(); other2->setFocus(); // ### QTRY_COMPARE(QApplication::activeWindow(), other2->window()); QCOMPARE(QApplication::focusWidget(), static_cast(other2)); currentResult = NoResult; ambigResult = NoResult; edit->clear(); other1->clear(); other2->clear(); sendKeyEvents(other2, Qt::ALT | Qt::Key_2); QCOMPARE(currentResult, Slot2Triggered); QCOMPARE(ambigResult, NoResult); QCOMPARE(edit->toPlainText(), QString()); QCOMPARE(other1->toPlainText(), QString()); QCOMPARE(other2->toPlainText(), QString()); } // QTBUG-38986, do not generate duplicated QEvent::ShortcutOverride in event processing. class OverrideCountingWidget : public QWidget { public: using QWidget::QWidget; int overrideCount = 0; bool event(QEvent *e) override { if (e->type() == QEvent::ShortcutOverride) overrideCount++; return QWidget::event(e); } }; void tst_QShortcut::duplicatedShortcutOverride() { if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) QSKIP("Wayland: This fails. Figure out why."); OverrideCountingWidget w; w.setWindowTitle(Q_FUNC_INFO); w.resize(200, 200); w.move(QGuiApplication::primaryScreen()->availableGeometry().center() - QPoint(100, 100)); w.show(); QApplication::setActiveWindow(&w); QVERIFY(QTest::qWaitForWindowActive(&w)); QTest::keyPress(w.windowHandle(), Qt::Key_A); QCoreApplication::processEvents(); QCOMPARE(w.overrideCount, 1); } QShortcut *tst_QShortcut::setupShortcut(QWidget *parent, const QString &name, const QKeySequence &ks, Qt::ShortcutContext context) { // Set up shortcut for next test auto cut = new QShortcut(ks, parent, nullptr, nullptr, context); cut->setObjectName(name); return cut; } QShortcut *tst_QShortcut::setupShortcut(QWidget *parent, const QString &name, Widget testWidget, const QKeySequence &ks, Qt::ShortcutContext context) { // Set up shortcut for next test auto cut = setupShortcut(parent, name, ks, context); switch (testWidget) { case TriggerSlot1: connect(cut, &QShortcut::activated, this, &tst_QShortcut::slotTrig1); connect(cut, &QShortcut::activatedAmbiguously, this, &tst_QShortcut::ambigSlot1); break; case TriggerSlot2: connect(cut, &QShortcut::activated, this, &tst_QShortcut::slotTrig2); connect(cut, &QShortcut::activatedAmbiguously, this, &tst_QShortcut::ambigSlot2); break; case TriggerSlot3: connect(cut, &QShortcut::activated, this, &tst_QShortcut::slotTrig3); connect(cut, &QShortcut::activatedAmbiguously, this, &tst_QShortcut::ambigSlot3); break; case TriggerSlot4: connect(cut, &QShortcut::activated, this, &tst_QShortcut::slotTrig4); connect(cut, &QShortcut::activatedAmbiguously, this, &tst_QShortcut::ambigSlot4); break; case TriggerSlot5: connect(cut, &QShortcut::activated, this, &tst_QShortcut::slotTrig5); connect(cut, &QShortcut::activatedAmbiguously, this, &tst_QShortcut::ambigSlot5); break; case TriggerSlot6: connect(cut, &QShortcut::activated, this, &tst_QShortcut::slotTrig6); connect(cut, &QShortcut::activatedAmbiguously, this, &tst_QShortcut::ambigSlot6); break; case TriggerSlot7: connect(cut, &QShortcut::activated, this, &tst_QShortcut::slotTrig7); connect(cut, &QShortcut::activatedAmbiguously, this, &tst_QShortcut::ambigSlot7); break; case NoWidget: break; } return cut; } void tst_QShortcut::sendKeyEvents(QWidget *w, int k1, QChar c1, int k2, QChar c2, int k3, QChar c3, int k4, QChar c4) { Qt::KeyboardModifiers b1 = toButtons( k1 ); Qt::KeyboardModifiers b2 = toButtons( k2 ); Qt::KeyboardModifiers b3 = toButtons( k3 ); Qt::KeyboardModifiers b4 = toButtons( k4 ); k1 &= ~Qt::MODIFIER_MASK; k2 &= ~Qt::MODIFIER_MASK; k3 &= ~Qt::MODIFIER_MASK; k4 &= ~Qt::MODIFIER_MASK; if (k1 || c1.toLatin1()) { QString c(c1.unicode() == QChar::Null ? QString() : QString(c1)); QTest::sendKeyEvent(QTest::Press, w, static_cast(k1), c, b1); QTest::sendKeyEvent(QTest::Release, w, static_cast(k1), c, b1); } if (k2 || c2.toLatin1()) { QString c(c2.unicode() == QChar::Null ? QString() : QString(c2)); QTest::sendKeyEvent(QTest::Press, w, static_cast(k2), c, b2); QTest::sendKeyEvent(QTest::Release, w, static_cast(k2), c, b2); } if (k3 || c3.toLatin1()) { QString c(c3.unicode() == QChar::Null ? QString() : QString(c3)); QTest::sendKeyEvent(QTest::Press, w, static_cast(k3), c, b3); QTest::sendKeyEvent(QTest::Release, w, static_cast(k3), c, b3); } if (k4 || c4.toLatin1()) { QString c(c4.unicode() == QChar::Null ? QString() : QString(c4)); QTest::sendKeyEvent(QTest::Press, w, static_cast(k4), c, b4); QTest::sendKeyEvent(QTest::Release, w, static_cast(k4), c, b4); } } void tst_QShortcut::testElement() { if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) QSKIP("Wayland: This fails. Figure out why."); currentResult = NoResult; QFETCH(tst_QShortcut::Action, action); QFETCH(tst_QShortcut::Widget, testWidget); QFETCH(QString, txt); QFETCH(int, k1); QFETCH(int, c1); QFETCH(int, k2); QFETCH(int, c2); QFETCH(int, k3); QFETCH(int, c3); QFETCH(int, k4); QFETCH(int, c4); QFETCH(tst_QShortcut::Result, result); auto mainWindowDeleter = qScopeGuard([&]{ if (action == TestEnd) mainWindow.reset(); }); if (mainWindow.isNull()) mainWindow.reset(new MainWindow); mainWindow->setWindowTitle(QTest::currentTestFunction()); mainWindow->show(); mainWindow->activateWindow(); // Don't use QVERIFY here; the data function uses QEXPECT_FAIL, // which would result in an XPASS failure. if (!QTest::qWaitForWindowActive(mainWindow.data())) QVERIFY(false); switch (action) { case ClearAll: qDeleteAll(mainWindow->findChildren()); break; case SetupAccel: setupShortcut(mainWindow.data(), txt, testWidget, txt.isEmpty() ? QKeySequence(k1, k2, k3, k4) : QKeySequence::fromString(txt)); break; case TestAccel: sendKeyEvents(mainWindow.data(), k1, char16_t(c1), k2, char16_t(c2), k3, char16_t(c3), k4, char16_t(c4)); QCOMPARE(currentResult, result); break; case TestEnd: // taken care of by the mainWindowDeleter break; } } void tst_QShortcut::shortcutToFocusProxy() { if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) QSKIP("Wayland: This fails. Figure out why."); QLineEdit le; QCompleter completer; QStringListModel *slm = new QStringListModel(QStringList() << "a0" << "a1" << "a2", &completer); completer.setModel(slm); completer.setCompletionMode(QCompleter::PopupCompletion); le.setCompleter(&completer); QShortcut *shortcut = new QShortcut(QKeySequence(Qt::ALT | Qt::Key_S), &le); QObject::connect(shortcut, &QShortcut::activated, &le, &QLineEdit::clear); le.setFocus(); le.show(); QVERIFY(QTest::qWaitForWindowActive(&le)); QCOMPARE(QApplication::focusWidget(), &le); QTest::keyEvent(QTest::Press, QApplication::focusWidget(), Qt::Key_A); QCOMPARE(le.text(), QString::fromLocal8Bit("a")); QTest::keyEvent(QTest::Press, QApplication::focusWidget(), Qt::Key_Alt); QTest::keyEvent(QTest::Press, QApplication::focusWidget(), Qt::Key_S, Qt::AltModifier); QCOMPARE(le.text(), QString()); } void tst_QShortcut::deleteLater() { QWidget w; QPointer sc(new QShortcut(QKeySequence(Qt::Key_1), &w)); sc->deleteLater(); QTRY_VERIFY(!sc); } void tst_QShortcut::keys() { QLineEdit le; QShortcut *sc = new QShortcut(QKeySequence::InsertParagraphSeparator, &le); QVERIFY(sc->keys().contains(QKeySequence(Qt::Key_Enter))); QVERIFY(sc->keys().contains(QKeySequence(Qt::Key_Return))); QSignalSpy spy(sc, &QShortcut::activated); le.setFocus(); le.show(); if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) QVERIFY(QTest::qWaitForWindowActive(&le)); else QTRY_VERIFY(le.windowHandle()->isActive()); QCOMPARE(QApplication::focusWidget(), &le); QTest::keyEvent(QTest::Press, QApplication::focusWidget(), Qt::Key_Enter); QTRY_COMPARE(spy.count(), 1); QTest::keyEvent(QTest::Press, QApplication::focusWidget(), Qt::Key_Return); QTRY_COMPARE(spy.count(), 2); } QTEST_MAIN(tst_QShortcut) #include "tst_qshortcut.moc"