on xenial help sign in
refactor a little, add some tests

design still needs improvement

commit6e47c0573e6531e7302af3c2c82e3b0c2c0d7f58 authorMichael Zanetti <michael.zanetti@canonical.com> dateMon 16 Nov 2015, 15:12 (11 years ago) parentdeee36b
12 files changed+498−137
qml/Components/VirtualTouchPad.qml+175 qml/DisabledScreenNotice.qml+1−136 tests/mocks/CMakeLists.txt+1 tests/mocks/UInput/CMakeLists.txt+7 tests/mocks/UInput/plugin.cpp+26 tests/mocks/UInput/plugin.h+32 tests/mocks/UInput/qmldir+3 tests/mocks/UInput/uinput.cpp+65 tests/mocks/UInput/uinput.h+49 tests/qmltests/CMakeLists.txt+1 tests/qmltests/Components/tst_VirtualTouchPad.qml+137 tests/qmltests/tst_DisabledScreenNotice.qml+1−1
qml/Components/VirtualTouchPad.qmlnew file+175
@@ -0,0 +1,175 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import QtQuick.Layouts 1.1
19+import Ubuntu.Components 1.3
20+import UInput 0.1
21+import Cursor 1.0
22+
23+ColumnLayout {
24+ spacing: units.gu(2)
25+
26+ property var uinput: UInput {
27+ Component.onCompleted: createMouse();
28+ Component.onDestruction: removeMouse();
29+ }
30+
31+ UbuntuShape {
32+ Layout.fillWidth: true
33+ Layout.fillHeight: true
34+ backgroundColor: "black"
35+ opacity: 0.4
36+
37+ Image {
38+ anchors.centerIn: parent
39+ height: units.gu(2)
40+ width: units.gu(2)
41+ source: "image://cursor/default/left_ptr"
42+ }
43+
44+ MultiPointTouchArea {
45+ objectName: "touchPadArea"
46+ anchors.fill: parent
47+
48+ property int clickThreshold: units.gu(1.5)
49+ property bool isClick: false
50+ property bool pressed: false
51+
52+ onPressed: {
53+ print("pressed", point1.pressed, point2.pressed)
54+ // If double-tapping *really* fast, it could happen that we end up having only point2 pressed
55+ // Make sure we check for both combos, only point1 or only point2
56+ if (((point1.pressed && !point2.pressed) || (!point1.pressed && point2.pressed))
57+ && clickTimer.running) {
58+ clickTimer.stop();
59+ uinput.pressMouse(UInput.ButtonLeft)
60+ pressed = true;
61+ }
62+ isClick = true;
63+ }
64+
65+ onUpdated: {
66+ switch (touchPoints.length) {
67+ case 1:
68+ moveMouse(touchPoints);
69+ return;
70+ case 2:
71+ scroll(touchPoints);
72+ return;
73+ }
74+ }
75+
76+ onReleased: {
77+ var tp = touchPoints[0]
78+ if (isClick) {
79+ if (pressed) {
80+ uinput.releaseMouse(UInput.ButtonLeft)
81+ pressed = false;
82+ }
83+ clickTimer.scheduleClick(point1.pressed ? UInput.ButtonRight : UInput.ButtonLeft)
84+ }
85+ isClick = false;
86+ }
87+
88+ Timer {
89+ id: clickTimer
90+ repeat: false
91+ interval: 100
92+ property int button: UInput.ButtonLeft
93+ onTriggered: {
94+ uinput.pressMouse(button);
95+ uinput.releaseMouse(button);
96+ }
97+ function scheduleClick(button) {
98+ clickTimer.button = button;
99+ clickTimer.start();
100+ }
101+ }
102+
103+ function moveMouse(touchPoints) {
104+ var tp = touchPoints[0];
105+ if (isClick &&
106+ (Math.abs(tp.x - tp.startX) > clickThreshold ||
107+ Math.abs(tp.y - tp.startY) > clickThreshold)) {
108+ isClick = false;
109+ }
110+
111+ uinput.moveMouse(tp.x - tp.previousX, tp.y - tp.previousY);
112+ }
113+
114+ function scroll(touchPoints) {
115+ var dh = 0;
116+ var dv = 0;
117+ var tp = touchPoints[0];
118+ if (isClick &&
119+ (Math.abs(tp.x - tp.startX) > clickThreshold ||
120+ Math.abs(tp.y - tp.startY) > clickThreshold)) {
121+ isClick = false;
122+ }
123+ dh += tp.x - tp.previousX;
124+ dv += tp.y - tp.previousY;
125+
126+ tp = touchPoints[1];
127+ if (isClick &&
128+ (Math.abs(tp.x - tp.startX) > clickThreshold ||
129+ Math.abs(tp.y - tp.startY) > clickThreshold)) {
130+ isClick = false;
131+ }
132+ dh += tp.x - tp.previousX;
133+ dv += tp.y - tp.previousY;
134+
135+ uinput.scrollMouse(dh, dv);
136+ }
137+
138+ touchPoints: [
139+ TouchPoint {
140+ id: point1
141+ },
142+ TouchPoint {
143+ id: point2
144+ }
145+ ]
146+ }
147+ }
148+
149+ RowLayout {
150+ Layout.fillWidth: true
151+ spacing: units.gu(2)
152+ Button {
153+ objectName: "leftButton"
154+ Layout.fillWidth: true
155+ onPressedChanged: {
156+ if (pressed) {
157+ uinput.pressMouse(UInput.ButtonLeft)
158+ } else {
159+ uinput.releaseMouse(UInput.ButtonLeft)
160+ }
161+ }
162+ }
163+ Button {
164+ objectName: "rightButton"
165+ Layout.fillWidth: true
166+ onPressedChanged: {
167+ if (pressed) {
168+ uinput.pressMouse(UInput.ButtonRight)
169+ } else {
170+ uinput.releaseMouse(UInput.ButtonRight)
171+ }
172+ }
173+ }
174+ }
175+}
qml/DisabledScreenNotice.qml+1−136
@@ -17,8 +17,6 @@
1717import QtQuick 2.4
1818import QtQuick.Layouts 1.1
1919import Ubuntu.Components 1.3
20−import UInput 0.1
21−import Cursor 1.0
2220import "Components"
2321
2422Image {
@@ -31,12 +29,6 @@ Image {
3129
3230 source: wallpaperResolver.background
3331
34− UInput {
35− id: uinput
36− Component.onCompleted: createMouse();
37− Component.onDestruction: removeMouse();
38− }
39−
4032 ColumnLayout {
4133 anchors.fill: parent
4234 anchors.margins: units.gu(2)
@@ -53,136 +45,9 @@ Image {
5345 wrapMode: Text.Wrap
5446 }
5547
56− UbuntuShape {
48+ VirtualTouchPad {
5749 Layout.fillWidth: true
5850 Layout.fillHeight: true
59− backgroundColor: "black"
60− opacity: 0.4
61−
62− Image {
63− anchors.centerIn: parent
64− height: units.gu(2)
65− width: units.gu(2)
66− source: "image://cursor/default/left_ptr"
67− }
68−
69− MultiPointTouchArea {
70− anchors.fill: parent
71−
72− property int clickThreshold: units.gu(1.5)
73− property bool isClick: false
74−
75− onPressed: {
76− if (point1.pressed && !point2.pressed && clickTimer.running) {
77− clickTimer.stop();
78− uinput.pressMouse(UInput.ButtonLeft)
79− }
80− isClick = true;
81− }
82−
83− onUpdated: {
84− switch (touchPoints.length) {
85− case 1:
86− moveMouse(touchPoints);
87− return;
88− case 2:
89− scroll(touchPoints);
90− return;
91− }
92− }
93−
94− onReleased: {
95− var tp = touchPoints[0]
96− if (isClick) {
97− clickTimer.scheduleClick(point1.pressed ? UInput.ButtonRight : UInput.ButtonLeft)
98− }
99− isClick = false;
100− }
101−
102− Timer {
103− id: clickTimer
104− repeat: false
105− interval: 100
106− property int button: UInput.ButtonLeft
107− onTriggered: {
108− uinput.pressMouse(button);
109− uinput.releaseMouse(button);
110− }
111− function scheduleClick(button) {
112− clickTimer.button = button;
113− clickTimer.start();
114− }
115− }
116−
117− function moveMouse(touchPoints) {
118− var tp = touchPoints[0];
119− if (isClick &&
120− (Math.abs(tp.x - tp.startX) > clickThreshold ||
121− Math.abs(tp.y - tp.startY) > clickThreshold)) {
122− isClick = false;
123− }
124−
125− uinput.moveMouse(tp.x - tp.previousX, tp.y - tp.previousY);
126− }
127−
128− function scroll(touchPoints) {
129− var dh = 0;
130− var dv = 0;
131− var tp = touchPoints[0];
132− if (isClick &&
133− (Math.abs(tp.x - tp.startX) > clickThreshold ||
134− Math.abs(tp.y - tp.startY) > clickThreshold)) {
135− isClick = false;
136− }
137− dh += tp.x - tp.previousX;
138− dv += tp.y - tp.previousY;
139−
140− tp = touchPoints[1];
141− if (isClick &&
142− (Math.abs(tp.x - tp.startX) > clickThreshold ||
143− Math.abs(tp.y - tp.startY) > clickThreshold)) {
144− isClick = false;
145− }
146− dh += tp.x - tp.previousX;
147− dv += tp.y - tp.previousY;
148−
149− uinput.scrollMouse(dh, dv);
150− }
151−
152− touchPoints: [
153− TouchPoint {
154− id: point1
155− },
156− TouchPoint {
157− id: point2
158− }
159− ]
160− }
161− }
162−
163− RowLayout {
164− Layout.fillWidth: true
165− spacing: units.gu(2)
166− Button {
167− Layout.fillWidth: true
168− onPressedChanged: {
169− if (pressed) {
170− uinput.pressMouse(UInput.ButtonLeft)
171− } else {
172− uinput.releaseMouse(UInput.ButtonLeft)
173− }
174− }
175− }
176− Button {
177− Layout.fillWidth: true
178− onPressedChanged: {
179− if (pressed) {
180− uinput.pressMouse(UInput.ButtonRight)
181− } else {
182− uinput.releaseMouse(UInput.ButtonRight)
183− }
184− }
185− }
18651 }
18752 }
18853}
tests/mocks/CMakeLists.txt+1
@@ -43,6 +43,7 @@ add_subdirectory(Unity)
4343add_subdirectory(QtMultimedia)
4444add_subdirectory(Wizard)
4545add_subdirectory(Utils)
46+add_subdirectory(UInput)
4647
4748install(
4849 DIRECTORY data
tests/mocks/UInput/CMakeLists.txtnew file+7
@@ -0,0 +1,7 @@
1+add_library(FakeUInput-qml MODULE
2+ plugin.cpp
3+ uinput.cpp
4+)
5+
6+qt5_use_modules(FakeUInput-qml Qml)
7+add_unity8_mock(UInput 0.1 UInput TARGETS FakeUInput-qml)
tests/mocks/UInput/plugin.cppnew file+26
@@ -0,0 +1,26 @@
1+/*
2+ * Copyright (C) 2015 Canonical Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it
5+ * under the terms of the GNU General Public License version 3, as published
6+ * by the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but
9+ * WITHOUT ANY WARRANTY; without even the implied warranties of
10+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11+ * PURPOSE. See the GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License along
14+ * with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "plugin.h"
18+#include "uinput.h"
19+
20+#include <QtQml/qqml.h>
21+
22+void UInputPlugin::registerTypes(const char *uri)
23+{
24+ Q_ASSERT(uri == QLatin1String("UInput"));
25+ qmlRegisterType<UInput>(uri, 0, 1, "UInput");
26+}
tests/mocks/UInput/plugin.hnew file+32
@@ -0,0 +1,32 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef UINPUT_PLUGIN_H
18+#define UINPUT_PLUGIN_H
19+
20+#include <QtQml/QQmlEngine>
21+#include <QtQml/QQmlExtensionPlugin>
22+
23+class UInputPlugin : public QQmlExtensionPlugin
24+{
25+ Q_OBJECT
26+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
27+
28+public:
29+ void registerTypes(const char *uri) override;
30+};
31+
32+#endif
tests/mocks/UInput/qmldirnew file+3
@@ -0,0 +1,3 @@
1+module UInput
2+plugin FakeUInput-qml
3+typeinfo UInput.qmltypes
tests/mocks/UInput/uinput.cppnew file+65
@@ -0,0 +1,65 @@
1+#include "uinput.h"
2+
3+#include <QFile>
4+#include <QDebug>
5+#include <QDateTime>
6+
7+#include <unistd.h>
8+
9+UInput::UInput(QObject *parent) :
10+ QObject(parent)
11+{
12+}
13+
14+UInput::~UInput()
15+{
16+}
17+
18+void UInput::createMouse()
19+{
20+ if (m_mouseCreated) {
21+ qDebug() << "Already have a virtual device. Not creating another one.";
22+ return;
23+ }
24+ m_mouseCreated = true;
25+ Q_EMIT mouseCreated();
26+}
27+
28+void UInput::removeMouse()
29+{
30+ if (!m_mouseCreated) {
31+ return;
32+ }
33+ qDebug() << "Virtual uinput mouse device removed.";
34+ m_mouseCreated = false;
35+ Q_EMIT mouseRemoved();
36+}
37+
38+void UInput::moveMouse(int dx, int dy)
39+{
40+ qDebug() << "moving mouse" << dx << dy;
41+ Q_EMIT mouseMoved(dx, dy);
42+}
43+
44+void UInput::pressMouse(Button button)
45+{
46+ injectMouse(button, 1);
47+ Q_EMIT mousePressed(button);
48+}
49+
50+void UInput::releaseMouse(Button button)
51+{
52+ injectMouse(button, 0);
53+ Q_EMIT mouseReleased(button);
54+}
55+
56+void UInput::scrollMouse(int dh, int dv)
57+{
58+ qDebug() << "scrolling" << dh << dv;
59+ Q_EMIT mouseScrolled(dh, dv);
60+}
61+
62+void UInput::injectMouse(Button button, int down)
63+{
64+ qDebug() << "mouse event" << button << down;
65+}
tests/mocks/UInput/uinput.hnew file+49
@@ -0,0 +1,49 @@
1+#ifndef UINPUT_H
2+#define UINPUT_H
3+
4+#include <QObject>
5+#include <QFile>
6+
7+#include <linux/uinput.h>
8+
9+
10+class UInput : public QObject
11+{
12+ Q_OBJECT
13+ Q_ENUMS(Button)
14+
15+public:
16+ enum Button {
17+ ButtonLeft,
18+ ButtonRight,
19+ ButtonMiddle
20+ };
21+
22+ explicit UInput(QObject *parent = 0);
23+ ~UInput();
24+
25+ Q_INVOKABLE void createMouse();
26+ Q_INVOKABLE void removeMouse();
27+
28+ Q_INVOKABLE void moveMouse(int dx, int dy);
29+ Q_INVOKABLE void pressMouse(Button button);
30+ Q_INVOKABLE void releaseMouse(Button button);
31+ Q_INVOKABLE void scrollMouse(int dh, int dv);
32+
33+Q_SIGNALS:
34+ // for testing
35+ void mouseCreated();
36+ void mouseRemoved();
37+ void mouseMoved(int dx, int dy);
38+ void mousePressed(Button button);
39+ void mouseReleased(Button button);
40+ void mouseScrolled(int dh, int dv);
41+
42+private:
43+ void injectMouse(Button button, int down);
44+
45+private:
46+ bool m_mouseCreated = false;
47+};
48+
49+#endif // UINPUT_H
tests/qmltests/CMakeLists.txt+1
@@ -16,6 +16,7 @@ add_unity8_qmltest(Components Rating)
1616add_unity8_qmltest(Components ResponsiveGridView)
1717add_unity8_qmltest(Components ResponsiveVerticalJournal)
1818add_unity8_qmltest(Components Showable)
19+add_unity8_qmltest(Components VirtualTouchPad)
1920add_unity8_qmltest(Components WallpaperResolver)
2021add_unity8_qmltest(Components ZoomableImage)
2122add_unity8_qmltest(Dash Dash)
tests/qmltests/Components/tst_VirtualTouchPad.qmlnew file+137
@@ -0,0 +1,137 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import QtTest 1.0
19+import Unity.Test 0.1
20+import UInput 0.1
21+import "../../../qml/Components"
22+
23+
24+Item {
25+ id: root
26+ width: units.gu(70)
27+ height: units.gu(70)
28+
29+ VirtualTouchPad {
30+ id: touchScreenPad
31+ anchors.fill: parent
32+ }
33+
34+ SignalSpy {
35+ id: mouseEventSpy1
36+ target: touchScreenPad.uinput
37+ }
38+ SignalSpy {
39+ id: mouseEventSpy2
40+ target: touchScreenPad.uinput
41+ }
42+
43+ UnityTestCase {
44+ id: testCase
45+ name: "VirtualTouchPad"
46+ when: windowShown
47+
48+ function init() {
49+ mouseEventSpy1.clear();
50+ mouseEventSpy2.clear();
51+ }
52+
53+ function test_click() {
54+ mouseEventSpy1.signalName = "mousePressed"
55+ mouseEventSpy2.signalName = "mouseReleased"
56+
57+ var touchPadArea = findChild(touchScreenPad, "touchPadArea");
58+
59+ tap(touchPadArea)
60+ tryCompare(mouseEventSpy1, "count", 1)
61+ tryCompare(mouseEventSpy2, "count", 1)
62+ }
63+
64+ function test_doubleClick() {
65+ mouseEventSpy1.signalName = "mousePressed"
66+ mouseEventSpy2.signalName = "mouseReleased"
67+
68+ var touchPadArea = findChild(touchScreenPad, "touchPadArea");
69+
70+ tap(touchPadArea)
71+ tap(touchPadArea)
72+
73+ tryCompare(mouseEventSpy1, "count", 2)
74+ tryCompare(mouseEventSpy2, "count", 2)
75+ }
76+
77+ function test_move() {
78+ mouseEventSpy1.signalName = "mouseMoved"
79+
80+ var touchPadArea = findChild(touchScreenPad, "touchPadArea");
81+
82+ var moveDiff = units.gu(2);
83+ var moveSteps = 5;
84+
85+ touchFlick(touchPadArea, units.gu(1), units.gu(1), units.gu(1) + moveDiff, units.gu(1) + moveDiff, true, true, 10, moveSteps)
86+
87+ tryCompare(mouseEventSpy1, "count", moveSteps)
88+ var movedX = 0;
89+ var movedY = 0
90+ for (var i = 0; i < 5; i++) {
91+ movedX += mouseEventSpy1.signalArguments[i][0]
92+ movedY += mouseEventSpy1.signalArguments[i][1]
93+ }
94+ compare(movedX, moveDiff)
95+ compare(movedY, moveDiff)
96+ }
97+
98+ function test_doubleTapAndHoldToDrag() {
99+ mouseEventSpy1.signalName = "mousePressed"
100+ mouseEventSpy2.signalName = "mouseReleased"
101+
102+ var touchPadArea = findChild(touchScreenPad, "touchPadArea");
103+
104+ tap(touchPadArea)
105+ mousePress(touchPadArea)
106+
107+ tryCompare(mouseEventSpy1, "count", 1)
108+ tryCompare(mouseEventSpy2, "count", 0)
109+ mouseRelease(touchPadArea)
110+ }
111+
112+ function test_buttons_data() {
113+ return [
114+ { tag: "left", button: UInput.ButtonLeft },
115+ { tag: "left", button: UInput.ButtonRight }
116+ ]
117+ }
118+
119+ function test_buttons(data) {
120+ mouseEventSpy1.signalName = "mousePressed"
121+ mouseEventSpy2.signalName = "mouseReleased"
122+
123+ var button = findChild(touchScreenPad, data.button === UInput.ButtonLeft ? "leftButton" : "rightButton");
124+
125+ mousePress(button);
126+ tryCompare(mouseEventSpy1, "count", 1)
127+ tryCompare(mouseEventSpy2, "count", 0)
128+ compare(mouseEventSpy1.signalArguments[0][0], data.button)
129+
130+ mouseRelease(button);
131+ tryCompare(mouseEventSpy1, "count", 1)
132+ tryCompare(mouseEventSpy2, "count", 1)
133+ compare(mouseEventSpy2.signalArguments[0][0], data.button)
134+
135+ }
136+ }
137+}
tests/qmltests/tst_DisabledScreenNotice.qml+1−1
@@ -19,13 +19,13 @@ import QtTest 1.0
1919import Unity.Test 0.1
2020import "../../qml"
2121
22−
2322Item {
2423 id: root
2524 width: units.gu(70)
2625 height: units.gu(70)
2726
2827 DisabledScreenNotice {
28+ id: touchScreenPad
2929 anchors.fill: parent
3030 }
3131