/*
* Copyright (C) 2014-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#include
#include
#include
#include
// C++ std lib
#include
#include
#include
#include "TestItem.h"
#include
UG_USE_NAMESPACE
class CandidateItem : public QQuickItem
{
Q_OBJECT
public:
std::function touchEventHandler;
protected:
void touchEvent(QTouchEvent *event) override;
};
class tst_TouchGate : public QObject
{
Q_OBJECT
public:
tst_TouchGate(QObject *parent = nullptr);
private Q_SLOTS:
void initTestCase(); // will be called before the first test function is executed
void cleanupTestCase(); // will be called after the last test function was executed.
void init(); // called right before each and every test function is executed
void cleanup(); // called right after each and every test function is executed
private:
void onlyCandidate_passThrough();
void holdsEventsUntilGainsOwnership();
void holdsEventsUntilGainsOwnership_data();
void holdsEventsUntilGainsOwnership_repeatingTouchId();
void dontDispacthToDisabledOrInvisibleTarget();
void dontDispacthToDisabledOrInvisibleTarget_data();
private Q_SLOTS:
void disabledWhileHoldingTouch();
private:
QQuickView *createView();
TouchRegistry *touchRegistry;
QQuickView *view;
QTouchDevice *device;
};
tst_TouchGate::tst_TouchGate(QObject *parent)
: QObject(parent)
, touchRegistry(nullptr)
, view(nullptr)
, device(nullptr)
{
}
void tst_TouchGate::initTestCase()
{
if (!device) {
device = new QTouchDevice;
device->setType(QTouchDevice::TouchScreen);
QWindowSystemInterface::registerTouchDevice(device);
}
}
void tst_TouchGate::cleanupTestCase()
{
}
void tst_TouchGate::init()
{
touchRegistry = TouchRegistry::instance();
view = createView();
view->setSource(QUrl::fromLocalFile(testDataDir() + "/plugins/Ubuntu/Gestures/touchGateExample.qml"));
view->show();
QVERIFY(QTest::qWaitForWindowExposed(view));
QVERIFY(view->rootObject() != 0);
qApp->processEvents();
}
void tst_TouchGate::cleanup()
{
delete view;
view = 0;
delete touchRegistry;
touchRegistry = nullptr;
}
QQuickView *tst_TouchGate::createView()
{
QQuickView *window = new QQuickView(0);
window->setResizeMode(QQuickView::SizeRootObjectToView);
window->resize(720, 720);
return window;
}
/*
Simplest case. TouchGate is the only candidate for the touch point, thus it
will instantly gain ownership causing the event to pass through it directly to
its child.
*/
void tst_TouchGate::onlyCandidate_passThrough()
{
TouchGate *touchGate = view->rootObject()->findChild("touchGate");
Q_ASSERT(touchGate);
TestItem *testItem = new TestItem;
testItem->setWidth(touchGate->width());
testItem->setHeight(touchGate->height());
testItem->setParentItem(view->rootObject());
testItem->setZ(0.0);
touchGate->setZ(1.0);
touchGate->setTargetItem(testItem);
QTest::touchEvent(view, device)
.press(0, QPoint(100,100));
QCOMPARE(testItem->touchEventsReceived.count(), 1);
QVERIFY(touchGate->m_storedEvents.isEmpty());
}
void tst_TouchGate::holdsEventsUntilGainsOwnership()
{
TouchGate *touchGate = view->rootObject()->findChild("touchGate");
Q_ASSERT(touchGate);
QFETCH(bool, ownershipAfterTouchEnd);
TestItem *testItem = new TestItem;
testItem->setWidth(touchGate->width());
testItem->setHeight(touchGate->height());
testItem->setParentItem(view->rootObject());
testItem->setZ(0.0);
touchGate->setZ(1.0);
touchGate->setTargetItem(testItem);
// Put it in front of touchGate
CandidateItem *candidateItem = new CandidateItem;
candidateItem->setWidth(touchGate->width());
candidateItem->setHeight(touchGate->height());
candidateItem->setParentItem(view->rootObject());
candidateItem->setZ(2.0);
candidateItem->touchEventHandler = [&](QTouchEvent* event) {
touchRegistry->addCandidateOwnerForTouch(0, candidateItem);
event->ignore();
};
QTest::touchEvent(view, device)
.press(0, QPoint(100,100));
QCOMPARE(testItem->touchEventsReceived.count(), 0);
QTest::touchEvent(view, device)
.move(0, QPoint(101,101));
QCOMPARE(testItem->touchEventsReceived.count(), 0);
if (!ownershipAfterTouchEnd) {
touchRegistry->removeCandidateOwnerForTouch(0, candidateItem);
QQuickWindowPrivate *wp = QQuickWindowPrivate::get(testItem->window());
wp->flushDelayedTouchEvent();
// TouchGate should now open its flood gates and let testItem get all
// events from touch 0 produced so far
QCOMPARE(testItem->touchEventsReceived.count(), 2);
}
QTest::touchEvent(view, device)
.release(0, QPoint(101,101));
if (ownershipAfterTouchEnd) {
QCOMPARE(testItem->touchEventsReceived.count(), 0);
touchRegistry->removeCandidateOwnerForTouch(0, candidateItem);
// TouchGate should now open its flood gates and let testItem get all
// events from touch 0 produced so far
}
QCOMPARE(testItem->touchEventsReceived.count(), 3);
QVERIFY(touchGate->m_storedEvents.isEmpty());
QVERIFY(touchGate->m_touchInfoMap.isEmpty());
}
void tst_TouchGate::holdsEventsUntilGainsOwnership_data()
{
QTest::addColumn("ownershipAfterTouchEnd");
QTest::newRow("touch end after touch ownership") << false;
QTest::newRow("touch ownership after touch end") << true;
}
/*
A touch point of id 0 starts and ends. Then the next touch point is also given
id 0. Check that TouchGate behaves correcly recognizing them as two distinct touch
points.
Note that this won't be a problem with Mir as it assigns unique ids for the touch
points it produces.
*/
void tst_TouchGate::holdsEventsUntilGainsOwnership_repeatingTouchId()
{
TouchGate *touchGate = view->rootObject()->findChild("touchGate");
Q_ASSERT(touchGate);
TestItem *testItem = new TestItem;
testItem->setWidth(touchGate->width());
testItem->setHeight(touchGate->height());
testItem->setParentItem(view->rootObject());
testItem->setZ(0.0);
touchGate->setZ(1.0);
touchGate->setTargetItem(testItem);
QTest::touchEvent(view, device)
.press(0, QPoint(100,100));
QTest::touchEvent(view, device)
.move(0, QPoint(101,101));
QTest::touchEvent(view, device)
.release(0, QPoint(101,101));
QCOMPARE(testItem->touchEventsReceived.count(), 3);
testItem->touchEventsReceived.clear();
// Put it in front of touchGate
CandidateItem *candidateItem = new CandidateItem;
candidateItem->setWidth(touchGate->width());
candidateItem->setHeight(touchGate->height());
candidateItem->setParentItem(view->rootObject());
candidateItem->setZ(2.0);
candidateItem->touchEventHandler = [&](QTouchEvent* event) {
touchRegistry->addCandidateOwnerForTouch(0, candidateItem);
event->ignore();
};
// A brand new touch, but with the same id as the previous one
QTest::touchEvent(view, device)
.press(0, QPoint(200,200));
QTest::touchEvent(view, device)
.move(0, QPoint(201,201));
QTest::touchEvent(view, device)
.release(0, QPoint(201,201));
QCOMPARE(testItem->touchEventsReceived.count(), 0);
touchRegistry->requestTouchOwnership(0, candidateItem);
QCOMPARE(testItem->touchEventsReceived.count(), 0);
QVERIFY(touchGate->m_storedEvents.isEmpty());
}
void tst_TouchGate::dontDispacthToDisabledOrInvisibleTarget()
{
TouchGate *touchGate = view->rootObject()->findChild("touchGate");
Q_ASSERT(touchGate);
TestItem *testItem = new TestItem;
testItem->setWidth(touchGate->width());
testItem->setHeight(touchGate->height());
testItem->setParentItem(view->rootObject());
testItem->setZ(0.0);
QFETCH(bool, isEnabled);
testItem->setEnabled(isEnabled);
QFETCH(bool, isVisible);
testItem->setVisible(isVisible);
touchGate->setZ(1.0);
touchGate->setTargetItem(testItem);
QTest::touchEvent(view, device)
.press(0, QPoint(100,100));
QCOMPARE(testItem->touchEventsReceived.count(), 0);
QVERIFY(touchGate->m_storedEvents.isEmpty());
}
void tst_TouchGate::dontDispacthToDisabledOrInvisibleTarget_data()
{
QTest::addColumn("isEnabled");
QTest::addColumn("isVisible");
QTest::newRow("disabled visible") << false << true;
QTest::newRow("enabled invisible") << true << false;
QTest::newRow("disabled invisible") << false << false;
}
void tst_TouchGate::disabledWhileHoldingTouch()
{
TouchGate *touchGate = view->rootObject()->findChild("touchGate");
Q_ASSERT(touchGate);
TestItem *testItem = new TestItem;
testItem->setWidth(touchGate->width());
testItem->setHeight(touchGate->height());
testItem->setParentItem(view->rootObject());
testItem->setZ(0.0);
touchGate->setZ(1.0);
touchGate->setTargetItem(testItem);
QTest::touchEvent(view, device)
.press(0, QPoint(100,100));
QCOMPARE(testItem->touchEventsReceived.count(), 1);
testItem->touchEventsReceived.clear();
touchGate->setEnabled(false);
QTest::touchEvent(view, device)
.move(0, QPoint(101,101));
// Nothing new came as TouchGate didn't even get it because it's disabled
QCOMPARE(testItem->touchEventsReceived.count(), 0);
touchGate->setEnabled(true);
QTest::touchEvent(view, device)
.move(0, QPoint(102,102));
QTest::touchEvent(view, device)
.release(0, QPoint(102,102));
// Nothing new came because TouchGate has already discarded touch 0
QCOMPARE(testItem->touchEventsReceived.count(), 0);
QTest::touchEvent(view, device)
.press(1, QPoint(200,200));
QCOMPARE(testItem->touchEventsReceived.count(), 1);
{
// it got only the new touch point.
QSharedPointer touchEvent = testItem->touchEventsReceived[0];
QCOMPARE(touchEvent->touchPoints().count(), 1);
QCOMPARE(touchEvent->touchPoints()[0].id(), 1);
}
}
///////////// CandidateItem /////////////////////////////////////////////////////////////
void CandidateItem::touchEvent(QTouchEvent *event)
{
touchEventHandler(event);
}
QTEST_MAIN(tst_TouchGate)
#include "tst_TouchGate.moc"