// SPDX-License-Identifier: MIT #include "panelcontroller.h" #include #include #include #include #include #include #include using namespace Qt::StringLiterals; class PanelWindow : public QQuickWindow { public: int delivered = 0; bool event(QEvent *event) override { if (event->type() == QEvent::Wheel || event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease || event->type() == QEvent::MouseButtonDblClick || event->type() == QEvent::MouseMove) { ++delivered; return true; } return QQuickWindow::event(event); } }; class PanelControllerTest : public QObject { Q_OBJECT private: static void setup(PanelController &controller, PanelWindow &window, QJSEngine &engine) { window.resize(400, 40); controller.setParentItem(window.contentItem()); controller.setProperty("active", true); controller.setProperty("locateArea", QVariant::fromValue(engine.evaluate( u"(function(x,y) { return x < 100 ? 'empty' : 'tasks'; })"_s))); controller.setProperty("scrollActions", QVariantMap{{u"empty"_s, u"volume"_s}, {u"tasks"_s, u"brightness"_s}}); } static void wheel(PanelWindow &window, int delta, int x = 20, bool pixels = false) { QWheelEvent event(QPointF(x, 10), QPointF(x, 10), pixels ? QPoint(0, delta) : QPoint(), pixels ? QPoint() : QPoint(0, delta), Qt::NoButton, Qt::NoModifier, Qt::NoScrollPhase, false); QCoreApplication::sendEvent(&window, &event); } static void mouse(PanelWindow &window, QEvent::Type type, int x, Qt::MouseButton button = Qt::LeftButton) { QMouseEvent event(type, QPointF(x, 10), QPointF(x, 10), type == QEvent::MouseMove ? Qt::NoButton : button, type == QEvent::MouseButtonRelease ? Qt::NoButton : button, Qt::NoModifier); QCoreApplication::sendEvent(&window, &event); } private Q_SLOTS: void clicksAndScrollsShareWidgetAreas() { QJSEngine engine; PanelWindow window; PanelController controller; setup(controller, window, engine); controller.setLocateArea(engine.evaluate(u"(function(x,y) { return 'launcher'; })"_s)); controller.setProperty("scrollActions", QVariantMap{{u"launcher"_s, u"brightness"_s}, {u"empty"_s, u"volume"_s}}); controller.setProperty("clickActions", QVariantMap{{u"leftClick"_s, u"mute"_s}, {u"leftDoubleClick"_s, u"overview"_s}}); QSignalSpy scrolling(&controller, &PanelController::actionRequested); QSignalSpy clicking(&controller, &PanelController::clickRequested); wheel(window, 120); QCOMPARE(scrolling.count(), 1); QCOMPARE(scrolling.first(), QVariantList({u"brightness"_s, 1})); mouse(window, QEvent::MouseButtonPress, 20); mouse(window, QEvent::MouseButtonRelease, 20); mouse(window, QEvent::MouseButtonPress, 20); mouse(window, QEvent::MouseButtonDblClick, 20); mouse(window, QEvent::MouseButtonRelease, 20); QCOMPARE(clicking.count(), 0); QCOMPARE(window.delivered, 5); // Plasma handles both clicks, including margin forwarding. } void wheelOverridesAndNormalBehaviour() { QJSEngine engine; PanelWindow window; PanelController controller; setup(controller, window, engine); QSignalSpy actions(&controller, &PanelController::actionRequested); wheel(window, 120); wheel(window, -120, 150); QCOMPARE(actions.count(), 2); QCOMPARE(actions.at(0), QVariantList({u"volume"_s, 1})); QCOMPARE(actions.at(1), QVariantList({u"brightness"_s, -1})); QCOMPARE(window.delivered, 0); controller.setProperty("scrollActions", QVariantMap{{u"empty"_s, u"normal"_s}}); wheel(window, 120); QCOMPARE(window.delivered, 1); QCOMPARE(actions.count(), 2); } void smoothScrollingAndAreaChanges() { QJSEngine engine; PanelWindow window; PanelController controller; setup(controller, window, engine); QSignalSpy actions(&controller, &PanelController::actionRequested); wheel(window, 60); QCOMPARE(actions.count(), 0); wheel(window, 60); QCOMPARE(actions.count(), 1); wheel(window, 60); wheel(window, 60, 150); QCOMPARE(actions.count(), 1); // partial gestures cannot cross areas wheel(window, 60, 150); QCOMPARE(actions.count(), 2); wheel(window, 20, 20, true); QCOMPARE(actions.count(), 2); wheel(window, 20, 20, true); QCOMPARE(actions.count(), 3); } void onlyEmptySpaceClicksAreOverridden() { QJSEngine engine; PanelWindow window; PanelController controller; setup(controller, window, engine); controller.setProperty("clickActions", QVariantMap{{u"leftClick"_s, u"mute"_s}}); QSignalSpy actions(&controller, &PanelController::clickRequested); mouse(window, QEvent::MouseButtonPress, 20); QCOMPARE(actions.count(), 0); mouse(window, QEvent::MouseButtonRelease, 20); QCOMPARE(actions.count(), 1); QCOMPARE(actions.at(0), QVariantList({u"leftClick"_s})); QCOMPARE(window.delivered, 0); mouse(window, QEvent::MouseButtonPress, 150); mouse(window, QEvent::MouseButtonRelease, 150); mouse(window, QEvent::MouseButtonPress, 20, Qt::RightButton); mouse(window, QEvent::MouseButtonRelease, 20, Qt::RightButton); QCOMPARE(window.delivered, 4); QCOMPARE(actions.count(), 1); } void disablingDiscardsPartialScroll() { QJSEngine engine; PanelWindow window; PanelController controller; setup(controller, window, engine); QSignalSpy actions(&controller, &PanelController::actionRequested); wheel(window, 60); controller.setProperty("active", false); controller.setProperty("active", true); wheel(window, 60); QCOMPARE(actions.count(), 0); wheel(window, 60); QCOMPARE(actions.count(), 1); } void draggingCancelsClickEvenWhenReturning() { QJSEngine engine; PanelWindow window; PanelController controller; setup(controller, window, engine); controller.setProperty("clickActions", QVariantMap{{u"leftClick"_s, u"mute"_s}}); QSignalSpy actions(&controller, &PanelController::clickRequested); mouse(window, QEvent::MouseButtonPress, 20); mouse(window, QEvent::MouseMove, 80); mouse(window, QEvent::MouseMove, 20); mouse(window, QEvent::MouseButtonRelease, 20); QCOMPARE(actions.count(), 0); } void releaseOutsidePanelDoesNotClick() { QJSEngine engine; PanelWindow window; PanelController controller; setup(controller, window, engine); controller.setProperty("clickActions", QVariantMap{{u"leftClick"_s, u"mute"_s}}); QSignalSpy actions(&controller, &PanelController::clickRequested); mouse(window, QEvent::MouseButtonPress, 1); mouse(window, QEvent::MouseButtonRelease, -1); QCOMPARE(actions.count(), 0); } void disabledAndOtherWindowsAreUntouched() { QJSEngine engine; PanelWindow window; PanelWindow popup; popup.resize(400, 40); PanelController controller; setup(controller, window, engine); QSignalSpy actions(&controller, &PanelController::actionRequested); wheel(popup, 120); QCOMPARE(popup.delivered, 1); controller.setProperty("active", false); wheel(window, 120); QCOMPARE(window.delivered, 1); QCOMPARE(actions.count(), 0); controller.setParentItem(popup.contentItem()); controller.setProperty("active", true); wheel(window, 120); QCOMPARE(window.delivered, 2); wheel(popup, 120); QCOMPARE(actions.count(), 1); } void doubleClicks_data() { QTest::addColumn("button"); QTest::addColumn("singleSlot"); QTest::addColumn("doubleSlot"); QTest::addColumn("precedingPress"); QTest::newRow("left") << int(Qt::LeftButton) << u"leftClick"_s << u"leftDoubleClick"_s << false; QTest::newRow("middle") << int(Qt::MiddleButton) << u"middleClick"_s << u"middleDoubleClick"_s << false; QTest::newRow("qt-window-left") << int(Qt::LeftButton) << u"leftClick"_s << u"leftDoubleClick"_s << true; QTest::newRow("qt-window-middle") << int(Qt::MiddleButton) << u"middleClick"_s << u"middleDoubleClick"_s << true; } void doubleClicks() { QFETCH(int, button); QFETCH(QString, singleSlot); QFETCH(QString, doubleSlot); QFETCH(bool, precedingPress); const auto mouseButton = Qt::MouseButton(button); QJSEngine engine; PanelWindow window; PanelController controller; setup(controller, window, engine); controller.setProperty("clickActions", QVariantMap{{singleSlot, u"mute"_s}, {doubleSlot, u"application"_s}}); QSignalSpy actions(&controller, &PanelController::clickRequested); mouse(window, QEvent::MouseButtonPress, 20, mouseButton); mouse(window, QEvent::MouseButtonRelease, 20, mouseButton); QCOMPARE(actions.count(), 0); // QGuiApplication sends both Press and DblClick for the second down. if (precedingPress) mouse(window, QEvent::MouseButtonPress, 20, mouseButton); mouse(window, QEvent::MouseButtonDblClick, 20, mouseButton); mouse(window, QEvent::MouseButtonRelease, 20, mouseButton); QCOMPARE(actions.count(), 1); QCOMPARE(actions.at(0), QVariantList({doubleSlot})); QTest::qWait(QGuiApplication::styleHints()->mouseDoubleClickInterval() + 30); QCOMPARE(actions.count(), 1); // no single-click action after the double mouse(window, QEvent::MouseButtonPress, 20, mouseButton); mouse(window, QEvent::MouseButtonRelease, 20, mouseButton); QCOMPARE(actions.count(), 1); QTRY_COMPARE(actions.count(), 2); QCOMPARE(actions.at(1), QVariantList({singleSlot})); } void distantClicksDoNotCombine() { QJSEngine engine; PanelWindow window; PanelController controller; setup(controller, window, engine); controller.setProperty("clickActions", QVariantMap{{u"leftClick"_s, u"mute"_s}, {u"leftDoubleClick"_s, u"overview"_s}}); QSignalSpy actions(&controller, &PanelController::clickRequested); mouse(window, QEvent::MouseButtonPress, 10); mouse(window, QEvent::MouseButtonRelease, 10); mouse(window, QEvent::MouseButtonPress, 90); mouse(window, QEvent::MouseButtonRelease, 90); QTRY_COMPARE(actions.count(), 2); QCOMPARE(actions.at(0), QVariantList({u"leftClick"_s})); QCOMPARE(actions.at(1), QVariantList({u"leftClick"_s})); } void doubleOnlyAndDisablingPendingClicks() { QJSEngine engine; PanelWindow window; PanelController controller; setup(controller, window, engine); controller.setProperty("clickActions", QVariantMap{{u"leftDoubleClick"_s, u"command"_s}}); QSignalSpy actions(&controller, &PanelController::clickRequested); mouse(window, QEvent::MouseButtonPress, 20); mouse(window, QEvent::MouseButtonRelease, 20); // Some platforms deliver the second press without a DblClick event. mouse(window, QEvent::MouseButtonPress, 20); mouse(window, QEvent::MouseButtonRelease, 20); QCOMPARE(actions.count(), 1); QCOMPARE(actions.at(0), QVariantList({u"leftDoubleClick"_s})); mouse(window, QEvent::MouseButtonPress, 20); mouse(window, QEvent::MouseButtonRelease, 20); mouse(window, QEvent::MouseButtonPress, 20); mouse(window, QEvent::MouseMove, 80); mouse(window, QEvent::MouseMove, 20); mouse(window, QEvent::MouseButtonRelease, 20); QCOMPARE(actions.count(), 1); // dragging the second press cancels the double mouse(window, QEvent::MouseButtonPress, 20); mouse(window, QEvent::MouseButtonRelease, 20); controller.setProperty("active", false); QTest::qWait(QGuiApplication::styleHints()->mouseDoubleClickInterval() + 30); QCOMPARE(actions.count(), 1); } }; QTEST_MAIN(PanelControllerTest) #include "test-panelcontroller.moc"