Merge lp:~osomon/webbrowser-app/bottom-edge into lp:webbrowser-app
- bottom-edge
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Olivier Tilloy | ||||
Approved revision: | 955 | ||||
Merged at revision: | 935 | ||||
Proposed branch: | lp:~osomon/webbrowser-app/bottom-edge | ||||
Merge into: | lp:webbrowser-app | ||||
Diff against target: |
6448 lines (+5313/-361) 62 files modified
CMakeLists.txt (+3/-0) debian/control (+2/-0) src/app/CMakeLists.txt (+6/-0) src/app/browserapplication.cpp (+17/-0) src/app/favicon-fetcher.h (+1/-1) src/app/unity8/CMakeLists.txt (+4/-0) src/app/unity8/README (+11/-0) src/app/unity8/libs/CMakeLists.txt (+1/-0) src/app/unity8/libs/UbuntuGestures/CMakeLists.txt (+36/-0) src/app/unity8/libs/UbuntuGestures/CandidateInactivityTimer.cpp (+41/-0) src/app/unity8/libs/UbuntuGestures/CandidateInactivityTimer.h (+49/-0) src/app/unity8/libs/UbuntuGestures/DebugHelpers.cpp (+95/-0) src/app/unity8/libs/UbuntuGestures/DebugHelpers.h (+31/-0) src/app/unity8/libs/UbuntuGestures/Pool.h (+132/-0) src/app/unity8/libs/UbuntuGestures/Timer.cpp (+109/-0) src/app/unity8/libs/UbuntuGestures/Timer.h (+105/-0) src/app/unity8/libs/UbuntuGestures/TouchOwnershipEvent.cpp (+35/-0) src/app/unity8/libs/UbuntuGestures/TouchOwnershipEvent.h (+50/-0) src/app/unity8/libs/UbuntuGestures/TouchRegistry.cpp (+500/-0) src/app/unity8/libs/UbuntuGestures/TouchRegistry.h (+182/-0) src/app/unity8/libs/UbuntuGestures/UbuntuGesturesGlobal.h (+23/-0) src/app/unity8/libs/UbuntuGestures/UnownedTouchEvent.cpp (+39/-0) src/app/unity8/libs/UbuntuGestures/UnownedTouchEvent.h (+45/-0) src/app/unity8/plugins/CMakeLists.txt (+1/-0) src/app/unity8/plugins/Ubuntu/CMakeLists.txt (+1/-0) src/app/unity8/plugins/Ubuntu/Gestures/AxisVelocityCalculator.cpp (+149/-0) src/app/unity8/plugins/Ubuntu/Gestures/AxisVelocityCalculator.h (+146/-0) src/app/unity8/plugins/Ubuntu/Gestures/CMakeLists.txt (+40/-0) src/app/unity8/plugins/Ubuntu/Gestures/Damper.h (+87/-0) src/app/unity8/plugins/Ubuntu/Gestures/Direction.cpp (+36/-0) src/app/unity8/plugins/Ubuntu/Gestures/Direction.h (+45/-0) src/app/unity8/plugins/Ubuntu/Gestures/DirectionalDragArea.cpp (+886/-0) src/app/unity8/plugins/Ubuntu/Gestures/DirectionalDragArea.h (+308/-0) src/app/unity8/plugins/Ubuntu/Gestures/Gestures.qmltypes (+168/-0) src/app/unity8/plugins/Ubuntu/Gestures/PressedOutsideNotifier.cpp (+119/-0) src/app/unity8/plugins/Ubuntu/Gestures/PressedOutsideNotifier.h (+62/-0) src/app/unity8/plugins/Ubuntu/Gestures/TimeSource.cpp (+49/-0) src/app/unity8/plugins/Ubuntu/Gestures/TimeSource.h (+54/-0) src/app/unity8/plugins/Ubuntu/Gestures/TouchDispatcher.cpp (+367/-0) src/app/unity8/plugins/Ubuntu/Gestures/TouchDispatcher.h (+89/-0) src/app/unity8/plugins/Ubuntu/Gestures/TouchGate.cpp (+253/-0) src/app/unity8/plugins/Ubuntu/Gestures/TouchGate.h (+112/-0) src/app/unity8/plugins/Ubuntu/Gestures/UbuntuGesturesQmlGlobal.h (+23/-0) src/app/unity8/plugins/Ubuntu/Gestures/plugin.cpp (+39/-0) src/app/unity8/plugins/Ubuntu/Gestures/plugin.h (+31/-0) src/app/unity8/plugins/Ubuntu/Gestures/qmldir (+3/-0) src/app/webbrowser/BottomEdgeHandle.qml (+36/-0) src/app/webbrowser/Browser.qml (+190/-26) src/app/webbrowser/BrowserTab.qml (+13/-4) src/app/webbrowser/ExpandedHistoryView.qml (+2/-2) src/app/webbrowser/HistorySectionDelegate.qml (+2/-2) src/app/webbrowser/HistoryView.qml (+2/-2) src/app/webbrowser/TabChrome.qml (+144/-0) src/app/webbrowser/TabPreview.qml (+67/-121) src/app/webbrowser/TabsList.qml (+130/-0) src/app/webbrowser/TabsView.qml (+0/-123) src/app/webbrowser/Toolbar.qml (+36/-1) src/app/webbrowser/UrlDelegate.qml (+3/-3) tests/autopilot/webbrowser_app/emulators/browser.py (+44/-22) tests/autopilot/webbrowser_app/tests/__init__.py (+20/-10) tests/autopilot/webbrowser_app/tests/test_addressbar_bookmark.py (+9/-13) tests/autopilot/webbrowser_app/tests/test_tabs.py (+30/-31) |
||||
To merge this branch: | bzr merge lp:~osomon/webbrowser-app/bottom-edge | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Florian Boucault (community) | Approve | ||
PS Jenkins bot | continuous-integration | Needs Fixing | |
Ken VanDine | Approve | ||
Bartosz Kosiorek (community) | Needs Information | ||
Review via email: mp+248019@code.launchpad.net |
Commit message
On mobile, reveal the tabs view with a swipe gesture from the bottom edge.
Note: this re-adds qtbase5-private-dev and qtdeclarative5-
Description of the change
On mobile, reveal the tabs view with a swipe gesture from the bottom edge.
PS Jenkins bot (ps-jenkins) wrote : | # |
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:899
http://
Executed test runs:
UNSTABLE: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:899
http://
Executed test runs:
UNSTABLE: http://
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:900
http://
Executed test runs:
UNSTABLE: http://
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:901
http://
Executed test runs:
UNSTABLE: http://
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:902
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:903
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:904
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:905
http://
Executed test runs:
UNSTABLE: http://
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:907
http://
Executed test runs:
UNSTABLE: http://
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:911
http://
Executed test runs:
UNSTABLE: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:912
http://
Executed test runs:
UNSTABLE: http://
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:913
http://
Executed test runs:
UNSTABLE: http://
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:914
http://
Executed test runs:
UNSTABLE: http://
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
Bartosz Kosiorek (gang65) wrote : | # |
Hello Olivier.
Thanks for this MR. It is most wanted feature in WebBrowser.
One small remark:
Could you please update README file, because executable location was changed.
To run you should type:
./src/app/
Do you think it will be possible to use gestures on Desktop?
I'm unable to show up tabs from bottom edge.
Olivier Tilloy (osomon) wrote : | # |
> Hello Olivier.
> Thanks for this MR. It is most wanted feature in WebBrowser.
>
> One small remark:
> Could you please update README file, because executable location was changed.
> To run you should type:
>
> ./src/app/
The README file is already up-to-date in this regard.
> Do you think it will be possible to use gestures on Desktop?
> I'm unable to show up tabs from bottom edge.
No, by design this is meant to work on mobile only. In the future the desktop UX will gain visible tabs, much like other desktop browsers do.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:915
http://
Executed test runs:
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:938
http://
Executed test runs:
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:940
http://
Executed test runs:
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:940
http://
Executed test runs:
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:940
http://
Executed test runs:
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Ken VanDine (ken-vandine) wrote : | # |
Packaging changes look fine, ACK from me.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:942
http://
Executed test runs:
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:943
http://
Executed test runs:
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Florian Boucault (fboucault) wrote : | # |
* tests/autopilot
- Factor duplicated code to swipe up from the bottom in a common function
* src/app/
- #5d5d5d is UbuntuColors.
* src/app/
- generally better for components to not define their own width/height but instead implicitWidth/
- "AbstractButton { id: closeButton" should use anchors instead of "height: parent.height"; same for unnamed Item below
- closeButton has a Rectangle without color
- closeButton has no visual feedback when pressing
- Images "tabBackgroundL
- "MouseArea.width: parent.width / 2" should probably have a comment
Preview Diff
1 | === modified file 'CMakeLists.txt' |
2 | --- CMakeLists.txt 2014-11-12 17:00:12 +0000 |
3 | +++ CMakeLists.txt 2015-03-20 16:26:56 +0000 |
4 | @@ -32,6 +32,9 @@ |
5 | ENABLE_COVERAGE_REPORT(EXCLUDES tests/*|.*moc_.*.cpp FILTER tests/* moc_*.cpp) |
6 | endif() |
7 | |
8 | +# for unity8 components |
9 | +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") |
10 | + |
11 | # enable QML debugging |
12 | if(CMAKE_BUILD_TYPE MATCHES DEBUG OR CMAKE_BUILD_TYPE MATCHES "Debug") |
13 | add_definitions(-DQT_QML_DEBUG) |
14 | |
15 | === modified file 'debian/control' |
16 | --- debian/control 2015-02-26 17:54:47 +0000 |
17 | +++ debian/control 2015-03-20 16:26:56 +0000 |
18 | @@ -17,7 +17,9 @@ |
19 | qt5-qmake, |
20 | qtbase5-dev (>= 5.4), |
21 | qtbase5-dev-tools, |
22 | + qtbase5-private-dev, |
23 | qtdeclarative5-dev, |
24 | + qtdeclarative5-private-dev, |
25 | qtdeclarative5-ubuntu-ui-toolkit-plugin, |
26 | xvfb, |
27 | Standards-Version: 3.9.5 |
28 | |
29 | === modified file 'src/app/CMakeLists.txt' |
30 | --- src/app/CMakeLists.txt 2015-02-27 09:28:41 +0000 |
31 | +++ src/app/CMakeLists.txt 2015-03-20 16:26:56 +0000 |
32 | @@ -1,5 +1,7 @@ |
33 | project(webbrowser-common) |
34 | |
35 | +add_subdirectory(unity8) |
36 | + |
37 | configure_file( |
38 | config.h.in |
39 | ${CMAKE_CURRENT_BINARY_DIR}/config.h |
40 | @@ -18,6 +20,10 @@ |
41 | |
42 | qt5_use_modules(${COMMONLIB} Core Gui Network Qml Quick Widgets) |
43 | |
44 | +include_directories(${unity8_SOURCE_DIR}/libs/UbuntuGestures |
45 | + ${unity8_SOURCE_DIR}/plugins) |
46 | +target_link_libraries(${COMMONLIB} UbuntuGesturesQml) |
47 | + |
48 | file(GLOB QML_FILES *.qml) |
49 | install(FILES ${QML_FILES} DESTINATION ${CMAKE_INSTALL_DATADIR}/webbrowser-app) |
50 | file(GLOB JS_FILES *.js) |
51 | |
52 | === modified file 'src/app/browserapplication.cpp' |
53 | --- src/app/browserapplication.cpp 2015-02-27 09:28:41 +0000 |
54 | +++ src/app/browserapplication.cpp 2015-03-20 16:26:56 +0000 |
55 | @@ -34,6 +34,10 @@ |
56 | #include "session-storage.h" |
57 | #include "webbrowser-window.h" |
58 | |
59 | +#include "TouchRegistry.h" |
60 | +#include "Ubuntu/Gestures/Direction.h" |
61 | +#include "Ubuntu/Gestures/DirectionalDragArea.h" |
62 | + |
63 | BrowserApplication::BrowserApplication(int& argc, char** argv) |
64 | : QApplication(argc, argv) |
65 | , m_engine(0) |
66 | @@ -95,6 +99,13 @@ |
67 | return QString(); |
68 | } |
69 | |
70 | +static QObject* Direction_singleton_factory(QQmlEngine* engine, QJSEngine* scriptEngine) |
71 | +{ |
72 | + Q_UNUSED(engine); |
73 | + Q_UNUSED(scriptEngine); |
74 | + return new Direction(); |
75 | +} |
76 | + |
77 | bool BrowserApplication::initialize(const QString& qmlFileSubPath) |
78 | { |
79 | Q_ASSERT(m_window == 0); |
80 | @@ -133,6 +144,10 @@ |
81 | qmlRegisterType<FaviconFetcher>(uri, 0, 1, "FaviconFetcher"); |
82 | qmlRegisterType<SessionStorage>(uri, 0, 1, "SessionStorage"); |
83 | |
84 | + const char* gesturesUri = "Ubuntu.Gestures"; |
85 | + qmlRegisterSingletonType<Direction>(gesturesUri, 0, 1, "Direction", Direction_singleton_factory); |
86 | + qmlRegisterType<DirectionalDragArea>(gesturesUri, 0, 1, "DirectionalDragArea"); |
87 | + |
88 | m_engine = new QQmlEngine; |
89 | connect(m_engine, SIGNAL(quit()), SLOT(quit())); |
90 | if (!isRunningInstalled()) { |
91 | @@ -156,6 +171,8 @@ |
92 | m_window = qobject_cast<QQuickWindow*>(browser); |
93 | m_webbrowserWindowProxy->setWindow(m_window); |
94 | |
95 | + m_window->installEventFilter(new TouchRegistry(this)); |
96 | + |
97 | browser->setProperty("developerExtrasEnabled", inspectorEnabled); |
98 | browser->setProperty("forceFullscreen", m_arguments.contains("--fullscreen")); |
99 | |
100 | |
101 | === modified file 'src/app/favicon-fetcher.h' |
102 | --- src/app/favicon-fetcher.h 2014-11-26 16:24:53 +0000 |
103 | +++ src/app/favicon-fetcher.h 2015-03-20 16:26:56 +0000 |
104 | @@ -29,7 +29,7 @@ |
105 | class QNetworkAccessManager; |
106 | class QNetworkReply; |
107 | |
108 | -class FaviconFetcher Q_DECL_FINAL : public QObject |
109 | +class FaviconFetcher : public QObject |
110 | { |
111 | Q_OBJECT |
112 | |
113 | |
114 | === added directory 'src/app/unity8' |
115 | === added file 'src/app/unity8/CMakeLists.txt' |
116 | --- src/app/unity8/CMakeLists.txt 1970-01-01 00:00:00 +0000 |
117 | +++ src/app/unity8/CMakeLists.txt 2015-03-20 16:26:56 +0000 |
118 | @@ -0,0 +1,4 @@ |
119 | +project(unity8) |
120 | + |
121 | +add_subdirectory(libs) |
122 | +add_subdirectory(plugins) |
123 | |
124 | === added file 'src/app/unity8/README' |
125 | --- src/app/unity8/README 1970-01-01 00:00:00 +0000 |
126 | +++ src/app/unity8/README 2015-03-20 16:26:56 +0000 |
127 | @@ -0,0 +1,11 @@ |
128 | +Code in this directory was copied over from unity8. |
129 | + - first import: 2015-02-04, at revision 1583 of lp:unity8 |
130 | + - last sync: 2015-03-17, at revision 1663 of lp:unity8 |
131 | + |
132 | +The structure of the directories has been kept identical, to ease syncing the |
133 | +code in the future. Minor changes were made to the build system to integrate it |
134 | +with the existing webbrowser-app code base. |
135 | + |
136 | +The goal is to eventually have those components moved over to the UITK, thus |
137 | +removing the need for a copy here. See |
138 | +https://docs.google.com/a/canonical.com/document/d/1_ljZunMXWqJfrBGEkCp0peVD8gq6-E4ZMFpuH0PVKRc. |
139 | |
140 | === added directory 'src/app/unity8/libs' |
141 | === added file 'src/app/unity8/libs/CMakeLists.txt' |
142 | --- src/app/unity8/libs/CMakeLists.txt 1970-01-01 00:00:00 +0000 |
143 | +++ src/app/unity8/libs/CMakeLists.txt 2015-03-20 16:26:56 +0000 |
144 | @@ -0,0 +1,1 @@ |
145 | +add_subdirectory(UbuntuGestures) |
146 | |
147 | === added directory 'src/app/unity8/libs/UbuntuGestures' |
148 | === added file 'src/app/unity8/libs/UbuntuGestures/CMakeLists.txt' |
149 | --- src/app/unity8/libs/UbuntuGestures/CMakeLists.txt 1970-01-01 00:00:00 +0000 |
150 | +++ src/app/unity8/libs/UbuntuGestures/CMakeLists.txt 2015-03-20 16:26:56 +0000 |
151 | @@ -0,0 +1,36 @@ |
152 | +# in order to include Qt's private headers |
153 | +remove_definitions(-DQT_NO_KEYWORDS) |
154 | + |
155 | +set(UbuntuGestures_SOURCES |
156 | + CandidateInactivityTimer.cpp |
157 | + DebugHelpers.cpp |
158 | + Timer.cpp |
159 | + TouchOwnershipEvent.cpp |
160 | + TouchRegistry.cpp |
161 | + UnownedTouchEvent.cpp |
162 | +) |
163 | + |
164 | +add_definitions(-DUBUNTUGESTURES_LIBRARY) |
165 | + |
166 | +add_library(UbuntuGestures STATIC ${UbuntuGestures_SOURCES}) |
167 | + |
168 | +qt5_use_modules(UbuntuGestures Core Quick) |
169 | + |
170 | +# So that Foo.cpp can #include "Foo.moc" |
171 | +include_directories(${CMAKE_CURRENT_BINARY_DIR}) |
172 | + |
173 | +# There's no cmake var for v8 include path :-/ so create one |
174 | +LIST(GET Qt5Core_INCLUDE_DIRS 0 QtCoreDir0) |
175 | +if(${Qt5Core_VERSION_STRING} VERSION_LESS "5.1.0") |
176 | + SET(Qt5V8_PRIVATE_INCLUDE_DIR ${QtCoreDir0}/../QtV8/${Qt5Core_VERSION_STRING}/QtV8) |
177 | +else() |
178 | + SET(Qt5V8_PRIVATE_INCLUDE_DIR ${QtCoreDir0}/QtV8/${Qt5Core_VERSION_STRING}/QtV8) |
179 | +endif() |
180 | + |
181 | +# DANGER! DANGER! Using Qt's private API! |
182 | +include_directories( |
183 | + ${Qt5Qml_PRIVATE_INCLUDE_DIRS} |
184 | + ${Qt5Quick_INCLUDE_DIRS} |
185 | + ${Qt5Quick_PRIVATE_INCLUDE_DIRS} |
186 | + ${Qt5V8_PRIVATE_INCLUDE_DIR} |
187 | +) |
188 | |
189 | === added file 'src/app/unity8/libs/UbuntuGestures/CandidateInactivityTimer.cpp' |
190 | --- src/app/unity8/libs/UbuntuGestures/CandidateInactivityTimer.cpp 1970-01-01 00:00:00 +0000 |
191 | +++ src/app/unity8/libs/UbuntuGestures/CandidateInactivityTimer.cpp 2015-03-20 16:26:56 +0000 |
192 | @@ -0,0 +1,41 @@ |
193 | +/* |
194 | + * Copyright (C) 2014 Canonical, Ltd. |
195 | + * |
196 | + * This program is free software; you can redistribute it and/or modify |
197 | + * it under the terms of the GNU General Public License as published by |
198 | + * the Free Software Foundation; version 3. |
199 | + * |
200 | + * This program is distributed in the hope that it will be useful, |
201 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
202 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
203 | + * GNU General Public License for more details. |
204 | + * |
205 | + * You should have received a copy of the GNU General Public License |
206 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
207 | + */ |
208 | + |
209 | +#include "CandidateInactivityTimer.h" |
210 | + |
211 | +namespace UbuntuGestures { |
212 | + |
213 | +CandidateInactivityTimer::CandidateInactivityTimer(int touchId, QQuickItem *candidate, |
214 | + AbstractTimerFactory &timerFactory, QObject *parent) |
215 | + : QObject(parent) |
216 | + , m_touchId(touchId) |
217 | + , m_candidate(candidate) |
218 | +{ |
219 | + m_timer = timerFactory.createTimer(this); |
220 | + connect(m_timer, &AbstractTimer::timeout, |
221 | + this, &CandidateInactivityTimer::onTimeout); |
222 | + m_timer->setInterval(durationMs); |
223 | + m_timer->setSingleShot(true); |
224 | + m_timer->start(); |
225 | +} |
226 | + |
227 | +void CandidateInactivityTimer::onTimeout() |
228 | +{ |
229 | + qWarning("[TouchRegistry] Candidate for touch %d defaulted!", m_touchId); |
230 | + Q_EMIT candidateDefaulted(m_touchId, m_candidate); |
231 | +} |
232 | + |
233 | +} // namespace UbuntuGestures |
234 | |
235 | === added file 'src/app/unity8/libs/UbuntuGestures/CandidateInactivityTimer.h' |
236 | --- src/app/unity8/libs/UbuntuGestures/CandidateInactivityTimer.h 1970-01-01 00:00:00 +0000 |
237 | +++ src/app/unity8/libs/UbuntuGestures/CandidateInactivityTimer.h 2015-03-20 16:26:56 +0000 |
238 | @@ -0,0 +1,49 @@ |
239 | +/* |
240 | + * Copyright (C) 2014 Canonical, Ltd. |
241 | + * |
242 | + * This program is free software; you can redistribute it and/or modify |
243 | + * it under the terms of the GNU General Public License as published by |
244 | + * the Free Software Foundation; version 3. |
245 | + * |
246 | + * This program is distributed in the hope that it will be useful, |
247 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
248 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
249 | + * GNU General Public License for more details. |
250 | + * |
251 | + * You should have received a copy of the GNU General Public License |
252 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
253 | + */ |
254 | + |
255 | +#ifndef UBUNTUGESTURES_CANDIDATE_INACTIVITY_TIMER_H |
256 | +#define UBUNTUGESTURES_CANDIDATE_INACTIVITY_TIMER_H |
257 | + |
258 | +#include <QObject> |
259 | + |
260 | +class QQuickItem; |
261 | + |
262 | +#include "Timer.h" |
263 | + |
264 | +namespace UbuntuGestures { |
265 | + |
266 | +class UBUNTUGESTURES_EXPORT CandidateInactivityTimer : public QObject { |
267 | + Q_OBJECT |
268 | +public: |
269 | + CandidateInactivityTimer(int touchId, QQuickItem *candidate, |
270 | + AbstractTimerFactory &timerFactory, |
271 | + QObject *parent = nullptr); |
272 | + |
273 | + const int durationMs = 350; |
274 | + |
275 | +Q_SIGNALS: |
276 | + void candidateDefaulted(int touchId, QQuickItem *candidate); |
277 | +private Q_SLOTS: |
278 | + void onTimeout(); |
279 | +private: |
280 | + AbstractTimer *m_timer; |
281 | + int m_touchId; |
282 | + QQuickItem *m_candidate; |
283 | +}; |
284 | + |
285 | +} // namespace UbuntuGestures |
286 | + |
287 | +#endif // UBUNTUGESTURES_CANDIDATE_INACTIVITY_TIMER_H |
288 | |
289 | === added file 'src/app/unity8/libs/UbuntuGestures/DebugHelpers.cpp' |
290 | --- src/app/unity8/libs/UbuntuGestures/DebugHelpers.cpp 1970-01-01 00:00:00 +0000 |
291 | +++ src/app/unity8/libs/UbuntuGestures/DebugHelpers.cpp 2015-03-20 16:26:56 +0000 |
292 | @@ -0,0 +1,95 @@ |
293 | +/* |
294 | + * Copyright (C) 2014 Canonical, Ltd. |
295 | + * |
296 | + * This program is free software; you can redistribute it and/or modify |
297 | + * it under the terms of the GNU General Public License as published by |
298 | + * the Free Software Foundation; version 3. |
299 | + * |
300 | + * This program is distributed in the hope that it will be useful, |
301 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
302 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
303 | + * GNU General Public License for more details. |
304 | + * |
305 | + * You should have received a copy of the GNU General Public License |
306 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
307 | + */ |
308 | + |
309 | +#include "DebugHelpers.h" |
310 | +#include <QTouchEvent> |
311 | +#include <QMouseEvent> |
312 | + |
313 | +QString touchPointStateToString(Qt::TouchPointState state) |
314 | +{ |
315 | + switch (state) { |
316 | + case Qt::TouchPointPressed: |
317 | + return QString("pressed"); |
318 | + case Qt::TouchPointMoved: |
319 | + return QString("moved"); |
320 | + case Qt::TouchPointStationary: |
321 | + return QString("stationary"); |
322 | + case Qt::TouchPointReleased: |
323 | + return QString("released"); |
324 | + default: |
325 | + return QString("INVALID_STATE"); |
326 | + } |
327 | +} |
328 | + |
329 | +QString touchEventToString(const QTouchEvent *ev) |
330 | +{ |
331 | + QString message; |
332 | + |
333 | + switch (ev->type()) { |
334 | + case QEvent::TouchBegin: |
335 | + message.append("TouchBegin "); |
336 | + break; |
337 | + case QEvent::TouchUpdate: |
338 | + message.append("TouchUpdate "); |
339 | + break; |
340 | + case QEvent::TouchEnd: |
341 | + message.append("TouchEnd "); |
342 | + break; |
343 | + case QEvent::TouchCancel: |
344 | + message.append("TouchCancel "); |
345 | + break; |
346 | + default: |
347 | + message.append("INVALID_TOUCH_EVENT_TYPE "); |
348 | + } |
349 | + |
350 | + foreach(const QTouchEvent::TouchPoint& touchPoint, ev->touchPoints()) { |
351 | + message.append( |
352 | + QString("(id:%1, state:%2, scenePos:(%3,%4)) ") |
353 | + .arg(touchPoint.id()) |
354 | + .arg(touchPointStateToString(touchPoint.state())) |
355 | + .arg(touchPoint.scenePos().x()) |
356 | + .arg(touchPoint.scenePos().y()) |
357 | + ); |
358 | + } |
359 | + |
360 | + return message; |
361 | +} |
362 | + |
363 | +QString mouseEventToString(const QMouseEvent *ev) |
364 | +{ |
365 | + QString message; |
366 | + |
367 | + switch (ev->type()) { |
368 | + case QEvent::MouseButtonPress: |
369 | + message.append("MouseButtonPress "); |
370 | + break; |
371 | + case QEvent::MouseButtonRelease: |
372 | + message.append("MouseButtonRelease "); |
373 | + break; |
374 | + case QEvent::MouseButtonDblClick: |
375 | + message.append("MouseButtonDblClick "); |
376 | + break; |
377 | + case QEvent::MouseMove: |
378 | + message.append("MouseMove "); |
379 | + break; |
380 | + default: |
381 | + message.append("INVALID_MOUSE_EVENT_TYPE "); |
382 | + } |
383 | + |
384 | + message.append(QString("pos(%1, %2)").arg(ev->x()).arg(ev->y())); |
385 | + |
386 | + return message; |
387 | +} |
388 | |
389 | === added file 'src/app/unity8/libs/UbuntuGestures/DebugHelpers.h' |
390 | --- src/app/unity8/libs/UbuntuGestures/DebugHelpers.h 1970-01-01 00:00:00 +0000 |
391 | +++ src/app/unity8/libs/UbuntuGestures/DebugHelpers.h 2015-03-20 16:26:56 +0000 |
392 | @@ -0,0 +1,31 @@ |
393 | +/* |
394 | + * Copyright (C) 2014 Canonical, Ltd. |
395 | + * |
396 | + * This program is free software; you can redistribute it and/or modify |
397 | + * it under the terms of the GNU General Public License as published by |
398 | + * the Free Software Foundation; version 3. |
399 | + * |
400 | + * This program is distributed in the hope that it will be useful, |
401 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
402 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
403 | + * GNU General Public License for more details. |
404 | + * |
405 | + * You should have received a copy of the GNU General Public License |
406 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
407 | + */ |
408 | + |
409 | +#ifndef UBUNTUGESTURES_DEBUG_HELPER_H |
410 | +#define UBUNTUGESTURES_DEBUG_HELPER_H |
411 | + |
412 | +#include <QString> |
413 | + |
414 | +#include "UbuntuGesturesGlobal.h" |
415 | + |
416 | +class QMouseEvent; |
417 | +class QTouchEvent; |
418 | + |
419 | +UBUNTUGESTURES_EXPORT QString touchPointStateToString(Qt::TouchPointState state); |
420 | +UBUNTUGESTURES_EXPORT QString touchEventToString(const QTouchEvent *ev); |
421 | +UBUNTUGESTURES_EXPORT QString mouseEventToString(const QMouseEvent *ev); |
422 | + |
423 | +#endif // UBUNTUGESTURES_DEBUG_HELPER_H |
424 | |
425 | === added file 'src/app/unity8/libs/UbuntuGestures/Pool.h' |
426 | --- src/app/unity8/libs/UbuntuGestures/Pool.h 1970-01-01 00:00:00 +0000 |
427 | +++ src/app/unity8/libs/UbuntuGestures/Pool.h 2015-03-20 16:26:56 +0000 |
428 | @@ -0,0 +1,132 @@ |
429 | +/* |
430 | + * Copyright (C) 2014 Canonical, Ltd. |
431 | + * |
432 | + * This program is free software; you can redistribute it and/or modify |
433 | + * it under the terms of the GNU General Public License as published by |
434 | + * the Free Software Foundation; version 3. |
435 | + * |
436 | + * This program is distributed in the hope that it will be useful, |
437 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
438 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
439 | + * GNU General Public License for more details. |
440 | + * |
441 | + * You should have received a copy of the GNU General Public License |
442 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
443 | + */ |
444 | + |
445 | +#ifndef UBUNTUGESTURES_POOL_H |
446 | +#define UBUNTUGESTURES_POOL_H |
447 | + |
448 | +#include <QVector> |
449 | + |
450 | +#include "UbuntuGesturesGlobal.h" |
451 | + |
452 | +/* |
453 | + An object pool. |
454 | + Avoids unnecessary creations/initializations and deletions/destructions of items. Useful |
455 | + in a scenario where items are created and destroyed very frequently but the total number |
456 | + of items at any given time remains small. They're stored in a unordered fashion. |
457 | + |
458 | + To be used in Pool, ItemType needs to have the following methods: |
459 | + |
460 | + - ItemType(); |
461 | + |
462 | + A constructor that takes no parameters. An object contructed with it must return false if |
463 | + isValid() is called. |
464 | + |
465 | + - bool isValid() const; |
466 | + |
467 | + Returns wheter the object holds a valid , "filled" state or is empty. |
468 | + Used by Pool to check if the slot occupied by this object is actually available. |
469 | + |
470 | + - void reset(); |
471 | + |
472 | + Resets the object to its initial, empty, state. After calling this method, isValid() must |
473 | + return false. |
474 | + */ |
475 | +template <class ItemType> class Pool |
476 | +{ |
477 | +public: |
478 | + Pool() : m_lastUsedIndex(-1) { |
479 | + } |
480 | + |
481 | + class Iterator { |
482 | + public: |
483 | + Iterator() : index(-1), item(nullptr) {} |
484 | + Iterator(int index, ItemType *item) |
485 | + : index(index), item(item) {} |
486 | + |
487 | + ItemType *operator->() const { return item; } |
488 | + ItemType &operator*() const { return *item; } |
489 | + ItemType &value() const { return *item; } |
490 | + |
491 | + Iterator &operator= (const Iterator& other) { |
492 | + index = other.index; |
493 | + item = other.item; |
494 | + |
495 | + // by convention, always return *this |
496 | + return *this; |
497 | + } |
498 | + |
499 | + operator bool() const { return item != nullptr; } |
500 | + |
501 | + int index; |
502 | + ItemType *item; |
503 | + }; |
504 | + |
505 | + ItemType &getEmptySlot() { |
506 | + Q_ASSERT(m_lastUsedIndex < m_slots.size()); |
507 | + |
508 | + // Look for an in-between vacancy first |
509 | + for (int i = 0; i < m_lastUsedIndex; ++i) { |
510 | + ItemType &item = m_slots[i]; |
511 | + if (!item.isValid()) { |
512 | + return item; |
513 | + } |
514 | + } |
515 | + |
516 | + ++m_lastUsedIndex; |
517 | + if (m_lastUsedIndex >= m_slots.size()) { |
518 | + m_slots.resize(m_lastUsedIndex + 1); |
519 | + } |
520 | + |
521 | + return m_slots[m_lastUsedIndex]; |
522 | + } |
523 | + |
524 | + void freeSlot(Iterator &iterator) { |
525 | + m_slots[iterator.index].reset(); |
526 | + if (iterator.index == m_lastUsedIndex) { |
527 | + do { |
528 | + --m_lastUsedIndex; |
529 | + } while (m_lastUsedIndex >= 0 && !m_slots.at(m_lastUsedIndex).isValid()); |
530 | + } |
531 | + } |
532 | + |
533 | + // Iterates through all valid items (i.e. the occupied slots) |
534 | + // calling the given function, with the option of ending the loop early. |
535 | + // |
536 | + // bool Func(Iterator& item) |
537 | + // |
538 | + // Returning true means it wants to continue the "for" loop, false |
539 | + // terminates the loop. |
540 | + template<typename Func> void forEach(Func func) { |
541 | + Iterator it; |
542 | + for (it.index = 0; it.index <= m_lastUsedIndex; ++it.index) { |
543 | + it.item = &m_slots[it.index]; |
544 | + if (!it.item->isValid()) |
545 | + continue; |
546 | + |
547 | + if (!func(it)) |
548 | + break; |
549 | + } |
550 | + } |
551 | + |
552 | + bool isEmpty() const { return m_lastUsedIndex == -1; } |
553 | + |
554 | + |
555 | +private: |
556 | + QVector<ItemType> m_slots; |
557 | + int m_lastUsedIndex; |
558 | +}; |
559 | + |
560 | +#endif // UBUNTUGESTURES_POOL_H |
561 | |
562 | === added file 'src/app/unity8/libs/UbuntuGestures/Timer.cpp' |
563 | --- src/app/unity8/libs/UbuntuGestures/Timer.cpp 1970-01-01 00:00:00 +0000 |
564 | +++ src/app/unity8/libs/UbuntuGestures/Timer.cpp 2015-03-20 16:26:56 +0000 |
565 | @@ -0,0 +1,109 @@ |
566 | +/* |
567 | + * Copyright (C) 2014 Canonical, Ltd. |
568 | + * |
569 | + * This program is free software; you can redistribute it and/or modify |
570 | + * it under the terms of the GNU General Public License as published by |
571 | + * the Free Software Foundation; version 3. |
572 | + * |
573 | + * This program is distributed in the hope that it will be useful, |
574 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
575 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
576 | + * GNU General Public License for more details. |
577 | + * |
578 | + * You should have received a copy of the GNU General Public License |
579 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
580 | + */ |
581 | + |
582 | +#include "Timer.h" |
583 | + |
584 | +namespace UbuntuGestures { |
585 | + |
586 | +Timer::Timer(QObject *parent) : AbstractTimer(parent) |
587 | +{ |
588 | + m_timer.setSingleShot(false); |
589 | + connect(&m_timer, &QTimer::timeout, this, &AbstractTimer::timeout); |
590 | +} |
591 | + |
592 | +int Timer::interval() const |
593 | +{ |
594 | + return m_timer.interval(); |
595 | +} |
596 | + |
597 | +void Timer::setInterval(int msecs) |
598 | +{ |
599 | + m_timer.setInterval(msecs); |
600 | +} |
601 | + |
602 | +void Timer::start() |
603 | +{ |
604 | + m_timer.start(); |
605 | + AbstractTimer::start(); |
606 | +} |
607 | + |
608 | +void Timer::stop() |
609 | +{ |
610 | + m_timer.stop(); |
611 | + AbstractTimer::stop(); |
612 | +} |
613 | + |
614 | +bool Timer::isSingleShot() const |
615 | +{ |
616 | + return m_timer.isSingleShot(); |
617 | +} |
618 | + |
619 | +void Timer::setSingleShot(bool value) |
620 | +{ |
621 | + m_timer.setSingleShot(value); |
622 | +} |
623 | + |
624 | +/////////////////////////////////// FakeTimer ////////////////////////////////// |
625 | + |
626 | +FakeTimer::FakeTimer(QObject *parent) |
627 | + : UbuntuGestures::AbstractTimer(parent) |
628 | + , m_interval(0) |
629 | + , m_singleShot(false) |
630 | +{ |
631 | +} |
632 | + |
633 | +int FakeTimer::interval() const |
634 | +{ |
635 | + return m_interval; |
636 | +} |
637 | + |
638 | +void FakeTimer::setInterval(int msecs) |
639 | +{ |
640 | + m_interval = msecs; |
641 | +} |
642 | + |
643 | +bool FakeTimer::isSingleShot() const |
644 | +{ |
645 | + return m_singleShot; |
646 | +} |
647 | + |
648 | +void FakeTimer::setSingleShot(bool value) |
649 | +{ |
650 | + m_singleShot = value; |
651 | +} |
652 | + |
653 | +/////////////////////////////////// FakeTimerFactory ////////////////////////////////// |
654 | + |
655 | +AbstractTimer *FakeTimerFactory::createTimer(QObject *parent) |
656 | +{ |
657 | + FakeTimer *fakeTimer = new FakeTimer(parent); |
658 | + |
659 | + timers.append(fakeTimer); |
660 | + |
661 | + return fakeTimer; |
662 | +} |
663 | + |
664 | +void FakeTimerFactory::makeRunningTimersTimeout() |
665 | +{ |
666 | + for (int i = 0; i < timers.count(); ++i) { |
667 | + FakeTimer *timer = timers[i].data(); |
668 | + if (timer && timer->isRunning()) { |
669 | + timer->emitTimeout(); |
670 | + } |
671 | + } |
672 | +} |
673 | + |
674 | +} // namespace UbuntuGestures |
675 | |
676 | === added file 'src/app/unity8/libs/UbuntuGestures/Timer.h' |
677 | --- src/app/unity8/libs/UbuntuGestures/Timer.h 1970-01-01 00:00:00 +0000 |
678 | +++ src/app/unity8/libs/UbuntuGestures/Timer.h 2015-03-20 16:26:56 +0000 |
679 | @@ -0,0 +1,105 @@ |
680 | +/* |
681 | + * Copyright (C) 2014 Canonical, Ltd. |
682 | + * |
683 | + * This program is free software; you can redistribute it and/or modify |
684 | + * it under the terms of the GNU General Public License as published by |
685 | + * the Free Software Foundation; version 3. |
686 | + * |
687 | + * This program is distributed in the hope that it will be useful, |
688 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
689 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
690 | + * GNU General Public License for more details. |
691 | + * |
692 | + * You should have received a copy of the GNU General Public License |
693 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
694 | + */ |
695 | + |
696 | +#ifndef UBUNTUGESTURES_TIMER_H |
697 | +#define UBUNTUGESTURES_TIMER_H |
698 | + |
699 | +#include "UbuntuGesturesGlobal.h" |
700 | + |
701 | +#include <QObject> |
702 | +#include <QPointer> |
703 | +#include <QTimer> |
704 | + |
705 | +namespace UbuntuGestures { |
706 | + |
707 | +/* Defines an interface for a Timer. Useful for tests. */ |
708 | +class UBUNTUGESTURES_EXPORT AbstractTimer : public QObject |
709 | +{ |
710 | + Q_OBJECT |
711 | +public: |
712 | + AbstractTimer(QObject *parent) : QObject(parent), m_isRunning(false) {} |
713 | + virtual int interval() const = 0; |
714 | + virtual void setInterval(int msecs) = 0; |
715 | + virtual void start() { m_isRunning = true; } |
716 | + virtual void stop() { m_isRunning = false; } |
717 | + bool isRunning() const { return m_isRunning; } |
718 | + virtual bool isSingleShot() const = 0; |
719 | + virtual void setSingleShot(bool value) = 0; |
720 | +Q_SIGNALS: |
721 | + void timeout(); |
722 | +private: |
723 | + bool m_isRunning; |
724 | +}; |
725 | + |
726 | +/* Essentially a QTimer wrapper */ |
727 | +class UBUNTUGESTURES_EXPORT Timer : public AbstractTimer |
728 | +{ |
729 | + Q_OBJECT |
730 | +public: |
731 | + Timer(QObject *parent = nullptr); |
732 | + |
733 | + int interval() const override; |
734 | + void setInterval(int msecs) override; |
735 | + void start() override; |
736 | + void stop() override; |
737 | + bool isSingleShot() const override; |
738 | + void setSingleShot(bool value) override; |
739 | +private: |
740 | + QTimer m_timer; |
741 | +}; |
742 | + |
743 | +/* For tests */ |
744 | +class UBUNTUGESTURES_EXPORT FakeTimer : public AbstractTimer |
745 | +{ |
746 | + Q_OBJECT |
747 | +public: |
748 | + FakeTimer(QObject *parent = nullptr); |
749 | + |
750 | + virtual void emitTimeout() { Q_EMIT timeout(); } |
751 | + |
752 | + int interval() const override; |
753 | + void setInterval(int msecs) override; |
754 | + bool isSingleShot() const override; |
755 | + void setSingleShot(bool value) override; |
756 | +private: |
757 | + int m_interval; |
758 | + bool m_singleShot; |
759 | +}; |
760 | + |
761 | +class UBUNTUGESTURES_EXPORT AbstractTimerFactory |
762 | +{ |
763 | +public: |
764 | + virtual ~AbstractTimerFactory() {} |
765 | + virtual AbstractTimer *createTimer(QObject *parent = nullptr) = 0; |
766 | +}; |
767 | + |
768 | +class UBUNTUGESTURES_EXPORT TimerFactory : public AbstractTimerFactory |
769 | +{ |
770 | +public: |
771 | + AbstractTimer *createTimer(QObject *parent = nullptr) override { return new Timer(parent); } |
772 | +}; |
773 | + |
774 | +class UBUNTUGESTURES_EXPORT FakeTimerFactory : public AbstractTimerFactory |
775 | +{ |
776 | +public: |
777 | + AbstractTimer *createTimer(QObject *parent = nullptr) override; |
778 | + void makeRunningTimersTimeout(); |
779 | + QList<QPointer<FakeTimer>> timers; |
780 | +}; |
781 | + |
782 | +} // namespace UbuntuGestures |
783 | + |
784 | +#endif // UBUNTUGESTURES_TIMER_H |
785 | |
786 | === added file 'src/app/unity8/libs/UbuntuGestures/TouchOwnershipEvent.cpp' |
787 | --- src/app/unity8/libs/UbuntuGestures/TouchOwnershipEvent.cpp 1970-01-01 00:00:00 +0000 |
788 | +++ src/app/unity8/libs/UbuntuGestures/TouchOwnershipEvent.cpp 2015-03-20 16:26:56 +0000 |
789 | @@ -0,0 +1,35 @@ |
790 | +/* |
791 | + * Copyright (C) 2014 Canonical, Ltd. |
792 | + * |
793 | + * This program is free software; you can redistribute it and/or modify |
794 | + * it under the terms of the GNU General Public License as published by |
795 | + * the Free Software Foundation; version 3. |
796 | + * |
797 | + * This program is distributed in the hope that it will be useful, |
798 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
799 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
800 | + * GNU General Public License for more details. |
801 | + * |
802 | + * You should have received a copy of the GNU General Public License |
803 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
804 | + */ |
805 | + |
806 | +#include "TouchOwnershipEvent.h" |
807 | + |
808 | +QEvent::Type TouchOwnershipEvent::m_touchOwnershipType = (QEvent::Type)-1; |
809 | + |
810 | +TouchOwnershipEvent::TouchOwnershipEvent(int touchId, bool gained) |
811 | + : QEvent(touchOwnershipEventType()) |
812 | + , m_touchId(touchId) |
813 | + , m_gained(gained) |
814 | +{ |
815 | +} |
816 | + |
817 | +QEvent::Type TouchOwnershipEvent::touchOwnershipEventType() |
818 | +{ |
819 | + if (m_touchOwnershipType == (QEvent::Type)-1) { |
820 | + m_touchOwnershipType = (QEvent::Type)registerEventType(); |
821 | + } |
822 | + |
823 | + return m_touchOwnershipType; |
824 | +} |
825 | |
826 | === added file 'src/app/unity8/libs/UbuntuGestures/TouchOwnershipEvent.h' |
827 | --- src/app/unity8/libs/UbuntuGestures/TouchOwnershipEvent.h 1970-01-01 00:00:00 +0000 |
828 | +++ src/app/unity8/libs/UbuntuGestures/TouchOwnershipEvent.h 2015-03-20 16:26:56 +0000 |
829 | @@ -0,0 +1,50 @@ |
830 | +/* |
831 | + * Copyright (C) 2014 Canonical, Ltd. |
832 | + * |
833 | + * This program is free software; you can redistribute it and/or modify |
834 | + * it under the terms of the GNU General Public License as published by |
835 | + * the Free Software Foundation; version 3. |
836 | + * |
837 | + * This program is distributed in the hope that it will be useful, |
838 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
839 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
840 | + * GNU General Public License for more details. |
841 | + * |
842 | + * You should have received a copy of the GNU General Public License |
843 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
844 | + */ |
845 | + |
846 | +#ifndef UBUNTU_TOUCHOWNERSHIPEVENT_H |
847 | +#define UBUNTU_TOUCHOWNERSHIPEVENT_H |
848 | + |
849 | +#include <QEvent> |
850 | +#include "UbuntuGesturesGlobal.h" |
851 | + |
852 | +/* |
853 | + When an item get an ownership event for a touch it can grab/steal that touch |
854 | + with a clean conscience. |
855 | + */ |
856 | +class UBUNTUGESTURES_EXPORT TouchOwnershipEvent : public QEvent |
857 | +{ |
858 | +public: |
859 | + TouchOwnershipEvent(int touchId, bool gained); |
860 | + |
861 | + static Type touchOwnershipEventType(); |
862 | + |
863 | + /* |
864 | + Whether ownership was gained (true) or lost (false) |
865 | + */ |
866 | + bool gained() const { return m_gained; } |
867 | + |
868 | + /* |
869 | + Id of the touch whose ownership was granted. |
870 | + */ |
871 | + int touchId() const { return m_touchId; } |
872 | + |
873 | +private: |
874 | + static Type m_touchOwnershipType; |
875 | + int m_touchId; |
876 | + bool m_gained; |
877 | +}; |
878 | + |
879 | +#endif // UBUNTU_TOUCHOWNERSHIPEVENT_H |
880 | |
881 | === added file 'src/app/unity8/libs/UbuntuGestures/TouchRegistry.cpp' |
882 | --- src/app/unity8/libs/UbuntuGestures/TouchRegistry.cpp 1970-01-01 00:00:00 +0000 |
883 | +++ src/app/unity8/libs/UbuntuGestures/TouchRegistry.cpp 2015-03-20 16:26:56 +0000 |
884 | @@ -0,0 +1,500 @@ |
885 | +/* |
886 | + * Copyright (C) 2014 Canonical, Ltd. |
887 | + * |
888 | + * This program is free software; you can redistribute it and/or modify |
889 | + * it under the terms of the GNU General Public License as published by |
890 | + * the Free Software Foundation; version 3. |
891 | + * |
892 | + * This program is distributed in the hope that it will be useful, |
893 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
894 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
895 | + * GNU General Public License for more details. |
896 | + * |
897 | + * You should have received a copy of the GNU General Public License |
898 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
899 | + */ |
900 | + |
901 | +#include "TouchRegistry.h" |
902 | + |
903 | +#include <QCoreApplication> |
904 | +#include <QDebug> |
905 | + |
906 | +#pragma GCC diagnostic push |
907 | +#pragma GCC diagnostic ignored "-pedantic" |
908 | +#include <private/qquickitem_p.h> |
909 | +#pragma GCC diagnostic pop |
910 | + |
911 | +#include "CandidateInactivityTimer.h" |
912 | +#include "Timer.h" |
913 | +#include "TouchOwnershipEvent.h" |
914 | +#include "UnownedTouchEvent.h" |
915 | + |
916 | +#define TOUCHREGISTRY_DEBUG 0 |
917 | + |
918 | +#if TOUCHREGISTRY_DEBUG |
919 | + #include "DebugHelpers.h" |
920 | + #define UG_DEBUG qDebug() << "[TouchRegistry]" |
921 | +#endif // TOUCHREGISTRY_DEBUG |
922 | + |
923 | +using namespace UbuntuGestures; |
924 | + |
925 | +TouchRegistry *TouchRegistry::m_instance = nullptr; |
926 | + |
927 | +TouchRegistry::TouchRegistry(QObject *parent) |
928 | + : TouchRegistry(parent, new TimerFactory) |
929 | +{ |
930 | +} |
931 | + |
932 | +TouchRegistry::TouchRegistry(QObject *parent, AbstractTimerFactory *timerFactory) |
933 | + : QObject(parent) |
934 | + , m_inDispatchLoop(false) |
935 | + , m_timerFactory(timerFactory) |
936 | +{ |
937 | + if (m_instance == nullptr) { |
938 | + m_instance = this; |
939 | + } else { |
940 | + qFatal("Cannot have more than one instance of TouchRegistry. It must be a singleton."); |
941 | + } |
942 | +} |
943 | + |
944 | +TouchRegistry::~TouchRegistry() |
945 | +{ |
946 | + Q_ASSERT(m_instance != nullptr); |
947 | + m_instance = nullptr; |
948 | + delete m_timerFactory; |
949 | +} |
950 | + |
951 | +void TouchRegistry::update(const QTouchEvent *event) |
952 | +{ |
953 | + #if TOUCHREGISTRY_DEBUG |
954 | + UG_DEBUG << "got" << qPrintable(touchEventToString(event)); |
955 | + #endif |
956 | + |
957 | + const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints(); |
958 | + for (int i = 0; i < touchPoints.count(); ++i) { |
959 | + const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i); |
960 | + if (touchPoint.state() == Qt::TouchPointPressed) { |
961 | + TouchInfo &touchInfo = m_touchInfoPool.getEmptySlot(); |
962 | + touchInfo.init(touchPoint.id()); |
963 | + } else if (touchPoint.state() == Qt::TouchPointReleased) { |
964 | + Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(touchPoint.id()); |
965 | + |
966 | + touchInfo->physicallyEnded = true; |
967 | + } |
968 | + } |
969 | + |
970 | + deliverTouchUpdatesToUndecidedCandidatesAndWatchers(event); |
971 | + |
972 | + freeEndedTouchInfos(); |
973 | +} |
974 | + |
975 | +void TouchRegistry::deliverTouchUpdatesToUndecidedCandidatesAndWatchers(const QTouchEvent *event) |
976 | +{ |
977 | + // TODO: Look into how we could optimize this whole thing. |
978 | + // Although it's not really a problem as we should have at most two candidates |
979 | + // for each point and there should not be many active points at any given moment. |
980 | + // But having three nested for-loops does scare. |
981 | + |
982 | + // TODO: Don't send it to the object that is already receiving the regular event |
983 | + // because QQuickWindow is sending it to him (i.e., he's the touch owner from Qt's point of view) |
984 | + // Problem is, we cannnot easily get this information. |
985 | + |
986 | + const QList<QTouchEvent::TouchPoint> &updatedTouchPoints = event->touchPoints(); |
987 | + |
988 | + // Maps an item to the touches in this event he should be informed about. |
989 | + // E.g.: a QTouchEvent might have three touches but a given item might be interested in only |
990 | + // one of them. So he will get a UnownedTouchEvent from this QTouchEvent containing only that |
991 | + // touch point. |
992 | + QMap<QQuickItem*, QList<int>> touchIdsForItems; |
993 | + |
994 | + // Build touchIdsForItems |
995 | + m_touchInfoPool.forEach([&](Pool<TouchInfo>::Iterator &touchInfo) { |
996 | + if (touchInfo->isOwned() && touchInfo->watchers.isEmpty()) |
997 | + return true; |
998 | + |
999 | + for (int j = 0; j < updatedTouchPoints.count(); ++j) { |
1000 | + if (updatedTouchPoints[j].id() == touchInfo->id) { |
1001 | + if (!touchInfo->isOwned()) { |
1002 | + for (int i = 0; i < touchInfo->candidates.count(); ++i) { |
1003 | + CandidateInfo &candidate = touchInfo->candidates[i]; |
1004 | + Q_ASSERT(!candidate.item.isNull()); |
1005 | + touchIdsForItems[candidate.item.data()].append(touchInfo->id); |
1006 | + } |
1007 | + } |
1008 | + |
1009 | + const QList<QPointer<QQuickItem>> &watchers = touchInfo->watchers; |
1010 | + for (int i = 0; i < watchers.count(); ++i) { |
1011 | + if (!watchers[i].isNull()) { |
1012 | + touchIdsForItems[watchers[i].data()].append(touchInfo->id); |
1013 | + } |
1014 | + } |
1015 | + |
1016 | + return true; |
1017 | + } |
1018 | + } |
1019 | + |
1020 | + return true; |
1021 | + }); |
1022 | + |
1023 | + // TODO: Consider what happens if an item calls any of TouchRegistry's public methods |
1024 | + // from the event handler callback. |
1025 | + m_inDispatchLoop = true; |
1026 | + auto it = touchIdsForItems.constBegin(); |
1027 | + while (it != touchIdsForItems.constEnd()) { |
1028 | + QQuickItem *item = it.key(); |
1029 | + const QList<int> &touchIds = it.value(); |
1030 | + dispatchPointsToItem(event, touchIds, item); |
1031 | + ++it; |
1032 | + }; |
1033 | + m_inDispatchLoop = false; |
1034 | +} |
1035 | + |
1036 | +void TouchRegistry::freeEndedTouchInfos() |
1037 | +{ |
1038 | + m_touchInfoPool.forEach([&](Pool<TouchInfo>::Iterator &touchInfo) { |
1039 | + if (touchInfo->ended()) { |
1040 | + m_touchInfoPool.freeSlot(touchInfo); |
1041 | + } |
1042 | + return true; |
1043 | + }); |
1044 | +} |
1045 | + |
1046 | +/* |
1047 | + Extracts the touches with the given touchIds from event and send them in a |
1048 | + UnownedTouchEvent to the given item |
1049 | + */ |
1050 | +void TouchRegistry::dispatchPointsToItem(const QTouchEvent *event, const QList<int> &touchIds, |
1051 | + QQuickItem *item) |
1052 | +{ |
1053 | + Qt::TouchPointStates touchPointStates = 0; |
1054 | + QList<QTouchEvent::TouchPoint> touchPoints; |
1055 | + |
1056 | + const QList<QTouchEvent::TouchPoint> &allTouchPoints = event->touchPoints(); |
1057 | + |
1058 | + QTransform windowToCandidateTransform = QQuickItemPrivate::get(item)->windowToItemTransform(); |
1059 | + QMatrix4x4 windowToCandidateMatrix(windowToCandidateTransform); |
1060 | + |
1061 | + for (int i = 0; i < allTouchPoints.count(); ++i) { |
1062 | + const QTouchEvent::TouchPoint &originalTouchPoint = allTouchPoints[i]; |
1063 | + if (touchIds.contains(originalTouchPoint.id())) { |
1064 | + QTouchEvent::TouchPoint touchPoint = originalTouchPoint; |
1065 | + |
1066 | + translateTouchPointFromScreenToWindowCoords(touchPoint); |
1067 | + |
1068 | + // Set the point's local coordinates to that of the item |
1069 | + touchPoint.setRect(windowToCandidateTransform.mapRect(touchPoint.sceneRect())); |
1070 | + touchPoint.setStartPos(windowToCandidateTransform.map(touchPoint.startScenePos())); |
1071 | + touchPoint.setLastPos(windowToCandidateTransform.map(touchPoint.lastScenePos())); |
1072 | + touchPoint.setVelocity(windowToCandidateMatrix.mapVector(touchPoint.velocity()).toVector2D()); |
1073 | + |
1074 | + touchPoints.append(touchPoint); |
1075 | + touchPointStates |= touchPoint.state(); |
1076 | + } |
1077 | + } |
1078 | + |
1079 | + QTouchEvent *eventForItem = new QTouchEvent(event->type(), |
1080 | + event->device(), |
1081 | + event->modifiers(), |
1082 | + touchPointStates, |
1083 | + touchPoints); |
1084 | + eventForItem->setWindow(event->window()); |
1085 | + eventForItem->setTimestamp(event->timestamp()); |
1086 | + eventForItem->setTarget(event->target()); |
1087 | + |
1088 | + UnownedTouchEvent unownedTouchEvent(eventForItem); |
1089 | + |
1090 | + #if TOUCHREGISTRY_DEBUG |
1091 | + UG_DEBUG << "Sending unowned" << qPrintable(touchEventToString(eventForItem)) |
1092 | + << "to" << item; |
1093 | + #endif |
1094 | + |
1095 | + QCoreApplication::sendEvent(item, &unownedTouchEvent); |
1096 | +} |
1097 | + |
1098 | +void TouchRegistry::translateTouchPointFromScreenToWindowCoords(QTouchEvent::TouchPoint &touchPoint) |
1099 | +{ |
1100 | + touchPoint.setScreenRect(touchPoint.sceneRect()); |
1101 | + touchPoint.setStartScreenPos(touchPoint.startScenePos()); |
1102 | + touchPoint.setLastScreenPos(touchPoint.lastScenePos()); |
1103 | + |
1104 | + touchPoint.setSceneRect(touchPoint.rect()); |
1105 | + touchPoint.setStartScenePos(touchPoint.startPos()); |
1106 | + touchPoint.setLastScenePos(touchPoint.lastPos()); |
1107 | +} |
1108 | + |
1109 | +bool TouchRegistry::eventFilter(QObject *watched, QEvent *event) |
1110 | +{ |
1111 | + Q_UNUSED(watched); |
1112 | + |
1113 | + switch (event->type()) { |
1114 | + case QEvent::TouchBegin: |
1115 | + case QEvent::TouchUpdate: |
1116 | + case QEvent::TouchEnd: |
1117 | + case QEvent::TouchCancel: |
1118 | + update(static_cast<QTouchEvent*>(event)); |
1119 | + break; |
1120 | + default: |
1121 | + // do nothing |
1122 | + break; |
1123 | + } |
1124 | + |
1125 | + // Do not filter out the event. i.e., let it be handled further as |
1126 | + // we're just monitoring events |
1127 | + return false; |
1128 | +} |
1129 | + |
1130 | +void TouchRegistry::addCandidateOwnerForTouch(int id, QQuickItem *candidate) |
1131 | +{ |
1132 | + #if TOUCHREGISTRY_DEBUG |
1133 | + UG_DEBUG << "addCandidateOwnerForTouch id" << id << "candidate" << candidate; |
1134 | + #endif |
1135 | + |
1136 | + Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id); |
1137 | + if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); } |
1138 | + |
1139 | + if (touchInfo->isOwned()) { |
1140 | + qWarning("TouchRegistry: trying to add candidate owner for a touch that's already owned"); |
1141 | + return; |
1142 | + } |
1143 | + |
1144 | + // TODO: Check if candidate already exists |
1145 | + |
1146 | + CandidateInfo candidateInfo; |
1147 | + candidateInfo.undecided = true; |
1148 | + candidateInfo.item = candidate; |
1149 | + candidateInfo.inactivityTimer = new CandidateInactivityTimer(id, candidate, |
1150 | + *m_timerFactory, |
1151 | + this); |
1152 | + connect(candidateInfo.inactivityTimer, &CandidateInactivityTimer::candidateDefaulted, |
1153 | + this, &TouchRegistry::rejectCandidateOwnerForTouch); |
1154 | + |
1155 | + touchInfo->candidates.append(candidateInfo); |
1156 | +} |
1157 | + |
1158 | +void TouchRegistry::addTouchWatcher(int touchId, QQuickItem *watcher) |
1159 | +{ |
1160 | + #if TOUCHREGISTRY_DEBUG |
1161 | + UG_DEBUG << "addTouchWatcher id" << touchId << "watcher" << watcher; |
1162 | + #endif |
1163 | + |
1164 | + Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(touchId); |
1165 | + if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); } |
1166 | + |
1167 | + // TODO: Check if watcher already exists |
1168 | + |
1169 | + touchInfo->watchers.append(watcher); |
1170 | +} |
1171 | + |
1172 | +void TouchRegistry::removeCandidateOwnerForTouch(int id, QQuickItem *candidate) |
1173 | +{ |
1174 | + #if TOUCHREGISTRY_DEBUG |
1175 | + UG_DEBUG << "removeCandidateOwnerForTouch id" << id << "candidate" << candidate; |
1176 | + #endif |
1177 | + |
1178 | + Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id); |
1179 | + if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); } |
1180 | + |
1181 | + int indexRemoved = -1; |
1182 | + |
1183 | + // TODO: check if the candidate is in fact the owner of the touch |
1184 | + |
1185 | + for (int i = 0; i < touchInfo->candidates.count() && indexRemoved == -1; ++i) { |
1186 | + CandidateInfo &candidateInfo = touchInfo->candidates[i]; |
1187 | + if (candidateInfo.item == candidate) { |
1188 | + Q_ASSERT(i > 0 || candidateInfo.undecided); |
1189 | + if (i == 0 && !candidateInfo.undecided) { |
1190 | + qCritical("TouchRegistry: touch owner is being removed."); |
1191 | + } |
1192 | + delete candidateInfo.inactivityTimer; |
1193 | + candidateInfo.inactivityTimer = nullptr; |
1194 | + touchInfo->candidates.removeAt(i); |
1195 | + indexRemoved = i; |
1196 | + } |
1197 | + } |
1198 | + |
1199 | + if (indexRemoved == 0) { |
1200 | + // the top candidate has been removed. if the new top candidate |
1201 | + // wants the touch let him know he's now the owner. |
1202 | + if (touchInfo->isOwned()) { |
1203 | + touchInfo->notifyCandidatesOfOwnershipResolution(); |
1204 | + } |
1205 | + } |
1206 | + |
1207 | + if (!m_inDispatchLoop && touchInfo->ended()) { |
1208 | + m_touchInfoPool.freeSlot(touchInfo); |
1209 | + } |
1210 | +} |
1211 | + |
1212 | +void TouchRegistry::requestTouchOwnership(int id, QQuickItem *candidate) |
1213 | +{ |
1214 | + #if TOUCHREGISTRY_DEBUG |
1215 | + UG_DEBUG << "requestTouchOwnership id " << id << "candidate" << candidate; |
1216 | + #endif |
1217 | + |
1218 | + Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id); |
1219 | + if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); } |
1220 | + |
1221 | + Q_ASSERT(!touchInfo->isOwned()); |
1222 | + |
1223 | + int candidateIndex = -1; |
1224 | + for (int i = 0; i < touchInfo->candidates.count(); ++i) { |
1225 | + CandidateInfo &candidateInfo = touchInfo->candidates[i]; |
1226 | + if (candidateInfo.item == candidate) { |
1227 | + candidateInfo.undecided = false; |
1228 | + delete candidateInfo.inactivityTimer; |
1229 | + candidateInfo.inactivityTimer = nullptr; |
1230 | + candidateIndex = i; |
1231 | + break; |
1232 | + } |
1233 | + } |
1234 | + |
1235 | + // add it as a candidate if not present yet |
1236 | + if (candidateIndex < 0) { |
1237 | + CandidateInfo candidateInfo; |
1238 | + candidateInfo.undecided = false; |
1239 | + candidateInfo.item = candidate; |
1240 | + candidateInfo.inactivityTimer = nullptr; |
1241 | + touchInfo->candidates.append(candidateInfo); |
1242 | + // it's the last one |
1243 | + candidateIndex = touchInfo->candidates.count() - 1; |
1244 | + } |
1245 | + |
1246 | + // If it's the top candidate it means it's now the owner. Let |
1247 | + // it know about it. |
1248 | + if (candidateIndex == 0) { |
1249 | + touchInfo->notifyCandidatesOfOwnershipResolution(); |
1250 | + } |
1251 | +} |
1252 | + |
1253 | +Pool<TouchRegistry::TouchInfo>::Iterator TouchRegistry::findTouchInfo(int id) |
1254 | +{ |
1255 | + Pool<TouchInfo>::Iterator touchInfo; |
1256 | + |
1257 | + m_touchInfoPool.forEach([&](Pool<TouchInfo>::Iterator &someTouchInfo) -> bool { |
1258 | + if (someTouchInfo->id == id) { |
1259 | + touchInfo = someTouchInfo; |
1260 | + return false; |
1261 | + } else { |
1262 | + return true; |
1263 | + } |
1264 | + }); |
1265 | + |
1266 | + return touchInfo; |
1267 | +} |
1268 | + |
1269 | + |
1270 | +void TouchRegistry::rejectCandidateOwnerForTouch(int id, QQuickItem *candidate) |
1271 | +{ |
1272 | + // NB: It's technically possible that candidate is a dangling pointer at this point. |
1273 | + // Although that would most likely be due to a bug in our code. |
1274 | + // In any case, only dereference it after it's confirmed that it indeed exists. |
1275 | + |
1276 | + #if TOUCHREGISTRY_DEBUG |
1277 | + UG_DEBUG << "rejectCandidateOwnerForTouch id" << id << "candidate" << (void*)candidate; |
1278 | + #endif |
1279 | + |
1280 | + Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id); |
1281 | + if (!touchInfo) { |
1282 | + #if TOUCHREGISTRY_DEBUG |
1283 | + UG_DEBUG << "Failed to find TouchInfo for id" << id; |
1284 | + #endif |
1285 | + return; |
1286 | + } |
1287 | + |
1288 | + int rejectedCandidateIndex = -1; |
1289 | + |
1290 | + // Check if the given candidate is valid and still undecided |
1291 | + for (int i = 0; i < touchInfo->candidates.count() && rejectedCandidateIndex == -1; ++i) { |
1292 | + CandidateInfo &candidateInfo = touchInfo->candidates[i]; |
1293 | + if (candidateInfo.item == candidate) { |
1294 | + Q_ASSERT(i > 0 || candidateInfo.undecided); |
1295 | + if (i == 0 && !candidateInfo.undecided) { |
1296 | + qCritical() << "TouchRegistry: Can't reject item (" << (void*)candidate |
1297 | + << ") as it already owns touch" << id; |
1298 | + return; |
1299 | + } else { |
1300 | + // we found the guy and it's all fine. |
1301 | + rejectedCandidateIndex = i; |
1302 | + } |
1303 | + } |
1304 | + } |
1305 | + |
1306 | + // If we reached this point it's because the given candidate exists and is indeed undecided. |
1307 | + |
1308 | + Q_ASSERT(rejectedCandidateIndex >= 0 && rejectedCandidateIndex < touchInfo->candidates.size()); |
1309 | + |
1310 | + { |
1311 | + TouchOwnershipEvent lostOwnershipEvent(id, false /*gained*/); |
1312 | + QCoreApplication::sendEvent(candidate, &lostOwnershipEvent); |
1313 | + } |
1314 | + |
1315 | + touchInfo->candidates.removeAt(rejectedCandidateIndex); |
1316 | + |
1317 | + if (rejectedCandidateIndex == 0) { |
1318 | + // the top candidate has been removed. if the new top candidate |
1319 | + // wants the touch let him know he's now the owner. |
1320 | + if (touchInfo->isOwned()) { |
1321 | + touchInfo->notifyCandidatesOfOwnershipResolution(); |
1322 | + } |
1323 | + } |
1324 | +} |
1325 | + |
1326 | +////////////////////////////////////// TouchRegistry::TouchInfo //////////////////////////////////// |
1327 | + |
1328 | +TouchRegistry::TouchInfo::TouchInfo(int id) |
1329 | +{ |
1330 | + init(id); |
1331 | +} |
1332 | + |
1333 | +void TouchRegistry::TouchInfo::reset() |
1334 | +{ |
1335 | + id = -1; |
1336 | + |
1337 | + for (int i = 0; i < candidates.count(); ++i) { |
1338 | + CandidateInfo &candidate = candidates[i]; |
1339 | + delete candidate.inactivityTimer; |
1340 | + candidate.inactivityTimer.clear(); // shoundn't be needed but anyway... |
1341 | + } |
1342 | +} |
1343 | + |
1344 | +void TouchRegistry::TouchInfo::init(int id) |
1345 | +{ |
1346 | + this->id = id; |
1347 | + physicallyEnded = false; |
1348 | + candidates.clear(); |
1349 | + watchers.clear(); |
1350 | +} |
1351 | + |
1352 | +bool TouchRegistry::TouchInfo::isOwned() const |
1353 | +{ |
1354 | + return !candidates.isEmpty() && !candidates.first().undecided; |
1355 | +} |
1356 | + |
1357 | +bool TouchRegistry::TouchInfo::ended() const |
1358 | +{ |
1359 | + Q_ASSERT(isValid()); |
1360 | + return physicallyEnded && (isOwned() || candidates.isEmpty()); |
1361 | +} |
1362 | + |
1363 | +void TouchRegistry::TouchInfo::notifyCandidatesOfOwnershipResolution() |
1364 | +{ |
1365 | + Q_ASSERT(isOwned()); |
1366 | + |
1367 | + #if TOUCHREGISTRY_DEBUG |
1368 | + UG_DEBUG << "sending TouchOwnershipEvent(id =" << id |
1369 | + << " gained) to candidate" << candidates[0].item; |
1370 | + #endif |
1371 | + |
1372 | + TouchOwnershipEvent gainedOwnershipEvent(id, true /*gained*/); |
1373 | + QCoreApplication::sendEvent(candidates[0].item, &gainedOwnershipEvent); |
1374 | + |
1375 | + |
1376 | + TouchOwnershipEvent lostOwnershipEvent(id, false /*gained*/); |
1377 | + for (int i = 1; i < candidates.count(); ++i) { |
1378 | + #if TOUCHREGISTRY_DEBUG |
1379 | + UG_DEBUG << "sending TouchWonershipEvent(id =" << id << " lost) to candidate" |
1380 | + << candidates[i].item; |
1381 | + #endif |
1382 | + QCoreApplication::sendEvent(candidates[i].item, &lostOwnershipEvent); |
1383 | + } |
1384 | +} |
1385 | |
1386 | === added file 'src/app/unity8/libs/UbuntuGestures/TouchRegistry.h' |
1387 | --- src/app/unity8/libs/UbuntuGestures/TouchRegistry.h 1970-01-01 00:00:00 +0000 |
1388 | +++ src/app/unity8/libs/UbuntuGestures/TouchRegistry.h 2015-03-20 16:26:56 +0000 |
1389 | @@ -0,0 +1,182 @@ |
1390 | +/* |
1391 | + * Copyright (C) 2014 Canonical, Ltd. |
1392 | + * |
1393 | + * This program is free software; you can redistribute it and/or modify |
1394 | + * it under the terms of the GNU General Public License as published by |
1395 | + * the Free Software Foundation; version 3. |
1396 | + * |
1397 | + * This program is distributed in the hope that it will be useful, |
1398 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1399 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1400 | + * GNU General Public License for more details. |
1401 | + * |
1402 | + * You should have received a copy of the GNU General Public License |
1403 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1404 | + */ |
1405 | + |
1406 | +#ifndef UNITY_TOUCHREGISTRY_H |
1407 | +#define UNITY_TOUCHREGISTRY_H |
1408 | + |
1409 | +#include <QQuickItem> |
1410 | +#include <QObject> |
1411 | +#include <QPointer> |
1412 | +#include <QTouchEvent> |
1413 | +#include <QVector> |
1414 | + |
1415 | +#include "UbuntuGesturesGlobal.h" |
1416 | +#include "CandidateInactivityTimer.h" |
1417 | +#include "Pool.h" |
1418 | + |
1419 | +namespace UbuntuGestures { |
1420 | + class AbstractTimerFactory; |
1421 | +} |
1422 | + |
1423 | +/* |
1424 | + Where the ownership of touches is registered. |
1425 | + |
1426 | + Singleton used for adding a touch point ownership model analogous to the one |
1427 | + described in the XInput 2.2 protocol[1] on top of the existing input dispatch logic in QQuickWindow. |
1428 | + |
1429 | + It provides a much more flexible and powerful way of dealing with pointer ownership than the existing |
1430 | + mechanisms in Qt. Namely QQuickItem::grabTouchPoints, QuickItem::keepTouchGrab, |
1431 | + QQuickItem::setFiltersChildMouseEvents, QQuickItem::ungrabTouchPoints and QQuickItem::touchUngrabEvent. |
1432 | + |
1433 | + Usage: |
1434 | + |
1435 | + 1- An item receives a a new touch point. If he's not sure whether he wants it yet, he calls: |
1436 | + TouchRegistry::instance()->addCandidateOwnerForTouch(touchId, this); |
1437 | + touchEvent->ignore(); |
1438 | + Ignoring the event is crucial so that it can be seen by other interested parties, which will |
1439 | + behave similarly. |
1440 | + |
1441 | + 2- That item will then start receiving UnownedTouchEvents for that touch from step 1. Once he's |
1442 | + made a decision he calls either: |
1443 | + TouchRegistry::instance()->requestTouchOwnership(touchId, this); |
1444 | + If he wants the touch point or: |
1445 | + TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, this); |
1446 | + if he does not want it. |
1447 | + |
1448 | + Candidates are put in a priority queue. The first one to call addCandidateOwnerForTouch() will |
1449 | + take precedence over the others for receiving ownership over the touch point (from now on called |
1450 | + simply top-candidate). |
1451 | + |
1452 | + If the top-candidate calls requestTouchOwnership() he will immediately receive a |
1453 | + TouchOwnershipEvent(gained=true) for that touch point. He can then safely call |
1454 | + QQuickItem::grabTouchPoints to actually get the owned touch points. The other candidates |
1455 | + will receive TouchOwnershipEvent(gained=false) and will no longer receive UnownedTouchEvents |
1456 | + for that touch point. They will have to undo whatever action they were performing with that |
1457 | + touch point. |
1458 | + |
1459 | + But if the top-candidate calls removeCandidateOwnerForTouch() instead, he's popped from the |
1460 | + candidacy queue and ownership is given to the new top-most candidate if he has already |
1461 | + made his decision, that is. |
1462 | + |
1463 | + The TouchRegistry cannot enforce the results of this pointer ownership negotiation (i.e., |
1464 | + who gets to grab the touch points) as that would clash with QQuickWindow's input event |
1465 | + dispatching logic. The candidates have to respect the decision and grab the touch points |
1466 | + themselves. |
1467 | + |
1468 | + If an item wants ownership over touches as soon as he receives the TouchBegin for them, his step 1 |
1469 | + would be instead: |
1470 | + TouchRegistry::instance()->requestTouchOwnership(touchId, this); |
1471 | + return true; |
1472 | + He would then be notified once ownership has been granted to him, from which point onwards he could |
1473 | + safely assume other TouchRegistry users wouldn't snatch this touch away from him. |
1474 | + |
1475 | + Items oblivious to TouchRegistry will lose their touch points without warning, just like in plain Qt. |
1476 | + |
1477 | + [1] - http://www.x.org/releases/X11R7.7/doc/inputproto/XI2proto.txt (see multitouch-ownership) |
1478 | + */ |
1479 | +class UBUNTUGESTURES_EXPORT TouchRegistry : public QObject |
1480 | +{ |
1481 | + Q_OBJECT |
1482 | +public: |
1483 | + TouchRegistry(QObject *parent = nullptr); |
1484 | + // Useful for tests, where you should feed a fake timer |
1485 | + TouchRegistry(QObject *parent, UbuntuGestures::AbstractTimerFactory *timerFactory); |
1486 | + |
1487 | + virtual ~TouchRegistry(); |
1488 | + |
1489 | + // Returns a pointer to the application's TouchRegistry instance. |
1490 | + // If no instance has been allocated, null is returned. |
1491 | + static TouchRegistry *instance() { return m_instance; } |
1492 | + |
1493 | + void update(const QTouchEvent *event); |
1494 | + |
1495 | + // Calls update() if the given event is a QTouchEvent |
1496 | + bool eventFilter(QObject *watched, QEvent *event) override; |
1497 | + |
1498 | + // An item that might later request ownership over the given touch point. |
1499 | + // He will be kept informed about that touch point through UnownedTouchEvents |
1500 | + // All candidates must eventually decide whether they want to own the touch point |
1501 | + // or not. That decision is informed through requestTouchOwnership() or |
1502 | + // removeCandidateOwnerForTouch() |
1503 | + void addCandidateOwnerForTouch(int id, QQuickItem *candidate); |
1504 | + |
1505 | + // The same as rejecting ownership of a touch |
1506 | + void removeCandidateOwnerForTouch(int id, QQuickItem *candidate); |
1507 | + |
1508 | + // The candidate object wants to be the owner of the touch with the given id. |
1509 | + // If he's currently the oldest/top-most candidate, he will get an ownership |
1510 | + // event immediately. If not, he will get ownership if (or once) he becomes the |
1511 | + // top-most candidate. |
1512 | + void requestTouchOwnership(int id, QQuickItem *candidate); |
1513 | + |
1514 | + // An item that has no interest (effective or potential) in owning a touch point |
1515 | + // but would nonetheless like to be kept up-to-date on its state. |
1516 | + void addTouchWatcher(int touchId, QQuickItem *watcherItem); |
1517 | + |
1518 | +private Q_SLOTS: |
1519 | + void rejectCandidateOwnerForTouch(int id, QQuickItem *candidate); |
1520 | + |
1521 | +private: |
1522 | + class CandidateInfo { |
1523 | + public: |
1524 | + bool undecided; |
1525 | + // TODO: Prune candidates that become null and resolve ownership accordingly. |
1526 | + QPointer<QQuickItem> item; |
1527 | + QPointer<UbuntuGestures::CandidateInactivityTimer> inactivityTimer; |
1528 | + }; |
1529 | + |
1530 | + class TouchInfo { |
1531 | + public: |
1532 | + TouchInfo() : id(-1) {} |
1533 | + TouchInfo(int id); |
1534 | + bool isValid() const { return id >= 0; } |
1535 | + void reset(); |
1536 | + void init(int id); |
1537 | + int id; |
1538 | + bool physicallyEnded; |
1539 | + bool isOwned() const; |
1540 | + bool ended() const; |
1541 | + void notifyCandidatesOfOwnershipResolution(); |
1542 | + |
1543 | + // TODO optimize storage (s/QList/Pool) |
1544 | + QList<CandidateInfo> candidates; |
1545 | + QList<QPointer<QQuickItem>> watchers; |
1546 | + }; |
1547 | + |
1548 | + Pool<TouchInfo>::Iterator findTouchInfo(int id); |
1549 | + |
1550 | + void deliverTouchUpdatesToUndecidedCandidatesAndWatchers(const QTouchEvent *event); |
1551 | + |
1552 | + static void translateTouchPointFromScreenToWindowCoords(QTouchEvent::TouchPoint &touchPoint); |
1553 | + |
1554 | + static void dispatchPointsToItem(const QTouchEvent *event, const QList<int> &touchIds, |
1555 | + QQuickItem *item); |
1556 | + void freeEndedTouchInfos(); |
1557 | + |
1558 | + Pool<TouchInfo> m_touchInfoPool; |
1559 | + |
1560 | + // the singleton instance |
1561 | + static TouchRegistry *m_instance; |
1562 | + |
1563 | + bool m_inDispatchLoop; |
1564 | + |
1565 | + UbuntuGestures::AbstractTimerFactory *m_timerFactory; |
1566 | + |
1567 | + friend class tst_TouchRegistry; |
1568 | + friend class tst_DirectionalDragArea; |
1569 | +}; |
1570 | + |
1571 | +#endif // UNITY_TOUCHREGISTRY_H |
1572 | |
1573 | === added file 'src/app/unity8/libs/UbuntuGestures/UbuntuGesturesGlobal.h' |
1574 | --- src/app/unity8/libs/UbuntuGestures/UbuntuGesturesGlobal.h 1970-01-01 00:00:00 +0000 |
1575 | +++ src/app/unity8/libs/UbuntuGestures/UbuntuGesturesGlobal.h 2015-03-20 16:26:56 +0000 |
1576 | @@ -0,0 +1,23 @@ |
1577 | +/* |
1578 | + * Copyright (C) 2014 Canonical, Ltd. |
1579 | + * |
1580 | + * This program is free software; you can redistribute it and/or modify |
1581 | + * it under the terms of the GNU General Public License as published by |
1582 | + * the Free Software Foundation; version 3. |
1583 | + * |
1584 | + * This program is distributed in the hope that it will be useful, |
1585 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1586 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1587 | + * GNU General Public License for more details. |
1588 | + * |
1589 | + * You should have received a copy of the GNU General Public License |
1590 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1591 | + */ |
1592 | + |
1593 | +#include <QtCore/QtGlobal> |
1594 | + |
1595 | +#if defined(UBUNTUGESTURES_LIBRARY) |
1596 | +# define UBUNTUGESTURES_EXPORT Q_DECL_EXPORT |
1597 | +#else |
1598 | +# define UBUNTUGESTURES_EXPORT Q_DECL_IMPORT |
1599 | +#endif |
1600 | |
1601 | === added file 'src/app/unity8/libs/UbuntuGestures/UnownedTouchEvent.cpp' |
1602 | --- src/app/unity8/libs/UbuntuGestures/UnownedTouchEvent.cpp 1970-01-01 00:00:00 +0000 |
1603 | +++ src/app/unity8/libs/UbuntuGestures/UnownedTouchEvent.cpp 2015-03-20 16:26:56 +0000 |
1604 | @@ -0,0 +1,39 @@ |
1605 | +/* |
1606 | + * Copyright (C) 2014 Canonical, Ltd. |
1607 | + * |
1608 | + * This program is free software; you can redistribute it and/or modify |
1609 | + * it under the terms of the GNU General Public License as published by |
1610 | + * the Free Software Foundation; version 3. |
1611 | + * |
1612 | + * This program is distributed in the hope that it will be useful, |
1613 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1614 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1615 | + * GNU General Public License for more details. |
1616 | + * |
1617 | + * You should have received a copy of the GNU General Public License |
1618 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1619 | + */ |
1620 | + |
1621 | +#include "UnownedTouchEvent.h" |
1622 | + |
1623 | +QEvent::Type UnownedTouchEvent::m_unownedTouchEventType = (QEvent::Type)-1; |
1624 | + |
1625 | +UnownedTouchEvent::UnownedTouchEvent(QTouchEvent *touchEvent) |
1626 | + : QEvent(unownedTouchEventType()) |
1627 | + , m_touchEvent(touchEvent) |
1628 | +{ |
1629 | +} |
1630 | + |
1631 | +QEvent::Type UnownedTouchEvent::unownedTouchEventType() |
1632 | +{ |
1633 | + if (m_unownedTouchEventType == (QEvent::Type)-1) { |
1634 | + m_unownedTouchEventType = (QEvent::Type)registerEventType(); |
1635 | + } |
1636 | + |
1637 | + return m_unownedTouchEventType; |
1638 | +} |
1639 | + |
1640 | +QTouchEvent *UnownedTouchEvent::touchEvent() |
1641 | +{ |
1642 | + return m_touchEvent.data(); |
1643 | +} |
1644 | |
1645 | === added file 'src/app/unity8/libs/UbuntuGestures/UnownedTouchEvent.h' |
1646 | --- src/app/unity8/libs/UbuntuGestures/UnownedTouchEvent.h 1970-01-01 00:00:00 +0000 |
1647 | +++ src/app/unity8/libs/UbuntuGestures/UnownedTouchEvent.h 2015-03-20 16:26:56 +0000 |
1648 | @@ -0,0 +1,45 @@ |
1649 | +/* |
1650 | + * Copyright (C) 2014 Canonical, Ltd. |
1651 | + * |
1652 | + * This program is free software; you can redistribute it and/or modify |
1653 | + * it under the terms of the GNU General Public License as published by |
1654 | + * the Free Software Foundation; version 3. |
1655 | + * |
1656 | + * This program is distributed in the hope that it will be useful, |
1657 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1658 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1659 | + * GNU General Public License for more details. |
1660 | + * |
1661 | + * You should have received a copy of the GNU General Public License |
1662 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1663 | + */ |
1664 | + |
1665 | +#ifndef UBUNTU_UNOWNEDTOUCHEVENT_H |
1666 | +#define UBUNTU_UNOWNEDTOUCHEVENT_H |
1667 | + |
1668 | +#include <QTouchEvent> |
1669 | +#include <QScopedPointer> |
1670 | +#include "UbuntuGesturesGlobal.h" |
1671 | + |
1672 | +/* |
1673 | + A touch event with touch points that do not belong the item receiving it. |
1674 | + |
1675 | + See TouchRegistry::addCandidateOwnerForTouch and TouchRegistry::addTouchWatcher |
1676 | + */ |
1677 | +class UBUNTUGESTURES_EXPORT UnownedTouchEvent : public QEvent |
1678 | +{ |
1679 | +public: |
1680 | + UnownedTouchEvent(QTouchEvent *touchEvent); |
1681 | + static Type unownedTouchEventType(); |
1682 | + |
1683 | + // TODO: It might be cleaner to store the information directly in UnownedTouchEvent |
1684 | + // instead of carrying around a synthesized QTouchEvent. But the latter option |
1685 | + // is very convenient. |
1686 | + QTouchEvent *touchEvent(); |
1687 | + |
1688 | +private: |
1689 | + static Type m_unownedTouchEventType; |
1690 | + QScopedPointer<QTouchEvent> m_touchEvent; |
1691 | +}; |
1692 | + |
1693 | +#endif // UBUNTU_UNOWNEDTOUCHEVENT_H |
1694 | |
1695 | === added directory 'src/app/unity8/plugins' |
1696 | === added file 'src/app/unity8/plugins/CMakeLists.txt' |
1697 | --- src/app/unity8/plugins/CMakeLists.txt 1970-01-01 00:00:00 +0000 |
1698 | +++ src/app/unity8/plugins/CMakeLists.txt 2015-03-20 16:26:56 +0000 |
1699 | @@ -0,0 +1,1 @@ |
1700 | +add_subdirectory(Ubuntu) |
1701 | |
1702 | === added directory 'src/app/unity8/plugins/Ubuntu' |
1703 | === added file 'src/app/unity8/plugins/Ubuntu/CMakeLists.txt' |
1704 | --- src/app/unity8/plugins/Ubuntu/CMakeLists.txt 1970-01-01 00:00:00 +0000 |
1705 | +++ src/app/unity8/plugins/Ubuntu/CMakeLists.txt 2015-03-20 16:26:56 +0000 |
1706 | @@ -0,0 +1,1 @@ |
1707 | +add_subdirectory(Gestures) |
1708 | |
1709 | === added directory 'src/app/unity8/plugins/Ubuntu/Gestures' |
1710 | === added file 'src/app/unity8/plugins/Ubuntu/Gestures/AxisVelocityCalculator.cpp' |
1711 | --- src/app/unity8/plugins/Ubuntu/Gestures/AxisVelocityCalculator.cpp 1970-01-01 00:00:00 +0000 |
1712 | +++ src/app/unity8/plugins/Ubuntu/Gestures/AxisVelocityCalculator.cpp 2015-03-20 16:26:56 +0000 |
1713 | @@ -0,0 +1,149 @@ |
1714 | +/* |
1715 | + * Copyright (C) 2013 - Canonical Ltd. |
1716 | + * |
1717 | + * This program is free software: you can redistribute it and/or modify it |
1718 | + * under the terms of the GNU Lesser General Public License, as |
1719 | + * published by the Free Software Foundation; either version 2.1 or 3.0 |
1720 | + * of the License. |
1721 | + * |
1722 | + * This program is distributed in the hope that it will be useful, but |
1723 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
1724 | + * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR |
1725 | + * PURPOSE. See the applicable version of the GNU Lesser General Public |
1726 | + * License for more details. |
1727 | + * |
1728 | + * You should have received a copy of both the GNU Lesser General Public |
1729 | + * License along with this program. If not, see <http://www.gnu.org/licenses/> |
1730 | + * |
1731 | + * Authored by: Daniel d'Andrada <daniel.dandrada@canonical.com> |
1732 | + */ |
1733 | + |
1734 | +#include "AxisVelocityCalculator.h" |
1735 | +#include <QtCore/QElapsedTimer> |
1736 | + |
1737 | +using namespace UbuntuGestures; |
1738 | + |
1739 | +AxisVelocityCalculator::AxisVelocityCalculator(QObject *parent) |
1740 | + : AxisVelocityCalculator(SharedTimeSource(new RealTimeSource), parent) |
1741 | +{ |
1742 | +} |
1743 | + |
1744 | +AxisVelocityCalculator::AxisVelocityCalculator(const SharedTimeSource &timeSource, |
1745 | + QObject *parent) |
1746 | + : QObject(parent) |
1747 | + , m_timeSource(timeSource) |
1748 | + , m_trackedPosition(0.0) |
1749 | +{ |
1750 | + reset(); |
1751 | +} |
1752 | + |
1753 | +AxisVelocityCalculator::~AxisVelocityCalculator() |
1754 | +{ |
1755 | +} |
1756 | + |
1757 | +qreal AxisVelocityCalculator::trackedPosition() const |
1758 | +{ |
1759 | + return m_trackedPosition; |
1760 | +} |
1761 | + |
1762 | +void AxisVelocityCalculator::setTrackedPosition(qreal newPosition) |
1763 | +{ |
1764 | + processMovement(newPosition - m_trackedPosition); |
1765 | + |
1766 | + if (newPosition != m_trackedPosition) { |
1767 | + m_trackedPosition = newPosition; |
1768 | + Q_EMIT trackedPositionChanged(newPosition); |
1769 | + } |
1770 | +} |
1771 | + |
1772 | +void AxisVelocityCalculator::updateIdleTime() |
1773 | +{ |
1774 | + processMovement(0); |
1775 | +} |
1776 | + |
1777 | +void AxisVelocityCalculator::processMovement(qreal movement) |
1778 | +{ |
1779 | + if (m_samplesRead == -1) { |
1780 | + m_samplesRead = m_samplesWrite; |
1781 | + } else if (m_samplesRead == m_samplesWrite) { |
1782 | + /* the oldest value is going to be overwritten. |
1783 | + so now the oldest will be the next one. */ |
1784 | + m_samplesRead = (m_samplesRead + 1) % MAX_SAMPLES; |
1785 | + } |
1786 | + |
1787 | + m_samples[m_samplesWrite].mov = movement; |
1788 | + m_samples[m_samplesWrite].time = m_timeSource->msecsSinceReference(); |
1789 | + m_samplesWrite = (m_samplesWrite + 1) % MAX_SAMPLES; |
1790 | +} |
1791 | + |
1792 | +qreal AxisVelocityCalculator::calculate() |
1793 | +{ |
1794 | + if (numSamples() < MIN_SAMPLES_NEEDED) { |
1795 | + return 0.0; |
1796 | + } |
1797 | + updateIdleTime(); // consider the time elapsed since the last update and now |
1798 | + |
1799 | + int lastIndex; |
1800 | + if (m_samplesWrite == 0) { |
1801 | + lastIndex = MAX_SAMPLES - 1; |
1802 | + } else { |
1803 | + lastIndex = m_samplesWrite - 1; |
1804 | + } |
1805 | + |
1806 | + qint64 currTime = m_samples[lastIndex].time; |
1807 | + |
1808 | + qreal totalTime = 0; |
1809 | + qreal totalDistance = 0; |
1810 | + |
1811 | + int sampleIndex = (m_samplesRead + 1) % MAX_SAMPLES; |
1812 | + qint64 previousTime = m_samples[m_samplesRead].time; |
1813 | + while (sampleIndex != m_samplesWrite) { |
1814 | + // Skip this sample if it's too old |
1815 | + if (currTime - m_samples[sampleIndex].time <= AGE_OLDEST_SAMPLE) { |
1816 | + int deltaTime = m_samples[sampleIndex].time - previousTime; |
1817 | + totalDistance += m_samples[sampleIndex].mov; |
1818 | + totalTime += deltaTime; |
1819 | + } |
1820 | + |
1821 | + previousTime = m_samples[sampleIndex].time; |
1822 | + sampleIndex = (sampleIndex + 1) % MAX_SAMPLES; |
1823 | + } |
1824 | + |
1825 | + return totalDistance / totalTime; |
1826 | +} |
1827 | + |
1828 | +void AxisVelocityCalculator::reset() |
1829 | +{ |
1830 | + m_samplesRead = -1; |
1831 | + m_samplesWrite = 0; |
1832 | +} |
1833 | + |
1834 | +int AxisVelocityCalculator::numSamples() const |
1835 | +{ |
1836 | + if (m_samplesRead == -1) { |
1837 | + return 0; |
1838 | + } else { |
1839 | + if (m_samplesWrite == 0) { |
1840 | + /* consider only what's to the right of m_samplesRead (including himself) */ |
1841 | + return MAX_SAMPLES - m_samplesRead; |
1842 | + } else if (m_samplesWrite == m_samplesRead) { |
1843 | + return MAX_SAMPLES; /* buffer is full */ |
1844 | + } else if (m_samplesWrite < m_samplesRead) { |
1845 | + return (MAX_SAMPLES - m_samplesRead) + m_samplesWrite; |
1846 | + } else { |
1847 | + return m_samplesWrite - m_samplesRead; |
1848 | + } |
1849 | + } |
1850 | +} |
1851 | + |
1852 | +void AxisVelocityCalculator::setTimeSource(const SharedTimeSource &timeSource) |
1853 | +{ |
1854 | + m_timeSource = timeSource; |
1855 | + |
1856 | + if (numSamples() > 0) { |
1857 | + qWarning("AxisVelocityCalculator: changing time source while there are samples present."); |
1858 | + // Any existent samples are based on the old time source and are, therefore, incompatible |
1859 | + // with this new one. |
1860 | + reset(); |
1861 | + } |
1862 | +} |
1863 | |
1864 | === added file 'src/app/unity8/plugins/Ubuntu/Gestures/AxisVelocityCalculator.h' |
1865 | --- src/app/unity8/plugins/Ubuntu/Gestures/AxisVelocityCalculator.h 1970-01-01 00:00:00 +0000 |
1866 | +++ src/app/unity8/plugins/Ubuntu/Gestures/AxisVelocityCalculator.h 2015-03-20 16:26:56 +0000 |
1867 | @@ -0,0 +1,146 @@ |
1868 | +/* |
1869 | + * Copyright (C) 2013 - Canonical Ltd. |
1870 | + * |
1871 | + * This program is free software: you can redistribute it and/or modify it |
1872 | + * under the terms of the GNU Lesser General Public License, as |
1873 | + * published by the Free Software Foundation; either version 2.1 or 3.0 |
1874 | + * of the License. |
1875 | + * |
1876 | + * This program is distributed in the hope that it will be useful, but |
1877 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
1878 | + * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR |
1879 | + * PURPOSE. See the applicable version of the GNU Lesser General Public |
1880 | + * License for more details. |
1881 | + * |
1882 | + * You should have received a copy of both the GNU Lesser General Public |
1883 | + * License along with this program. If not, see <http://www.gnu.org/licenses/> |
1884 | + * |
1885 | + * Authored by: Daniel d'Andrada <daniel.dandrada@canonical.com> |
1886 | + */ |
1887 | + |
1888 | +#ifndef VELOCITY_CALCULATOR_H |
1889 | +#define VELOCITY_CALCULATOR_H |
1890 | + |
1891 | +#include "UbuntuGesturesQmlGlobal.h" |
1892 | +#include <stdint.h> |
1893 | +#include <QtCore/QObject> |
1894 | +#include "TimeSource.h" |
1895 | + |
1896 | +/* |
1897 | + Estimates the current velocity of a finger based on recent movement along an axis |
1898 | + |
1899 | + Taking an estimate from a reasonable number of samples, instead of only |
1900 | + from its last movement, removes wild variations in velocity caused |
1901 | + by the jitter normally present in input from a touchscreen. |
1902 | + |
1903 | + Usage example: |
1904 | + |
1905 | + AxisVelocityCalculator { |
1906 | + id: velocityCalculator |
1907 | + trackedPosition: myMouseArea.mouseX |
1908 | + } |
1909 | + |
1910 | + MouseArea { |
1911 | + id: myMouseArea |
1912 | + |
1913 | + onReleased: { |
1914 | + console.log("Drag velocity along the X axis before release was: " |
1915 | + + velocityCalculator.calculate()) |
1916 | + } |
1917 | + } |
1918 | + */ |
1919 | +class UBUNTUGESTURESQML_EXPORT AxisVelocityCalculator : public QObject |
1920 | +{ |
1921 | + Q_OBJECT |
1922 | + |
1923 | + /* |
1924 | + Position whose movement will be tracked to calculate its velocity |
1925 | + */ |
1926 | + Q_PROPERTY(qreal trackedPosition READ trackedPosition WRITE setTrackedPosition |
1927 | + NOTIFY trackedPositionChanged) |
1928 | +public: |
1929 | + |
1930 | + /* |
1931 | + Regular, simple, constructor |
1932 | + */ |
1933 | + AxisVelocityCalculator(QObject *parent = 0); |
1934 | + |
1935 | + /* |
1936 | + Constructor that takes a TimeSource |
1937 | + */ |
1938 | + AxisVelocityCalculator(const UbuntuGestures::SharedTimeSource &timeSource, QObject *parent = 0); |
1939 | + |
1940 | + virtual ~AxisVelocityCalculator(); |
1941 | + |
1942 | + qreal trackedPosition() const; |
1943 | + void setTrackedPosition(qreal value); |
1944 | + |
1945 | + /* |
1946 | + Calculates the finger velocity, in axis units/millisecond |
1947 | + */ |
1948 | + Q_INVOKABLE qreal calculate(); |
1949 | + |
1950 | + /* |
1951 | + Removes all stored movements from previous calls to setTrackedPosition() |
1952 | + */ |
1953 | + Q_INVOKABLE void reset(); |
1954 | + |
1955 | + int numSamples() const; |
1956 | + |
1957 | + /* |
1958 | + Replaces the TimeSource with the given one. Useful for testing purposes. |
1959 | + */ |
1960 | + void setTimeSource(const UbuntuGestures::SharedTimeSource &timeSource); |
1961 | + |
1962 | + /* |
1963 | + The minimum amount of samples needed for a velocity calculation. |
1964 | + */ |
1965 | + static const int MIN_SAMPLES_NEEDED = 2; |
1966 | + |
1967 | + /* |
1968 | + Maximum number of movement samples stored |
1969 | + */ |
1970 | + static const int MAX_SAMPLES = 50; |
1971 | + |
1972 | + /* |
1973 | + Age of the oldest sample considered in the velocity calculations, in |
1974 | + milliseconds, compared to the most recent one. |
1975 | + */ |
1976 | + static const int AGE_OLDEST_SAMPLE = 100; |
1977 | + |
1978 | +Q_SIGNALS: |
1979 | + void trackedPositionChanged(qreal value); |
1980 | + |
1981 | +private: |
1982 | + |
1983 | + /* |
1984 | + Inform that trackedPosition remained motionless since the time it was |
1985 | + last changed. |
1986 | + |
1987 | + It's the same as calling setTrackedPosition(trackedPosition()) |
1988 | + */ |
1989 | + void updateIdleTime(); |
1990 | + |
1991 | + /* |
1992 | + How much the finger has moved since processMovement() was last called. |
1993 | + */ |
1994 | + void processMovement(qreal movement); |
1995 | + |
1996 | + class Sample |
1997 | + { |
1998 | + public: |
1999 | + qreal mov; /* movement distance since last sample */ |
2000 | + qint64 time; /* time, in milliseconds */ |
2001 | + }; |
2002 | + |
2003 | + /* a circular buffer of samples */ |
2004 | + Sample m_samples[MAX_SAMPLES]; |
2005 | + int m_samplesRead; /* index of the oldest sample available. -1 if buffer is empty */ |
2006 | + int m_samplesWrite; /* index where the next sample will be written */ |
2007 | + |
2008 | + UbuntuGestures::SharedTimeSource m_timeSource; |
2009 | + |
2010 | + qreal m_trackedPosition; |
2011 | +}; |
2012 | + |
2013 | +#endif // VELOCITY_CALCULATOR_H |
2014 | |
2015 | === added file 'src/app/unity8/plugins/Ubuntu/Gestures/CMakeLists.txt' |
2016 | --- src/app/unity8/plugins/Ubuntu/Gestures/CMakeLists.txt 1970-01-01 00:00:00 +0000 |
2017 | +++ src/app/unity8/plugins/Ubuntu/Gestures/CMakeLists.txt 2015-03-20 16:26:56 +0000 |
2018 | @@ -0,0 +1,40 @@ |
2019 | +# in order to include Qt's private headers |
2020 | +remove_definitions(-DQT_NO_KEYWORDS) |
2021 | + |
2022 | +set(UbuntuGesturesQml_SOURCES |
2023 | +# plugin.cpp |
2024 | + AxisVelocityCalculator.cpp |
2025 | + Direction.cpp |
2026 | + DirectionalDragArea.cpp |
2027 | + PressedOutsideNotifier.cpp |
2028 | + TimeSource.cpp |
2029 | + TouchDispatcher.cpp |
2030 | + TouchGate.cpp |
2031 | +) |
2032 | + |
2033 | +add_definitions(-DUBUNTUGESTURESQML_LIBRARY) |
2034 | + |
2035 | +add_library(UbuntuGesturesQml STATIC ${UbuntuGesturesQml_SOURCES}) |
2036 | +target_link_libraries(UbuntuGesturesQml UbuntuGestures) |
2037 | + |
2038 | +qt5_use_modules(UbuntuGesturesQml Core Quick) |
2039 | + |
2040 | +# So that Foo.cpp can #include "Foo.moc" |
2041 | +include_directories(${CMAKE_CURRENT_BINARY_DIR}) |
2042 | + |
2043 | +include_directories(${unity8_SOURCE_DIR}/libs/UbuntuGestures) |
2044 | + |
2045 | +# There's no cmake var for v8 include path :-/ so create one |
2046 | +LIST(GET Qt5Core_INCLUDE_DIRS 0 QtCoreDir0) |
2047 | +SET(Qt5V8_PRIVATE_INCLUDE_DIR ${QtCoreDir0}/QtV8/${Qt5Core_VERSION_STRING}/QtV8) |
2048 | + |
2049 | +# DANGER! DANGER! Using Qt's private API! |
2050 | +include_directories( |
2051 | + ${Qt5Qml_PRIVATE_INCLUDE_DIRS} |
2052 | + ${Qt5Quick_INCLUDE_DIRS} |
2053 | + ${Qt5Quick_PRIVATE_INCLUDE_DIRS} |
2054 | + ${Qt5V8_PRIVATE_INCLUDE_DIR} |
2055 | +) |
2056 | + |
2057 | +#add_unity8_plugin(Ubuntu.Gestures 0.1 Ubuntu/Gestures TARGETS UbuntuGesturesQml) |
2058 | +# TODO |
2059 | |
2060 | === added file 'src/app/unity8/plugins/Ubuntu/Gestures/Damper.h' |
2061 | --- src/app/unity8/plugins/Ubuntu/Gestures/Damper.h 1970-01-01 00:00:00 +0000 |
2062 | +++ src/app/unity8/plugins/Ubuntu/Gestures/Damper.h 2015-03-20 16:26:56 +0000 |
2063 | @@ -0,0 +1,87 @@ |
2064 | +/* |
2065 | + * Copyright (C) 2013 Canonical, Ltd. |
2066 | + * |
2067 | + * This program is free software; you can redistribute it and/or modify |
2068 | + * it under the terms of the GNU General Public License as published by |
2069 | + * the Free Software Foundation; version 3. |
2070 | + * |
2071 | + * This program is distributed in the hope that it will be useful, |
2072 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
2073 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2074 | + * GNU General Public License for more details. |
2075 | + * |
2076 | + * You should have received a copy of the GNU General Public License |
2077 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
2078 | + */ |
2079 | + |
2080 | +#ifndef UBUNTU_GESTURES_DAMPER_H |
2081 | +#define UBUNTU_GESTURES_DAMPER_H |
2082 | + |
2083 | +#include <QtCore/QPointF> |
2084 | + |
2085 | +/* |
2086 | + Decreases the oscillations of a value along an axis. |
2087 | + */ |
2088 | +template <class Type> class Damper { |
2089 | +public: |
2090 | + Damper() : m_value(0), m_maxDelta(0) { } |
2091 | + |
2092 | + // Maximum delta between the raw value and its dampened counterpart. |
2093 | + void setMaxDelta(Type maxDelta) { |
2094 | + if (maxDelta < 0) qFatal("Damper::maxDelta must be a positive number."); |
2095 | + m_maxDelta = maxDelta; |
2096 | + } |
2097 | + Type maxDelta() const { return m_maxDelta; } |
2098 | + |
2099 | + void reset(Type value) { |
2100 | + m_value = value; |
2101 | + } |
2102 | + |
2103 | + Type update(Type value) { |
2104 | + Type delta = value - m_value; |
2105 | + if (delta > 0 && delta > m_maxDelta) { |
2106 | + m_value += delta - m_maxDelta; |
2107 | + } else if (delta < 0 && delta < -m_maxDelta) { |
2108 | + m_value += delta + m_maxDelta; |
2109 | + } |
2110 | + |
2111 | + return m_value; |
2112 | + } |
2113 | + |
2114 | + Type value() const { return m_value; } |
2115 | + |
2116 | +private: |
2117 | + Type m_value; |
2118 | + Type m_maxDelta; |
2119 | +}; |
2120 | + |
2121 | +/* |
2122 | + A point that has its movement dampened. |
2123 | + */ |
2124 | +class DampedPointF { |
2125 | +public: |
2126 | + void setMaxDelta(qreal maxDelta) { |
2127 | + m_x.setMaxDelta(maxDelta); |
2128 | + m_y.setMaxDelta(maxDelta); |
2129 | + } |
2130 | + |
2131 | + qreal maxDelta() const { return m_x.maxDelta(); } |
2132 | + |
2133 | + void reset(const QPointF &point) { |
2134 | + m_x.reset(point.x()); |
2135 | + m_y.reset(point.y()); |
2136 | + } |
2137 | + |
2138 | + void update(const QPointF &point) { |
2139 | + m_x.update(point.x()); |
2140 | + m_y.update(point.y()); |
2141 | + } |
2142 | + |
2143 | + qreal x() const { return m_x.value(); } |
2144 | + qreal y() const { return m_y.value(); } |
2145 | +private: |
2146 | + Damper<qreal> m_x; |
2147 | + Damper<qreal> m_y; |
2148 | +}; |
2149 | + |
2150 | +#endif // UBUNTU_GESTURES_DAMPER_H |
2151 | |
2152 | === added file 'src/app/unity8/plugins/Ubuntu/Gestures/Direction.cpp' |
2153 | --- src/app/unity8/plugins/Ubuntu/Gestures/Direction.cpp 1970-01-01 00:00:00 +0000 |
2154 | +++ src/app/unity8/plugins/Ubuntu/Gestures/Direction.cpp 2015-03-20 16:26:56 +0000 |
2155 | @@ -0,0 +1,36 @@ |
2156 | +/* |
2157 | + * Copyright (C) 2013 Canonical, Ltd. |
2158 | + * |
2159 | + * This program is free software; you can redistribute it and/or modify |
2160 | + * it under the terms of the GNU General Public License as published by |
2161 | + * the Free Software Foundation; version 3. |
2162 | + * |
2163 | + * This program is distributed in the hope that it will be useful, |
2164 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
2165 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2166 | + * GNU General Public License for more details. |
2167 | + * |
2168 | + * You should have received a copy of the GNU General Public License |
2169 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
2170 | + */ |
2171 | + |
2172 | +#include "Direction.h" |
2173 | + |
2174 | +bool Direction::isHorizontal(Direction::Type type) |
2175 | +{ |
2176 | + return type == Direction::Leftwards |
2177 | + || type == Direction::Rightwards |
2178 | + || type == Direction::Horizontal; |
2179 | +} |
2180 | + |
2181 | +bool Direction::isVertical(Direction::Type type) |
2182 | +{ |
2183 | + return type == Direction::Upwards || type == Direction::Downwards; |
2184 | +} |
2185 | + |
2186 | +bool Direction::isPositive(Direction::Type type) |
2187 | +{ |
2188 | + return type == Rightwards |
2189 | + || type == Downwards |
2190 | + || type == Horizontal; |
2191 | +} |
2192 | |
2193 | === added file 'src/app/unity8/plugins/Ubuntu/Gestures/Direction.h' |
2194 | --- src/app/unity8/plugins/Ubuntu/Gestures/Direction.h 1970-01-01 00:00:00 +0000 |
2195 | +++ src/app/unity8/plugins/Ubuntu/Gestures/Direction.h 2015-03-20 16:26:56 +0000 |
2196 | @@ -0,0 +1,45 @@ |
2197 | +/* |
2198 | + * Copyright (C) 2013 Canonical, Ltd. |
2199 | + * |
2200 | + * This program is free software; you can redistribute it and/or modify |
2201 | + * it under the terms of the GNU General Public License as published by |
2202 | + * the Free Software Foundation; version 3. |
2203 | + * |
2204 | + * This program is distributed in the hope that it will be useful, |
2205 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
2206 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2207 | + * GNU General Public License for more details. |
2208 | + * |
2209 | + * You should have received a copy of the GNU General Public License |
2210 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
2211 | + */ |
2212 | + |
2213 | +#ifndef DIRECTION_H |
2214 | +#define DIRECTION_H |
2215 | + |
2216 | +#include "UbuntuGesturesQmlGlobal.h" |
2217 | +#include <QObject> |
2218 | + |
2219 | +/* |
2220 | + A Direction enum wrapper so that we can do things like "direction: Direction.Righwards" |
2221 | + from QML. |
2222 | + */ |
2223 | +class UBUNTUGESTURESQML_EXPORT Direction : public QObject { |
2224 | + Q_OBJECT |
2225 | + Q_ENUMS(Type) |
2226 | + |
2227 | +public: |
2228 | + enum Type { |
2229 | + Rightwards, // Along the positive direction of the X axis |
2230 | + Leftwards, // Along the negative direction of the X axis |
2231 | + Downwards, // Along the positive direction of the Y axis |
2232 | + Upwards, // Along the negative direction of the Y axis |
2233 | + Horizontal // Along the X axis, in any direction |
2234 | + }; |
2235 | + |
2236 | + Q_INVOKABLE static bool isHorizontal(Direction::Type type); |
2237 | + Q_INVOKABLE static bool isVertical(Direction::Type type); |
2238 | + Q_INVOKABLE static bool isPositive(Direction::Type type); |
2239 | +}; |
2240 | + |
2241 | +#endif // DIRECTION_H |
2242 | |
2243 | === added file 'src/app/unity8/plugins/Ubuntu/Gestures/DirectionalDragArea.cpp' |
2244 | --- src/app/unity8/plugins/Ubuntu/Gestures/DirectionalDragArea.cpp 1970-01-01 00:00:00 +0000 |
2245 | +++ src/app/unity8/plugins/Ubuntu/Gestures/DirectionalDragArea.cpp 2015-03-20 16:26:56 +0000 |
2246 | @@ -0,0 +1,886 @@ |
2247 | +/* |
2248 | + * Copyright (C) 2013-2014 Canonical, Ltd. |
2249 | + * |
2250 | + * This program is free software; you can redistribute it and/or modify |
2251 | + * it under the terms of the GNU General Public License as published by |
2252 | + * the Free Software Foundation; version 3. |
2253 | + * |
2254 | + * This program is distributed in the hope that it will be useful, |
2255 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
2256 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2257 | + * GNU General Public License for more details. |
2258 | + * |
2259 | + * You should have received a copy of the GNU General Public License |
2260 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
2261 | + */ |
2262 | + |
2263 | +#define ACTIVETOUCHESINFO_DEBUG 0 |
2264 | +#define DIRECTIONALDRAGAREA_DEBUG 0 |
2265 | + |
2266 | +#include "DirectionalDragArea.h" |
2267 | + |
2268 | +#include <QQuickWindow> |
2269 | +#include <QtCore/qmath.h> |
2270 | +#include <QDebug> |
2271 | + |
2272 | +#pragma GCC diagnostic push |
2273 | +#pragma GCC diagnostic ignored "-pedantic" |
2274 | +#include <private/qquickwindow_p.h> |
2275 | +#pragma GCC diagnostic pop |
2276 | + |
2277 | +// local |
2278 | +#include "TouchOwnershipEvent.h" |
2279 | +#include "TouchRegistry.h" |
2280 | +#include "UnownedTouchEvent.h" |
2281 | + |
2282 | +using namespace UbuntuGestures; |
2283 | + |
2284 | +#if DIRECTIONALDRAGAREA_DEBUG |
2285 | +#define ddaDebug(params) qDebug().nospace() << "[DDA(" << qPrintable(objectName()) << ")] " << params |
2286 | +#include "DebugHelpers.h" |
2287 | + |
2288 | +namespace { |
2289 | +const char *statusToString(DirectionalDragArea::Status status) |
2290 | +{ |
2291 | + if (status == DirectionalDragArea::WaitingForTouch) { |
2292 | + return "WaitingForTouch"; |
2293 | + } else if (status == DirectionalDragArea::Undecided) { |
2294 | + return "Undecided"; |
2295 | + } else { |
2296 | + return "Recognized"; |
2297 | + } |
2298 | +} |
2299 | + |
2300 | +} // namespace { |
2301 | +#else // DIRECTIONALDRAGAREA_DEBUG |
2302 | +#define ddaDebug(params) ((void)0) |
2303 | +#endif // DIRECTIONALDRAGAREA_DEBUG |
2304 | + |
2305 | + |
2306 | +DirectionalDragArea::DirectionalDragArea(QQuickItem *parent) |
2307 | + : QQuickItem(parent) |
2308 | + , m_status(WaitingForTouch) |
2309 | + , m_sceneDistance(0) |
2310 | + , m_touchId(-1) |
2311 | + , m_direction(Direction::Rightwards) |
2312 | + , m_wideningAngle(0) |
2313 | + , m_wideningFactor(0) |
2314 | + , m_distanceThreshold(0) |
2315 | + , m_distanceThresholdSquared(0.) |
2316 | + , m_minSpeed(0) |
2317 | + , m_maxSilenceTime(200) |
2318 | + , m_silenceTime(0) |
2319 | + , m_compositionTime(60) |
2320 | + , m_numSamplesOnLastSpeedCheck(0) |
2321 | + , m_recognitionTimer(0) |
2322 | + , m_velocityCalculator(0) |
2323 | + , m_timeSource(new RealTimeSource) |
2324 | + , m_activeTouches(m_timeSource) |
2325 | +{ |
2326 | + setRecognitionTimer(new Timer(this)); |
2327 | + m_recognitionTimer->setInterval(60); |
2328 | + m_recognitionTimer->setSingleShot(false); |
2329 | + |
2330 | + m_velocityCalculator = new AxisVelocityCalculator(this); |
2331 | + |
2332 | + connect(this, &QQuickItem::enabledChanged, this, &DirectionalDragArea::giveUpIfDisabledOrInvisible); |
2333 | + connect(this, &QQuickItem::visibleChanged, this, &DirectionalDragArea::giveUpIfDisabledOrInvisible); |
2334 | +} |
2335 | + |
2336 | +Direction::Type DirectionalDragArea::direction() const |
2337 | +{ |
2338 | + return m_direction; |
2339 | +} |
2340 | + |
2341 | +void DirectionalDragArea::setDirection(Direction::Type direction) |
2342 | +{ |
2343 | + if (direction != m_direction) { |
2344 | + m_direction = direction; |
2345 | + Q_EMIT directionChanged(m_direction); |
2346 | + } |
2347 | +} |
2348 | + |
2349 | +void DirectionalDragArea::setMaxDeviation(qreal value) |
2350 | +{ |
2351 | + if (m_dampedScenePos.maxDelta() != value) { |
2352 | + m_dampedScenePos.setMaxDelta(value); |
2353 | + Q_EMIT maxDeviationChanged(value); |
2354 | + } |
2355 | +} |
2356 | + |
2357 | +qreal DirectionalDragArea::wideningAngle() const |
2358 | +{ |
2359 | + return m_wideningAngle; |
2360 | +} |
2361 | + |
2362 | +void DirectionalDragArea::setWideningAngle(qreal angle) |
2363 | +{ |
2364 | + if (angle == m_wideningAngle) |
2365 | + return; |
2366 | + |
2367 | + m_wideningAngle = angle; |
2368 | + |
2369 | + // wideningFactor = pow(cosine(angle), 2) |
2370 | + { |
2371 | + qreal angleRadians = angle * M_PI / 180.0; |
2372 | + m_wideningFactor = qCos(angleRadians); |
2373 | + m_wideningFactor = m_wideningFactor * m_wideningFactor; |
2374 | + } |
2375 | + |
2376 | + Q_EMIT wideningAngleChanged(angle); |
2377 | +} |
2378 | + |
2379 | +void DirectionalDragArea::setDistanceThreshold(qreal value) |
2380 | +{ |
2381 | + if (m_distanceThreshold != value) { |
2382 | + m_distanceThreshold = value; |
2383 | + m_distanceThresholdSquared = m_distanceThreshold * m_distanceThreshold; |
2384 | + Q_EMIT distanceThresholdChanged(value); |
2385 | + } |
2386 | +} |
2387 | + |
2388 | +void DirectionalDragArea::setMinSpeed(qreal value) |
2389 | +{ |
2390 | + if (m_minSpeed != value) { |
2391 | + m_minSpeed = value; |
2392 | + Q_EMIT minSpeedChanged(value); |
2393 | + } |
2394 | +} |
2395 | + |
2396 | +void DirectionalDragArea::setMaxSilenceTime(int value) |
2397 | +{ |
2398 | + if (m_maxSilenceTime != value) { |
2399 | + m_maxSilenceTime = value; |
2400 | + Q_EMIT maxSilenceTimeChanged(value); |
2401 | + } |
2402 | +} |
2403 | + |
2404 | +void DirectionalDragArea::setCompositionTime(int value) |
2405 | +{ |
2406 | + if (m_compositionTime != value) { |
2407 | + m_compositionTime = value; |
2408 | + Q_EMIT compositionTimeChanged(value); |
2409 | + } |
2410 | +} |
2411 | + |
2412 | +void DirectionalDragArea::setRecognitionTimer(UbuntuGestures::AbstractTimer *timer) |
2413 | +{ |
2414 | + int interval = 0; |
2415 | + bool timerWasRunning = false; |
2416 | + bool wasSingleShot = false; |
2417 | + |
2418 | + // can be null when called from the constructor |
2419 | + if (m_recognitionTimer) { |
2420 | + interval = m_recognitionTimer->interval(); |
2421 | + timerWasRunning = m_recognitionTimer->isRunning(); |
2422 | + if (m_recognitionTimer->parent() == this) { |
2423 | + delete m_recognitionTimer; |
2424 | + } |
2425 | + } |
2426 | + |
2427 | + m_recognitionTimer = timer; |
2428 | + timer->setInterval(interval); |
2429 | + timer->setSingleShot(wasSingleShot); |
2430 | + connect(timer, &UbuntuGestures::AbstractTimer::timeout, |
2431 | + this, &DirectionalDragArea::checkSpeed); |
2432 | + if (timerWasRunning) { |
2433 | + m_recognitionTimer->start(); |
2434 | + } |
2435 | +} |
2436 | + |
2437 | +void DirectionalDragArea::setTimeSource(const SharedTimeSource &timeSource) |
2438 | +{ |
2439 | + m_timeSource = timeSource; |
2440 | + m_velocityCalculator->setTimeSource(timeSource); |
2441 | + m_activeTouches.m_timeSource = timeSource; |
2442 | +} |
2443 | + |
2444 | +qreal DirectionalDragArea::distance() const |
2445 | +{ |
2446 | + if (Direction::isHorizontal(m_direction)) { |
2447 | + return m_previousPos.x() - m_startPos.x(); |
2448 | + } else { |
2449 | + return m_previousPos.y() - m_startPos.y(); |
2450 | + } |
2451 | +} |
2452 | + |
2453 | +void DirectionalDragArea::updateSceneDistance() |
2454 | +{ |
2455 | + QPointF totalMovement = m_previousScenePos - m_startScenePos; |
2456 | + m_sceneDistance = projectOntoDirectionVector(totalMovement); |
2457 | +} |
2458 | + |
2459 | +qreal DirectionalDragArea::sceneDistance() const |
2460 | +{ |
2461 | + return m_sceneDistance; |
2462 | +} |
2463 | + |
2464 | +qreal DirectionalDragArea::touchX() const |
2465 | +{ |
2466 | + return m_previousPos.x(); |
2467 | +} |
2468 | + |
2469 | +qreal DirectionalDragArea::touchY() const |
2470 | +{ |
2471 | + return m_previousPos.y(); |
2472 | +} |
2473 | + |
2474 | +qreal DirectionalDragArea::touchSceneX() const |
2475 | +{ |
2476 | + return m_previousScenePos.x(); |
2477 | +} |
2478 | + |
2479 | +qreal DirectionalDragArea::touchSceneY() const |
2480 | +{ |
2481 | + return m_previousScenePos.y(); |
2482 | +} |
2483 | + |
2484 | +bool DirectionalDragArea::event(QEvent *event) |
2485 | +{ |
2486 | + if (event->type() == TouchOwnershipEvent::touchOwnershipEventType()) { |
2487 | + touchOwnershipEvent(static_cast<TouchOwnershipEvent *>(event)); |
2488 | + return true; |
2489 | + } else if (event->type() == UnownedTouchEvent::unownedTouchEventType()) { |
2490 | + unownedTouchEvent(static_cast<UnownedTouchEvent *>(event)); |
2491 | + return true; |
2492 | + } else { |
2493 | + return QQuickItem::event(event); |
2494 | + } |
2495 | +} |
2496 | + |
2497 | +void DirectionalDragArea::touchOwnershipEvent(TouchOwnershipEvent *event) |
2498 | +{ |
2499 | + if (event->gained()) { |
2500 | + QVector<int> ids; |
2501 | + ids.append(event->touchId()); |
2502 | + ddaDebug("grabbing touch"); |
2503 | + grabTouchPoints(ids); |
2504 | + |
2505 | + // Work around for Qt bug. If we grab a touch that is being used for mouse pointer |
2506 | + // emulation it will cause the emulation logic to go nuts. |
2507 | + // Thus we have to also grab the mouse in this case. |
2508 | + // |
2509 | + // The fix for this bug has landed in Qt 5.4 (https://codereview.qt-project.org/96887) |
2510 | + // TODO: Remove this workaround once we start using Qt 5.4 |
2511 | + if (window()) { |
2512 | + QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(window()); |
2513 | + if (windowPrivate->touchMouseId == event->touchId() && window()->mouseGrabberItem()) { |
2514 | + ddaDebug("removing mouse grabber"); |
2515 | + window()->mouseGrabberItem()->ungrabMouse(); |
2516 | + } |
2517 | + } |
2518 | + } else { |
2519 | + // We still wanna know when it ends for keeping the composition time window up-to-date |
2520 | + TouchRegistry::instance()->addTouchWatcher(m_touchId, this); |
2521 | + |
2522 | + setStatus(WaitingForTouch); |
2523 | + } |
2524 | +} |
2525 | + |
2526 | +void DirectionalDragArea::unownedTouchEvent(UnownedTouchEvent *unownedTouchEvent) |
2527 | +{ |
2528 | + QTouchEvent *event = unownedTouchEvent->touchEvent(); |
2529 | + |
2530 | + Q_ASSERT(!event->touchPointStates().testFlag(Qt::TouchPointPressed)); |
2531 | + |
2532 | + ddaDebug("Unowned " << m_timeSource->msecsSinceReference() << " " << qPrintable(touchEventToString(event))); |
2533 | + |
2534 | + switch (m_status) { |
2535 | + case WaitingForTouch: |
2536 | + // do nothing |
2537 | + break; |
2538 | + case Undecided: |
2539 | + Q_ASSERT(isEnabled() && isVisible()); |
2540 | + unownedTouchEvent_undecided(unownedTouchEvent); |
2541 | + break; |
2542 | + default: // Recognized: |
2543 | + // do nothing |
2544 | + break; |
2545 | + } |
2546 | + |
2547 | + m_activeTouches.update(event); |
2548 | +} |
2549 | + |
2550 | +void DirectionalDragArea::unownedTouchEvent_undecided(UnownedTouchEvent *unownedTouchEvent) |
2551 | +{ |
2552 | + const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(unownedTouchEvent->touchEvent()); |
2553 | + if (!touchPoint) { |
2554 | + qCritical() << "DirectionalDragArea[status=Undecided]: touch " << m_touchId |
2555 | + << "missing from UnownedTouchEvent without first reaching state Qt::TouchPointReleased. " |
2556 | + "Considering it as released."; |
2557 | + |
2558 | + TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this); |
2559 | + setStatus(WaitingForTouch); |
2560 | + return; |
2561 | + } |
2562 | + |
2563 | + const QPointF &touchScenePos = touchPoint->scenePos(); |
2564 | + |
2565 | + if (touchPoint->state() == Qt::TouchPointReleased) { |
2566 | + // touch has ended before recognition concluded |
2567 | + ddaDebug("Touch has ended before recognition concluded"); |
2568 | + TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this); |
2569 | + emitSignalIfTapped(); |
2570 | + setStatus(WaitingForTouch); |
2571 | + return; |
2572 | + } |
2573 | + |
2574 | + m_previousDampedScenePos.setX(m_dampedScenePos.x()); |
2575 | + m_previousDampedScenePos.setY(m_dampedScenePos.y()); |
2576 | + m_dampedScenePos.update(touchScenePos); |
2577 | + updateVelocityCalculator(touchScenePos); |
2578 | + |
2579 | + if (!pointInsideAllowedArea()) { |
2580 | + ddaDebug("Rejecting gesture because touch point is outside allowed area."); |
2581 | + TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this); |
2582 | + // We still wanna know when it ends for keeping the composition time window up-to-date |
2583 | + TouchRegistry::instance()->addTouchWatcher(m_touchId, this); |
2584 | + setStatus(WaitingForTouch); |
2585 | + return; |
2586 | + } |
2587 | + |
2588 | + if (!movingInRightDirection()) { |
2589 | + ddaDebug("Rejecting gesture because touch point is moving in the wrong direction."); |
2590 | + TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this); |
2591 | + // We still wanna know when it ends for keeping the composition time window up-to-date |
2592 | + TouchRegistry::instance()->addTouchWatcher(m_touchId, this); |
2593 | + setStatus(WaitingForTouch); |
2594 | + return; |
2595 | + } |
2596 | + |
2597 | + setPreviousPos(touchPoint->pos()); |
2598 | + setPreviousScenePos(touchScenePos); |
2599 | + |
2600 | + if (isWithinTouchCompositionWindow()) { |
2601 | + // There's still time for some new touch to appear and ruin our party as it would be combined |
2602 | + // with our m_touchId one and therefore deny the possibility of a single-finger gesture. |
2603 | + ddaDebug("Sill within composition window. Let's wait more."); |
2604 | + return; |
2605 | + } |
2606 | + |
2607 | + if (movedFarEnough(touchScenePos)) { |
2608 | + TouchRegistry::instance()->requestTouchOwnership(m_touchId, this); |
2609 | + setStatus(Recognized); |
2610 | + } else { |
2611 | + ddaDebug("Didn't move far enough yet. Let's wait more."); |
2612 | + } |
2613 | +} |
2614 | + |
2615 | +void DirectionalDragArea::touchEvent(QTouchEvent *event) |
2616 | +{ |
2617 | + // TODO: Consider when more than one touch starts in the same event (although it's not possible |
2618 | + // with Mir's android-input). Have to track them all. Consider it a plus/bonus. |
2619 | + |
2620 | + ddaDebug(m_timeSource->msecsSinceReference() << " " << qPrintable(touchEventToString(event))); |
2621 | + |
2622 | + if (!isEnabled() || !isVisible()) { |
2623 | + QQuickItem::touchEvent(event); |
2624 | + return; |
2625 | + } |
2626 | + |
2627 | + switch (m_status) { |
2628 | + case WaitingForTouch: |
2629 | + touchEvent_absent(event); |
2630 | + break; |
2631 | + case Undecided: |
2632 | + touchEvent_undecided(event); |
2633 | + break; |
2634 | + default: // Recognized: |
2635 | + touchEvent_recognized(event); |
2636 | + break; |
2637 | + } |
2638 | + |
2639 | + m_activeTouches.update(event); |
2640 | +} |
2641 | + |
2642 | +void DirectionalDragArea::touchEvent_absent(QTouchEvent *event) |
2643 | +{ |
2644 | + // TODO: accept/reject is for the whole event, not per touch id. See how that affects us. |
2645 | + |
2646 | + if (!event->touchPointStates().testFlag(Qt::TouchPointPressed)) { |
2647 | + // Nothing to see here. No touch starting in this event. |
2648 | + return; |
2649 | + } |
2650 | + |
2651 | + // to be proven wrong, if that's the case |
2652 | + bool allGood = true; |
2653 | + |
2654 | + if (isWithinTouchCompositionWindow()) { |
2655 | + // too close to the last touch start. So we consider them as starting roughly at the same time. |
2656 | + // Can't be a single-touch gesture. |
2657 | + ddaDebug("A new touch point came in but we're still within time composition window. Ignoring it."); |
2658 | + allGood = false; |
2659 | + } |
2660 | + |
2661 | + const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints(); |
2662 | + |
2663 | + const QTouchEvent::TouchPoint *newTouchPoint = nullptr; |
2664 | + for (int i = 0; i < touchPoints.count() && allGood; ++i) { |
2665 | + const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i); |
2666 | + if (touchPoint.state() == Qt::TouchPointPressed) { |
2667 | + if (newTouchPoint) { |
2668 | + // more than one touch starting in this QTouchEvent. Can't be a single-touch gesture |
2669 | + allGood = false; |
2670 | + } else { |
2671 | + // that's our candidate |
2672 | + m_touchId = touchPoint.id(); |
2673 | + newTouchPoint = &touchPoint; |
2674 | + } |
2675 | + } |
2676 | + } |
2677 | + |
2678 | + if (allGood) { |
2679 | + Q_ASSERT(newTouchPoint); |
2680 | + |
2681 | + m_startPos = newTouchPoint->pos(); |
2682 | + m_startScenePos = newTouchPoint->scenePos(); |
2683 | + m_touchId = newTouchPoint->id(); |
2684 | + m_dampedScenePos.reset(m_startScenePos); |
2685 | + m_velocityCalculator->setTrackedPosition(0.); |
2686 | + m_velocityCalculator->reset(); |
2687 | + m_numSamplesOnLastSpeedCheck = 0; |
2688 | + m_silenceTime = 0; |
2689 | + setPreviousPos(m_startPos); |
2690 | + setPreviousScenePos(m_startScenePos); |
2691 | + updateSceneDirectionVector(); |
2692 | + |
2693 | + if (recognitionIsDisabled()) { |
2694 | + // Behave like a dumb TouchArea |
2695 | + ddaDebug("Gesture recognition is disabled. Requesting touch ownership immediately."); |
2696 | + TouchRegistry::instance()->requestTouchOwnership(m_touchId, this); |
2697 | + setStatus(Recognized); |
2698 | + event->accept(); |
2699 | + } else { |
2700 | + // just monitor the touch points for now. |
2701 | + TouchRegistry::instance()->addCandidateOwnerForTouch(m_touchId, this); |
2702 | + |
2703 | + setStatus(Undecided); |
2704 | + // Let the item below have it. We will monitor it and grab it later if a gesture |
2705 | + // gets recognized. |
2706 | + event->ignore(); |
2707 | + } |
2708 | + } else { |
2709 | + watchPressedTouchPoints(touchPoints); |
2710 | + event->ignore(); |
2711 | + } |
2712 | +} |
2713 | + |
2714 | +void DirectionalDragArea::touchEvent_undecided(QTouchEvent *event) |
2715 | +{ |
2716 | + Q_ASSERT(event->type() == QEvent::TouchBegin); |
2717 | + Q_ASSERT(fetchTargetTouchPoint(event) == nullptr); |
2718 | + |
2719 | + // We're not interested in new touch points. We already have our candidate (m_touchId). |
2720 | + // But we do want to know when those new touches end for keeping the composition time |
2721 | + // window up-to-date |
2722 | + event->ignore(); |
2723 | + watchPressedTouchPoints(event->touchPoints()); |
2724 | + |
2725 | + if (event->touchPointStates().testFlag(Qt::TouchPointPressed) && isWithinTouchCompositionWindow()) { |
2726 | + // multi-finger drags are not accepted |
2727 | + ddaDebug("Multi-finger drags are not accepted"); |
2728 | + |
2729 | + TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this); |
2730 | + // We still wanna know when it ends for keeping the composition time window up-to-date |
2731 | + TouchRegistry::instance()->addTouchWatcher(m_touchId, this); |
2732 | + |
2733 | + setStatus(WaitingForTouch); |
2734 | + } |
2735 | +} |
2736 | + |
2737 | +void DirectionalDragArea::touchEvent_recognized(QTouchEvent *event) |
2738 | +{ |
2739 | + const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(event); |
2740 | + |
2741 | + if (!touchPoint) { |
2742 | + qCritical() << "DirectionalDragArea[status=Recognized]: touch " << m_touchId |
2743 | + << "missing from QTouchEvent without first reaching state Qt::TouchPointReleased. " |
2744 | + "Considering it as released."; |
2745 | + setStatus(WaitingForTouch); |
2746 | + } else { |
2747 | + setPreviousPos(touchPoint->pos()); |
2748 | + setPreviousScenePos(touchPoint->scenePos()); |
2749 | + |
2750 | + if (touchPoint->state() == Qt::TouchPointReleased) { |
2751 | + emitSignalIfTapped(); |
2752 | + setStatus(WaitingForTouch); |
2753 | + } |
2754 | + } |
2755 | +} |
2756 | + |
2757 | +void DirectionalDragArea::watchPressedTouchPoints(const QList<QTouchEvent::TouchPoint> &touchPoints) |
2758 | +{ |
2759 | + for (int i = 0; i < touchPoints.count(); ++i) { |
2760 | + const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i); |
2761 | + if (touchPoint.state() == Qt::TouchPointPressed) { |
2762 | + TouchRegistry::instance()->addTouchWatcher(touchPoint.id(), this); |
2763 | + } |
2764 | + } |
2765 | +} |
2766 | + |
2767 | +bool DirectionalDragArea::recognitionIsDisabled() const |
2768 | +{ |
2769 | + return distanceThreshold() <= 0 && compositionTime() <= 0; |
2770 | +} |
2771 | + |
2772 | +void DirectionalDragArea::emitSignalIfTapped() |
2773 | +{ |
2774 | + qint64 touchDuration = m_timeSource->msecsSinceReference() - m_activeTouches.touchStartTime(m_touchId); |
2775 | + if (touchDuration <= maxTapDuration()) { |
2776 | + Q_EMIT tapped(); |
2777 | + } |
2778 | +} |
2779 | + |
2780 | +const QTouchEvent::TouchPoint *DirectionalDragArea::fetchTargetTouchPoint(QTouchEvent *event) |
2781 | +{ |
2782 | + const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints(); |
2783 | + const QTouchEvent::TouchPoint *touchPoint = 0; |
2784 | + for (int i = 0; i < touchPoints.size(); ++i) { |
2785 | + if (touchPoints.at(i).id() == m_touchId) { |
2786 | + touchPoint = &touchPoints.at(i); |
2787 | + break; |
2788 | + } |
2789 | + } |
2790 | + return touchPoint; |
2791 | +} |
2792 | + |
2793 | +bool DirectionalDragArea::pointInsideAllowedArea() const |
2794 | +{ |
2795 | + // NB: Using squared values to avoid computing the square root to find |
2796 | + // the length totalMovement |
2797 | + |
2798 | + QPointF totalMovement(m_dampedScenePos.x() - m_startScenePos.x(), |
2799 | + m_dampedScenePos.y() - m_startScenePos.y()); |
2800 | + |
2801 | + qreal squaredTotalMovSize = totalMovement.x() * totalMovement.x() + |
2802 | + totalMovement.y() * totalMovement.y(); |
2803 | + |
2804 | + if (squaredTotalMovSize == 0.) { |
2805 | + // didn't move |
2806 | + return true; |
2807 | + } |
2808 | + |
2809 | + qreal projectedMovement = projectOntoDirectionVector(totalMovement); |
2810 | + |
2811 | + |
2812 | + qreal cosineAngleSquared = (projectedMovement * projectedMovement) / squaredTotalMovSize; |
2813 | + |
2814 | + // Same as: |
2815 | + // angle_between_movement_vector_and_gesture_direction_vector <= widening_angle |
2816 | + return cosineAngleSquared >= m_wideningFactor; |
2817 | +} |
2818 | + |
2819 | +bool DirectionalDragArea::movingInRightDirection() const |
2820 | +{ |
2821 | + if (m_direction == Direction::Horizontal) { |
2822 | + return true; |
2823 | + } else { |
2824 | + QPointF movementVector(m_dampedScenePos.x() - m_previousDampedScenePos.x(), |
2825 | + m_dampedScenePos.y() - m_previousDampedScenePos.y()); |
2826 | + |
2827 | + qreal scalarProjection = projectOntoDirectionVector(movementVector); |
2828 | + |
2829 | + return scalarProjection >= 0.; |
2830 | + } |
2831 | +} |
2832 | + |
2833 | +bool DirectionalDragArea::movedFarEnough(const QPointF &point) const |
2834 | +{ |
2835 | + if (m_distanceThreshold <= 0.) { |
2836 | + // distance threshold check is disabled |
2837 | + return true; |
2838 | + } else { |
2839 | + QPointF totalMovement(point.x() - m_startScenePos.x(), |
2840 | + point.y() - m_startScenePos.y()); |
2841 | + |
2842 | + qreal squaredTotalMovSize = totalMovement.x() * totalMovement.x() + |
2843 | + totalMovement.y() * totalMovement.y(); |
2844 | + |
2845 | + return squaredTotalMovSize > m_distanceThresholdSquared; |
2846 | + } |
2847 | +} |
2848 | + |
2849 | +void DirectionalDragArea::checkSpeed() |
2850 | +{ |
2851 | + Q_ASSERT(m_status == Undecided); |
2852 | + |
2853 | + if (m_velocityCalculator->numSamples() >= AxisVelocityCalculator::MIN_SAMPLES_NEEDED) { |
2854 | + qreal speed = qFabs(m_velocityCalculator->calculate()); |
2855 | + qreal minSpeedMsecs = m_minSpeed / 1000.0; |
2856 | + |
2857 | + if (speed < minSpeedMsecs) { |
2858 | + ddaDebug("Rejecting gesture because it's below minimum speed."); |
2859 | + TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this); |
2860 | + TouchRegistry::instance()->addTouchWatcher(m_touchId, this); |
2861 | + setStatus(WaitingForTouch); |
2862 | + } |
2863 | + } |
2864 | + |
2865 | + if (m_velocityCalculator->numSamples() == m_numSamplesOnLastSpeedCheck) { |
2866 | + m_silenceTime += m_recognitionTimer->interval(); |
2867 | + |
2868 | + if (m_silenceTime > m_maxSilenceTime) { |
2869 | + ddaDebug("Rejecting gesture because its silence time has been exceeded."); |
2870 | + TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this); |
2871 | + TouchRegistry::instance()->addTouchWatcher(m_touchId, this); |
2872 | + setStatus(WaitingForTouch); |
2873 | + } |
2874 | + } else { |
2875 | + m_silenceTime = 0; |
2876 | + } |
2877 | + m_numSamplesOnLastSpeedCheck = m_velocityCalculator->numSamples(); |
2878 | +} |
2879 | + |
2880 | +void DirectionalDragArea::giveUpIfDisabledOrInvisible() |
2881 | +{ |
2882 | + if (!isEnabled() || !isVisible()) { |
2883 | + if (m_status == Undecided) { |
2884 | + TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this); |
2885 | + // We still wanna know when it ends for keeping the composition time window up-to-date |
2886 | + TouchRegistry::instance()->addTouchWatcher(m_touchId, this); |
2887 | + } |
2888 | + |
2889 | + if (m_status != WaitingForTouch) { |
2890 | + ddaDebug("Resetting status because got disabled or made invisible"); |
2891 | + setStatus(WaitingForTouch); |
2892 | + } |
2893 | + } |
2894 | +} |
2895 | + |
2896 | +void DirectionalDragArea::setStatus(DirectionalDragArea::Status newStatus) |
2897 | +{ |
2898 | + if (newStatus == m_status) |
2899 | + return; |
2900 | + |
2901 | + DirectionalDragArea::Status oldStatus = m_status; |
2902 | + |
2903 | + if (oldStatus == Undecided) { |
2904 | + m_recognitionTimer->stop(); |
2905 | + } |
2906 | + |
2907 | + m_status = newStatus; |
2908 | + Q_EMIT statusChanged(m_status); |
2909 | + |
2910 | + ddaDebug(statusToString(oldStatus) << " -> " << statusToString(newStatus)); |
2911 | + |
2912 | + switch (newStatus) { |
2913 | + case WaitingForTouch: |
2914 | + Q_EMIT draggingChanged(false); |
2915 | + break; |
2916 | + case Undecided: |
2917 | + m_recognitionTimer->start(); |
2918 | + Q_EMIT draggingChanged(true); |
2919 | + break; |
2920 | + case Recognized: |
2921 | + if (oldStatus == WaitingForTouch) |
2922 | + Q_EMIT draggingChanged(true); |
2923 | + break; |
2924 | + default: |
2925 | + // no-op |
2926 | + break; |
2927 | + } |
2928 | +} |
2929 | + |
2930 | +void DirectionalDragArea::setPreviousPos(const QPointF &point) |
2931 | +{ |
2932 | + bool xChanged = m_previousPos.x() != point.x(); |
2933 | + bool yChanged = m_previousPos.y() != point.y(); |
2934 | + |
2935 | + m_previousPos = point; |
2936 | + |
2937 | + if (xChanged) { |
2938 | + Q_EMIT touchXChanged(point.x()); |
2939 | + if (Direction::isHorizontal(m_direction)) |
2940 | + Q_EMIT distanceChanged(distance()); |
2941 | + } |
2942 | + |
2943 | + if (yChanged) { |
2944 | + Q_EMIT touchYChanged(point.y()); |
2945 | + if (Direction::isVertical(m_direction)) |
2946 | + Q_EMIT distanceChanged(distance()); |
2947 | + } |
2948 | +} |
2949 | + |
2950 | +void DirectionalDragArea::setPreviousScenePos(const QPointF &point) |
2951 | +{ |
2952 | + bool xChanged = m_previousScenePos.x() != point.x(); |
2953 | + bool yChanged = m_previousScenePos.y() != point.y(); |
2954 | + |
2955 | + if (!xChanged && !yChanged) |
2956 | + return; |
2957 | + |
2958 | + qreal oldSceneDistance = sceneDistance(); |
2959 | + m_previousScenePos = point; |
2960 | + updateSceneDistance(); |
2961 | + |
2962 | + if (oldSceneDistance != sceneDistance()) { |
2963 | + Q_EMIT sceneDistanceChanged(sceneDistance()); |
2964 | + } |
2965 | + |
2966 | + if (xChanged) { |
2967 | + Q_EMIT touchSceneXChanged(point.x()); |
2968 | + } |
2969 | + |
2970 | + if (yChanged) { |
2971 | + Q_EMIT touchSceneYChanged(point.y()); |
2972 | + } |
2973 | +} |
2974 | + |
2975 | +void DirectionalDragArea::updateVelocityCalculator(const QPointF &scenePos) |
2976 | +{ |
2977 | + QPointF totalSceneMovement = scenePos - m_startScenePos; |
2978 | + |
2979 | + qreal scalarProjection = projectOntoDirectionVector(totalSceneMovement); |
2980 | + |
2981 | + m_velocityCalculator->setTrackedPosition(scalarProjection); |
2982 | +} |
2983 | + |
2984 | +bool DirectionalDragArea::isWithinTouchCompositionWindow() |
2985 | +{ |
2986 | + return |
2987 | + compositionTime() > 0 && |
2988 | + !m_activeTouches.isEmpty() && |
2989 | + m_timeSource->msecsSinceReference() <= |
2990 | + m_activeTouches.mostRecentStartTime() + (qint64)compositionTime(); |
2991 | +} |
2992 | + |
2993 | +//************************** ActiveTouchesInfo ************************** |
2994 | + |
2995 | +DirectionalDragArea::ActiveTouchesInfo::ActiveTouchesInfo(const SharedTimeSource &timeSource) |
2996 | + : m_timeSource(timeSource) |
2997 | +{ |
2998 | +} |
2999 | + |
3000 | +void DirectionalDragArea::ActiveTouchesInfo::update(QTouchEvent *event) |
3001 | +{ |
3002 | + if (!(event->touchPointStates() & (Qt::TouchPointPressed | Qt::TouchPointReleased))) { |
3003 | + // nothing to update |
3004 | + #if ACTIVETOUCHESINFO_DEBUG |
3005 | + qDebug("[DDA::ActiveTouchesInfo] Nothing to Update"); |
3006 | + #endif |
3007 | + return; |
3008 | + } |
3009 | + |
3010 | + const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints(); |
3011 | + for (int i = 0; i < touchPoints.count(); ++i) { |
3012 | + const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i); |
3013 | + if (touchPoint.state() == Qt::TouchPointPressed) { |
3014 | + addTouchPoint(touchPoint.id()); |
3015 | + } else if (touchPoint.state() == Qt::TouchPointReleased) { |
3016 | + removeTouchPoint(touchPoint.id()); |
3017 | + } |
3018 | + } |
3019 | +} |
3020 | + |
3021 | +#if ACTIVETOUCHESINFO_DEBUG |
3022 | +QString DirectionalDragArea::ActiveTouchesInfo::toString() |
3023 | +{ |
3024 | + QString string = "("; |
3025 | + |
3026 | + { |
3027 | + QTextStream stream(&string); |
3028 | + m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) { |
3029 | + stream << "(id=" << touchInfo->id << ",startTime=" << touchInfo->startTime << ")"; |
3030 | + return true; |
3031 | + }); |
3032 | + } |
3033 | + |
3034 | + string.append(")"); |
3035 | + |
3036 | + return string; |
3037 | +} |
3038 | +#endif // ACTIVETOUCHESINFO_DEBUG |
3039 | + |
3040 | +void DirectionalDragArea::ActiveTouchesInfo::addTouchPoint(int touchId) |
3041 | +{ |
3042 | + ActiveTouchInfo &activeTouchInfo = m_touchInfoPool.getEmptySlot(); |
3043 | + activeTouchInfo.id = touchId; |
3044 | + activeTouchInfo.startTime = m_timeSource->msecsSinceReference(); |
3045 | + |
3046 | + #if ACTIVETOUCHESINFO_DEBUG |
3047 | + qDebug() << "[DDA::ActiveTouchesInfo]" << qPrintable(toString()); |
3048 | + #endif |
3049 | +} |
3050 | + |
3051 | +qint64 DirectionalDragArea::ActiveTouchesInfo::touchStartTime(int touchId) |
3052 | +{ |
3053 | + qint64 result = -1; |
3054 | + |
3055 | + m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) { |
3056 | + if (touchId == touchInfo->id) { |
3057 | + result = touchInfo->startTime; |
3058 | + return false; |
3059 | + } else { |
3060 | + return true; |
3061 | + } |
3062 | + }); |
3063 | + |
3064 | + Q_ASSERT(result != -1); |
3065 | + return result; |
3066 | +} |
3067 | + |
3068 | +void DirectionalDragArea::ActiveTouchesInfo::removeTouchPoint(int touchId) |
3069 | +{ |
3070 | + m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) { |
3071 | + if (touchId == touchInfo->id) { |
3072 | + m_touchInfoPool.freeSlot(touchInfo); |
3073 | + return false; |
3074 | + } else { |
3075 | + return true; |
3076 | + } |
3077 | + }); |
3078 | + |
3079 | + #if ACTIVETOUCHESINFO_DEBUG |
3080 | + qDebug() << "[DDA::ActiveTouchesInfo]" << qPrintable(toString()); |
3081 | + #endif |
3082 | +} |
3083 | + |
3084 | +qint64 DirectionalDragArea::ActiveTouchesInfo::mostRecentStartTime() |
3085 | +{ |
3086 | + Q_ASSERT(!m_touchInfoPool.isEmpty()); |
3087 | + |
3088 | + qint64 highestStartTime = -1; |
3089 | + |
3090 | + m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &activeTouchInfo) { |
3091 | + if (activeTouchInfo->startTime > highestStartTime) { |
3092 | + highestStartTime = activeTouchInfo->startTime; |
3093 | + } |
3094 | + return true; |
3095 | + }); |
3096 | + |
3097 | + return highestStartTime; |
3098 | +} |
3099 | + |
3100 | +void DirectionalDragArea::updateSceneDirectionVector() |
3101 | +{ |
3102 | + QPointF localOrigin(0., 0.); |
3103 | + QPointF localDirection; |
3104 | + switch (m_direction) { |
3105 | + case Direction::Upwards: |
3106 | + localDirection.rx() = 0.; |
3107 | + localDirection.ry() = -1.; |
3108 | + break; |
3109 | + case Direction::Downwards: |
3110 | + localDirection.rx() = 0.; |
3111 | + localDirection.ry() = 1; |
3112 | + break; |
3113 | + case Direction::Leftwards: |
3114 | + localDirection.rx() = -1.; |
3115 | + localDirection.ry() = 0.; |
3116 | + break; |
3117 | + default: // Direction::Rightwards || Direction.Horizontal |
3118 | + localDirection.rx() = 1.; |
3119 | + localDirection.ry() = 0.; |
3120 | + break; |
3121 | + } |
3122 | + QPointF sceneOrigin = mapToScene(localOrigin); |
3123 | + QPointF sceneDirection = mapToScene(localDirection); |
3124 | + m_sceneDirectionVector = sceneDirection - sceneOrigin; |
3125 | +} |
3126 | + |
3127 | +qreal DirectionalDragArea::projectOntoDirectionVector(const QPointF &sceneVector) const |
3128 | +{ |
3129 | + // same as dot product as m_sceneDirectionVector is a unit vector |
3130 | + return sceneVector.x() * m_sceneDirectionVector.x() + |
3131 | + sceneVector.y() * m_sceneDirectionVector.y(); |
3132 | +} |
3133 | |
3134 | === added file 'src/app/unity8/plugins/Ubuntu/Gestures/DirectionalDragArea.h' |
3135 | --- src/app/unity8/plugins/Ubuntu/Gestures/DirectionalDragArea.h 1970-01-01 00:00:00 +0000 |
3136 | +++ src/app/unity8/plugins/Ubuntu/Gestures/DirectionalDragArea.h 2015-03-20 16:26:56 +0000 |
3137 | @@ -0,0 +1,308 @@ |
3138 | +/* |
3139 | + * Copyright (C) 2013,2014 Canonical, Ltd. |
3140 | + * |
3141 | + * This program is free software; you can redistribute it and/or modify |
3142 | + * it under the terms of the GNU General Public License as published by |
3143 | + * the Free Software Foundation; version 3. |
3144 | + * |
3145 | + * This program is distributed in the hope that it will be useful, |
3146 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
3147 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3148 | + * GNU General Public License for more details. |
3149 | + * |
3150 | + * You should have received a copy of the GNU General Public License |
3151 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
3152 | + */ |
3153 | + |
3154 | +#ifndef DIRECTIONAL_DRAG_AREA_H |
3155 | +#define DIRECTIONAL_DRAG_AREA_H |
3156 | + |
3157 | +#include <QtQuick/QQuickItem> |
3158 | +#include "AxisVelocityCalculator.h" |
3159 | +#include "UbuntuGesturesQmlGlobal.h" |
3160 | +#include "Damper.h" |
3161 | +#include "Direction.h" |
3162 | + |
3163 | +// lib UbuntuGestures |
3164 | +#include <Pool.h> |
3165 | +#include <Timer.h> |
3166 | + |
3167 | +class TouchOwnershipEvent; |
3168 | +class UnownedTouchEvent; |
3169 | + |
3170 | +/* |
3171 | + An area that detects axis-aligned single-finger drag gestures |
3172 | + |
3173 | + If a drag deviates too much from the components' direction recognition will |
3174 | + fail. It will also fail if the drag or flick is too short. E.g. a noisy or |
3175 | + fidgety click |
3176 | + |
3177 | + See doc/DirectionalDragArea.svg |
3178 | + */ |
3179 | +class UBUNTUGESTURESQML_EXPORT DirectionalDragArea : public QQuickItem { |
3180 | + Q_OBJECT |
3181 | + |
3182 | + // The direction in which the gesture should move in order to be recognized. |
3183 | + Q_PROPERTY(Direction::Type direction READ direction WRITE setDirection NOTIFY directionChanged) |
3184 | + |
3185 | + // The distance travelled by the finger along the axis specified by |
3186 | + // DirectionalDragArea's direction. |
3187 | + Q_PROPERTY(qreal distance READ distance NOTIFY distanceChanged) |
3188 | + |
3189 | + // The distance travelled by the finger along the axis specified by |
3190 | + // DirectionalDragArea's direction in scene coordinates |
3191 | + Q_PROPERTY(qreal sceneDistance READ sceneDistance NOTIFY sceneDistanceChanged) |
3192 | + |
3193 | + // Position of the touch point performing the drag relative to this item. |
3194 | + Q_PROPERTY(qreal touchX READ touchX NOTIFY touchXChanged) |
3195 | + Q_PROPERTY(qreal touchY READ touchY NOTIFY touchYChanged) |
3196 | + |
3197 | + // Position of the touch point performing the drag, in scene's coordinate system |
3198 | + Q_PROPERTY(qreal touchSceneX READ touchSceneX NOTIFY touchSceneXChanged) |
3199 | + Q_PROPERTY(qreal touchSceneY READ touchSceneY NOTIFY touchSceneYChanged) |
3200 | + |
3201 | + // The current status of the directional drag gesture area. |
3202 | + Q_PROPERTY(Status status READ status NOTIFY statusChanged) |
3203 | + |
3204 | + // Whether a drag gesture is taking place |
3205 | + // This will be true as long as status is Undecided or Recognized |
3206 | + // When a gesture gets rejected, dragging turns to false. |
3207 | + Q_PROPERTY(bool dragging READ dragging NOTIFY draggingChanged) |
3208 | + |
3209 | + ///// |
3210 | + // stuff that will be set in stone at some point |
3211 | + |
3212 | + // How far the touch point can move away from its expected position before |
3213 | + // it causes a rejection in the gesture recognition. This is to compensate |
3214 | + // for both noise in the touch input signal and for the natural irregularities |
3215 | + // in the finger movement. |
3216 | + // Proper value is likely device-specific. |
3217 | + Q_PROPERTY(qreal maxDeviation READ maxDeviation WRITE setMaxDeviation NOTIFY maxDeviationChanged) |
3218 | + |
3219 | + // Widening angle, in degrees |
3220 | + // It's roughly the maximum angle a touch point can make relative to the |
3221 | + // axis defined by the compoment's direction for it to be recognized as a |
3222 | + // directional drag. |
3223 | + Q_PROPERTY(qreal wideningAngle READ wideningAngle WRITE setWideningAngle |
3224 | + NOTIFY wideningAngleChanged) |
3225 | + |
3226 | + // How far a touch point has to move from its initial position in order for |
3227 | + // it to be recognized as a directional drag. |
3228 | + Q_PROPERTY(qreal distanceThreshold READ distanceThreshold WRITE setDistanceThreshold |
3229 | + NOTIFY distanceThresholdChanged) |
3230 | + |
3231 | + // Minimum speed a gesture needs to have in order to be recognized as a |
3232 | + // directional drag. |
3233 | + // In pixels per second |
3234 | + Q_PROPERTY(qreal minSpeed READ minSpeed WRITE setMinSpeed NOTIFY minSpeedChanged) |
3235 | + |
3236 | + // A gesture will be rejected if more than maxSilenceTime milliseconds has |
3237 | + // passed since we last got an input event from it (during Undecided state). |
3238 | + // |
3239 | + // Silence (i.e., lack of new input events) doesn't necessarily mean that the user's |
3240 | + // finger is still (zero drag speed). In some cases the finger might be moving but |
3241 | + // the driver's high noise filtering might cause those silence periods, specially |
3242 | + // in the moments succeeding a press (talking about Galaxy Nexus here). |
3243 | + Q_PROPERTY(int maxSilenceTime READ maxSilenceTime |
3244 | + WRITE setMaxSilenceTime |
3245 | + NOTIFY maxSilenceTimeChanged) |
3246 | + |
3247 | + // |
3248 | + ///// |
3249 | + |
3250 | + // Maximum time (in milliseconds) after the start of a given touch point where |
3251 | + // subsequent touch starts are grouped with the first one into an N-touches gesture |
3252 | + // (e.g. a two-fingers tap or drag). |
3253 | + Q_PROPERTY(int compositionTime READ compositionTime |
3254 | + WRITE setCompositionTime |
3255 | + NOTIFY compositionTimeChanged) |
3256 | + |
3257 | + Q_ENUMS(Direction) |
3258 | + Q_ENUMS(Status) |
3259 | +public: |
3260 | + DirectionalDragArea(QQuickItem *parent = 0); |
3261 | + |
3262 | + Direction::Type direction() const; |
3263 | + void setDirection(Direction::Type); |
3264 | + |
3265 | + // Describes the state of the directional drag gesture. |
3266 | + enum Status { |
3267 | + // Waiting for a new touch point to land on this area. No gesture is being processed |
3268 | + // or tracked. |
3269 | + WaitingForTouch, |
3270 | + |
3271 | + // A touch point has landed on this area but it's not know yet whether it is |
3272 | + // performing a drag in the correct direction. |
3273 | + // If it's decided that the touch point is not performing a directional drag gesture, |
3274 | + // it will be rejected/ignored and status will return to WaitingForTouch. |
3275 | + Undecided, //Recognizing, |
3276 | + |
3277 | + // There's a touch point in this area and it performed a drag in the correct |
3278 | + // direction. |
3279 | + // |
3280 | + // Once recognized, the gesture state will move back to WaitingForTouch only once |
3281 | + // that touch point ends. The gesture will remain in the Recognized state even if |
3282 | + // the touch point starts moving in other directions or halts. |
3283 | + Recognized, |
3284 | + }; |
3285 | + Status status() const { return m_status; } |
3286 | + |
3287 | + qreal distance() const; |
3288 | + qreal sceneDistance() const; |
3289 | + void updateSceneDistance(); |
3290 | + |
3291 | + qreal touchX() const; |
3292 | + qreal touchY() const; |
3293 | + |
3294 | + qreal touchSceneX() const; |
3295 | + qreal touchSceneY() const; |
3296 | + |
3297 | + bool dragging() const { return (m_status == Undecided) || (m_status == Recognized); } |
3298 | + |
3299 | + qreal maxDeviation() const { return m_dampedScenePos.maxDelta(); } |
3300 | + void setMaxDeviation(qreal value); |
3301 | + |
3302 | + qreal wideningAngle() const; |
3303 | + void setWideningAngle(qreal value); |
3304 | + |
3305 | + qreal distanceThreshold() const { return m_distanceThreshold; } |
3306 | + void setDistanceThreshold(qreal value); |
3307 | + |
3308 | + qreal minSpeed() const { return m_minSpeed; } |
3309 | + void setMinSpeed(qreal value); |
3310 | + |
3311 | + int maxSilenceTime() const { return m_maxSilenceTime; } |
3312 | + void setMaxSilenceTime(int value); |
3313 | + |
3314 | + int compositionTime() const { return m_compositionTime; } |
3315 | + void setCompositionTime(int value); |
3316 | + |
3317 | + // Replaces the existing Timer with the given one. |
3318 | + // |
3319 | + // Useful for providing a fake timer when testing. |
3320 | + void setRecognitionTimer(UbuntuGestures::AbstractTimer *timer); |
3321 | + |
3322 | + // Useful for testing, where a fake time source can be supplied |
3323 | + void setTimeSource(const UbuntuGestures::SharedTimeSource &timeSource); |
3324 | + |
3325 | + bool event(QEvent *e) override; |
3326 | + |
3327 | + // Maximum time, in milliseconds, between a press and a release, for a touch |
3328 | + // sequence to be considered a tap. |
3329 | + int maxTapDuration() const { return 300; } |
3330 | + |
3331 | +Q_SIGNALS: |
3332 | + void directionChanged(Direction::Type direction); |
3333 | + void statusChanged(Status value); |
3334 | + void draggingChanged(bool value); |
3335 | + void distanceChanged(qreal value); |
3336 | + void sceneDistanceChanged(qreal value); |
3337 | + void maxDeviationChanged(qreal value); |
3338 | + void wideningAngleChanged(qreal value); |
3339 | + void distanceThresholdChanged(qreal value); |
3340 | + void minSpeedChanged(qreal value); |
3341 | + void maxSilenceTimeChanged(int value); |
3342 | + void compositionTimeChanged(int value); |
3343 | + void touchXChanged(qreal value); |
3344 | + void touchYChanged(qreal value); |
3345 | + void touchSceneXChanged(qreal value); |
3346 | + void touchSceneYChanged(qreal value); |
3347 | + |
3348 | + // TODO: I would rather not have such signal as it has nothing to do with drag gestures. |
3349 | + // Remove when no longer used or move its implementation to the QML code that uses it |
3350 | + // See maxTapDuration() |
3351 | + void tapped(); |
3352 | + |
3353 | +protected: |
3354 | + virtual void touchEvent(QTouchEvent *event); |
3355 | + |
3356 | +private Q_SLOTS: |
3357 | + void checkSpeed(); |
3358 | + void giveUpIfDisabledOrInvisible(); |
3359 | + |
3360 | +private: |
3361 | + void touchEvent_absent(QTouchEvent *event); |
3362 | + void touchEvent_undecided(QTouchEvent *event); |
3363 | + void touchEvent_recognized(QTouchEvent *event); |
3364 | + bool pointInsideAllowedArea() const; |
3365 | + bool movingInRightDirection() const; |
3366 | + bool movedFarEnough(const QPointF &point) const; |
3367 | + const QTouchEvent::TouchPoint *fetchTargetTouchPoint(QTouchEvent *event); |
3368 | + void setStatus(Status newStatus); |
3369 | + void setPreviousPos(const QPointF &point); |
3370 | + void setPreviousScenePos(const QPointF &point); |
3371 | + void updateVelocityCalculator(const QPointF &point); |
3372 | + bool isWithinTouchCompositionWindow(); |
3373 | + void updateSceneDirectionVector(); |
3374 | + // returns the scalar projection between the given vector (in scene coordinates) |
3375 | + // and m_sceneDirectionVector |
3376 | + qreal projectOntoDirectionVector(const QPointF &sceneVector) const; |
3377 | + void touchOwnershipEvent(TouchOwnershipEvent *event); |
3378 | + void unownedTouchEvent(UnownedTouchEvent *event); |
3379 | + void unownedTouchEvent_undecided(UnownedTouchEvent *unownedTouchEvent); |
3380 | + void watchPressedTouchPoints(const QList<QTouchEvent::TouchPoint> &touchPoints); |
3381 | + bool recognitionIsDisabled() const; |
3382 | + void emitSignalIfTapped(); |
3383 | + |
3384 | + Status m_status; |
3385 | + |
3386 | + QPointF m_startPos; |
3387 | + QPointF m_startScenePos; |
3388 | + QPointF m_previousPos; |
3389 | + QPointF m_previousScenePos; |
3390 | + qreal m_sceneDistance; |
3391 | + int m_touchId; |
3392 | + |
3393 | + // A movement damper is used in some of the gesture recognition calculations |
3394 | + // to get rid of noise or small oscillations in the touch position. |
3395 | + DampedPointF m_dampedScenePos; |
3396 | + QPointF m_previousDampedScenePos; |
3397 | + |
3398 | + // Unit vector in scene coordinates describing the direction of the gesture recognition |
3399 | + QPointF m_sceneDirectionVector; |
3400 | + |
3401 | + Direction::Type m_direction; |
3402 | + qreal m_wideningAngle; // in degrees |
3403 | + qreal m_wideningFactor; // it's pow(cosine(m_wideningAngle), 2) |
3404 | + qreal m_distanceThreshold; |
3405 | + qreal m_distanceThresholdSquared; // it's pow(m_distanceThreshold, 2) |
3406 | + qreal m_minSpeed; |
3407 | + int m_maxSilenceTime; // in milliseconds |
3408 | + int m_silenceTime; // in milliseconds |
3409 | + int m_compositionTime; // in milliseconds |
3410 | + int m_numSamplesOnLastSpeedCheck; |
3411 | + UbuntuGestures::AbstractTimer *m_recognitionTimer; |
3412 | + AxisVelocityCalculator *m_velocityCalculator; |
3413 | + |
3414 | + UbuntuGestures::SharedTimeSource m_timeSource; |
3415 | + |
3416 | + // Information about an active touch point |
3417 | + struct ActiveTouchInfo { |
3418 | + ActiveTouchInfo() : id(-1), startTime(-1) {} |
3419 | + bool isValid() const { return id != -1; } |
3420 | + void reset() { id = -1; } |
3421 | + int id; |
3422 | + qint64 startTime; |
3423 | + }; |
3424 | + class ActiveTouchesInfo { |
3425 | + public: |
3426 | + ActiveTouchesInfo(const UbuntuGestures::SharedTimeSource &timeSource); |
3427 | + void update(QTouchEvent *event); |
3428 | + qint64 touchStartTime(int id); |
3429 | + bool isEmpty() const { return m_touchInfoPool.isEmpty(); } |
3430 | + qint64 mostRecentStartTime(); |
3431 | + UbuntuGestures::SharedTimeSource m_timeSource; |
3432 | + private: |
3433 | + void addTouchPoint(int touchId); |
3434 | + void removeTouchPoint(int touchId); |
3435 | + #if ACTIVETOUCHESINFO_DEBUG |
3436 | + QString toString(); |
3437 | + #endif |
3438 | + |
3439 | + Pool<ActiveTouchInfo> m_touchInfoPool; |
3440 | + } m_activeTouches; |
3441 | + |
3442 | + friend class tst_DirectionalDragArea; |
3443 | +}; |
3444 | + |
3445 | +#endif // DIRECTIONAL_DRAG_AREA_H |
3446 | |
3447 | === added file 'src/app/unity8/plugins/Ubuntu/Gestures/Gestures.qmltypes' |
3448 | --- src/app/unity8/plugins/Ubuntu/Gestures/Gestures.qmltypes 1970-01-01 00:00:00 +0000 |
3449 | +++ src/app/unity8/plugins/Ubuntu/Gestures/Gestures.qmltypes 2015-03-20 16:26:56 +0000 |
3450 | @@ -0,0 +1,168 @@ |
3451 | +import QtQuick.tooling 1.1 |
3452 | + |
3453 | +// This file describes the plugin-supplied types contained in the library. |
3454 | +// It is used for QML tooling purposes only. |
3455 | +// |
3456 | +// This file was auto-generated by: |
3457 | +// 'qmlplugindump -notrelocatable Ubuntu.Gestures 0.1 plugins' |
3458 | + |
3459 | +Module { |
3460 | + Component { |
3461 | + name: "AxisVelocityCalculator" |
3462 | + prototype: "QObject" |
3463 | + exports: ["Ubuntu.Gestures/AxisVelocityCalculator 0.1"] |
3464 | + exportMetaObjectRevisions: [0] |
3465 | + Property { name: "trackedPosition"; type: "double" } |
3466 | + Signal { |
3467 | + name: "trackedPositionChanged" |
3468 | + Parameter { name: "value"; type: "double" } |
3469 | + } |
3470 | + Method { name: "calculate"; type: "double" } |
3471 | + Method { name: "reset" } |
3472 | + } |
3473 | + Component { |
3474 | + name: "Direction" |
3475 | + prototype: "QObject" |
3476 | + exports: ["Ubuntu.Gestures/Direction 0.1"] |
3477 | + isCreatable: false |
3478 | + isSingleton: true |
3479 | + exportMetaObjectRevisions: [0] |
3480 | + Enum { |
3481 | + name: "Type" |
3482 | + values: { |
3483 | + "Rightwards": 0, |
3484 | + "Leftwards": 1, |
3485 | + "Downwards": 2, |
3486 | + "Upwards": 3, |
3487 | + "Horizontal": 4 |
3488 | + } |
3489 | + } |
3490 | + Method { |
3491 | + name: "isHorizontal" |
3492 | + type: "bool" |
3493 | + Parameter { name: "type"; type: "Direction::Type" } |
3494 | + } |
3495 | + Method { |
3496 | + name: "isVertical" |
3497 | + type: "bool" |
3498 | + Parameter { name: "type"; type: "Direction::Type" } |
3499 | + } |
3500 | + Method { |
3501 | + name: "isPositive" |
3502 | + type: "bool" |
3503 | + Parameter { name: "type"; type: "Direction::Type" } |
3504 | + } |
3505 | + } |
3506 | + Component { |
3507 | + name: "DirectionalDragArea" |
3508 | + defaultProperty: "data" |
3509 | + prototype: "QQuickItem" |
3510 | + exports: ["Ubuntu.Gestures/DirectionalDragArea 0.1"] |
3511 | + exportMetaObjectRevisions: [0] |
3512 | + Enum { |
3513 | + name: "Status" |
3514 | + values: { |
3515 | + "WaitingForTouch": 0, |
3516 | + "Undecided": 1, |
3517 | + "Recognized": 2 |
3518 | + } |
3519 | + } |
3520 | + Property { name: "direction"; type: "Direction::Type" } |
3521 | + Property { name: "distance"; type: "double"; isReadonly: true } |
3522 | + Property { name: "sceneDistance"; type: "double"; isReadonly: true } |
3523 | + Property { name: "touchX"; type: "double"; isReadonly: true } |
3524 | + Property { name: "touchY"; type: "double"; isReadonly: true } |
3525 | + Property { name: "touchSceneX"; type: "double"; isReadonly: true } |
3526 | + Property { name: "touchSceneY"; type: "double"; isReadonly: true } |
3527 | + Property { name: "status"; type: "Status"; isReadonly: true } |
3528 | + Property { name: "dragging"; type: "bool"; isReadonly: true } |
3529 | + Property { name: "maxDeviation"; type: "double" } |
3530 | + Property { name: "wideningAngle"; type: "double" } |
3531 | + Property { name: "distanceThreshold"; type: "double" } |
3532 | + Property { name: "minSpeed"; type: "double" } |
3533 | + Property { name: "maxSilenceTime"; type: "int" } |
3534 | + Property { name: "compositionTime"; type: "int" } |
3535 | + Signal { |
3536 | + name: "directionChanged" |
3537 | + Parameter { name: "direction"; type: "Direction::Type" } |
3538 | + } |
3539 | + Signal { |
3540 | + name: "statusChanged" |
3541 | + Parameter { name: "value"; type: "Status" } |
3542 | + } |
3543 | + Signal { |
3544 | + name: "draggingChanged" |
3545 | + Parameter { name: "value"; type: "bool" } |
3546 | + } |
3547 | + Signal { |
3548 | + name: "distanceChanged" |
3549 | + Parameter { name: "value"; type: "double" } |
3550 | + } |
3551 | + Signal { |
3552 | + name: "sceneDistanceChanged" |
3553 | + Parameter { name: "value"; type: "double" } |
3554 | + } |
3555 | + Signal { |
3556 | + name: "maxDeviationChanged" |
3557 | + Parameter { name: "value"; type: "double" } |
3558 | + } |
3559 | + Signal { |
3560 | + name: "wideningAngleChanged" |
3561 | + Parameter { name: "value"; type: "double" } |
3562 | + } |
3563 | + Signal { |
3564 | + name: "distanceThresholdChanged" |
3565 | + Parameter { name: "value"; type: "double" } |
3566 | + } |
3567 | + Signal { |
3568 | + name: "minSpeedChanged" |
3569 | + Parameter { name: "value"; type: "double" } |
3570 | + } |
3571 | + Signal { |
3572 | + name: "maxSilenceTimeChanged" |
3573 | + Parameter { name: "value"; type: "int" } |
3574 | + } |
3575 | + Signal { |
3576 | + name: "compositionTimeChanged" |
3577 | + Parameter { name: "value"; type: "int" } |
3578 | + } |
3579 | + Signal { |
3580 | + name: "touchXChanged" |
3581 | + Parameter { name: "value"; type: "double" } |
3582 | + } |
3583 | + Signal { |
3584 | + name: "touchYChanged" |
3585 | + Parameter { name: "value"; type: "double" } |
3586 | + } |
3587 | + Signal { |
3588 | + name: "touchSceneXChanged" |
3589 | + Parameter { name: "value"; type: "double" } |
3590 | + } |
3591 | + Signal { |
3592 | + name: "touchSceneYChanged" |
3593 | + Parameter { name: "value"; type: "double" } |
3594 | + } |
3595 | + Signal { name: "tapped" } |
3596 | + } |
3597 | + Component { |
3598 | + name: "PressedOutsideNotifier" |
3599 | + defaultProperty: "data" |
3600 | + prototype: "QQuickItem" |
3601 | + exports: ["Ubuntu.Gestures/PressedOutsideNotifier 0.1"] |
3602 | + exportMetaObjectRevisions: [0] |
3603 | + Signal { name: "pressedOutside" } |
3604 | + } |
3605 | + Component { |
3606 | + name: "TouchGate" |
3607 | + defaultProperty: "data" |
3608 | + prototype: "QQuickItem" |
3609 | + exports: ["Ubuntu.Gestures/TouchGate 0.1"] |
3610 | + exportMetaObjectRevisions: [0] |
3611 | + Property { name: "targetItem"; type: "QQuickItem"; isPointer: true } |
3612 | + Signal { |
3613 | + name: "targetItemChanged" |
3614 | + Parameter { name: "item"; type: "QQuickItem"; isPointer: true } |
3615 | + } |
3616 | + Signal { name: "pressed" } |
3617 | + } |
3618 | +} |
3619 | |
3620 | === added file 'src/app/unity8/plugins/Ubuntu/Gestures/PressedOutsideNotifier.cpp' |
3621 | --- src/app/unity8/plugins/Ubuntu/Gestures/PressedOutsideNotifier.cpp 1970-01-01 00:00:00 +0000 |
3622 | +++ src/app/unity8/plugins/Ubuntu/Gestures/PressedOutsideNotifier.cpp 2015-03-20 16:26:56 +0000 |
3623 | @@ -0,0 +1,119 @@ |
3624 | +/* |
3625 | + * Copyright (C) 2013 Canonical, Ltd. |
3626 | + * |
3627 | + * This program is free software; you can redistribute it and/or modify |
3628 | + * it under the terms of the GNU General Public License as published by |
3629 | + * the Free Software Foundation; version 3. |
3630 | + * |
3631 | + * This program is distributed in the hope that it will be useful, |
3632 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
3633 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3634 | + * GNU General Public License for more details. |
3635 | + * |
3636 | + * You should have received a copy of the GNU General Public License |
3637 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
3638 | + */ |
3639 | + |
3640 | +#include "PressedOutsideNotifier.h" |
3641 | + |
3642 | +#include <QMouseEvent> |
3643 | + |
3644 | +PressedOutsideNotifier::PressedOutsideNotifier(QQuickItem *parent) |
3645 | + : QQuickItem(parent) |
3646 | +{ |
3647 | + connect(this, &QQuickItem::enabledChanged, |
3648 | + this, &PressedOutsideNotifier::setupOrTearDownEventFiltering); |
3649 | + |
3650 | + m_signalEmissionTimer.setSingleShot(true); |
3651 | + m_signalEmissionTimer.setInterval(0); // times out on the next iteration of the event loop |
3652 | + connect(&m_signalEmissionTimer, &QTimer::timeout, |
3653 | + this, &PressedOutsideNotifier::pressedOutside); |
3654 | +} |
3655 | + |
3656 | +bool PressedOutsideNotifier::eventFilter(QObject *watched, QEvent *event) |
3657 | +{ |
3658 | + Q_UNUSED(watched); |
3659 | + Q_ASSERT(watched == m_filteredWindow); |
3660 | + |
3661 | + // We are already going to emit pressedOutside() anyway, thus no need |
3662 | + // for new checks. |
3663 | + // This case takes place when a QTouchEvent comes in and isn't handled by any item, |
3664 | + // causing QQuickWindow to synthesize a QMouseEvent out of it, which would |
3665 | + // be filtered by us as well and count as a second press, which is wrong. |
3666 | + if (m_signalEmissionTimer.isActive()) { |
3667 | + return false; |
3668 | + } |
3669 | + |
3670 | + switch (event->type()) { |
3671 | + case QEvent::MouseButtonPress: { |
3672 | + QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event); |
3673 | + QPointF p = mapFromScene(mouseEvent->windowPos()); |
3674 | + if (!contains(p)) { |
3675 | + m_signalEmissionTimer.start(); |
3676 | + } |
3677 | + break; |
3678 | + } |
3679 | + case QEvent::TouchBegin: |
3680 | + processFilteredTouchBegin(static_cast<QTouchEvent*>(event)); |
3681 | + default: |
3682 | + break; |
3683 | + } |
3684 | + |
3685 | + // let the event be handled further |
3686 | + return false; |
3687 | +} |
3688 | + |
3689 | +void PressedOutsideNotifier::itemChange(ItemChange change, const ItemChangeData &value) |
3690 | +{ |
3691 | + if (change == QQuickItem::ItemSceneChange) { |
3692 | + setupOrTearDownEventFiltering(); |
3693 | + } |
3694 | + |
3695 | + QQuickItem::itemChange(change, value); |
3696 | +} |
3697 | + |
3698 | +void PressedOutsideNotifier::setupOrTearDownEventFiltering() |
3699 | +{ |
3700 | + if (isEnabled() && window()) { |
3701 | + setupEventFiltering(); |
3702 | + } else if (m_filteredWindow) { |
3703 | + tearDownEventFiltering(); |
3704 | + } |
3705 | +} |
3706 | + |
3707 | +void PressedOutsideNotifier::setupEventFiltering() |
3708 | +{ |
3709 | + QQuickWindow *currentWindow = window(); |
3710 | + Q_ASSERT(currentWindow != nullptr); |
3711 | + |
3712 | + if (currentWindow == m_filteredWindow) |
3713 | + return; |
3714 | + |
3715 | + if (m_filteredWindow) { |
3716 | + m_filteredWindow->removeEventFilter(this); |
3717 | + } |
3718 | + |
3719 | + currentWindow->installEventFilter(this); |
3720 | + m_filteredWindow = currentWindow; |
3721 | +} |
3722 | + |
3723 | +void PressedOutsideNotifier::tearDownEventFiltering() |
3724 | +{ |
3725 | + m_filteredWindow->removeEventFilter(this); |
3726 | + m_filteredWindow.clear(); |
3727 | +} |
3728 | + |
3729 | +void PressedOutsideNotifier::processFilteredTouchBegin(QTouchEvent *event) |
3730 | +{ |
3731 | + const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints(); |
3732 | + for (int i = 0; i < touchPoints.count(); ++i) { |
3733 | + const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i); |
3734 | + if (touchPoint.state() == Qt::TouchPointPressed) { |
3735 | + QPointF p = mapFromScene(touchPoint.pos()); |
3736 | + if (!contains(p)) { |
3737 | + m_signalEmissionTimer.start(); |
3738 | + return; |
3739 | + } |
3740 | + } |
3741 | + } |
3742 | +} |
3743 | |
3744 | === added file 'src/app/unity8/plugins/Ubuntu/Gestures/PressedOutsideNotifier.h' |
3745 | --- src/app/unity8/plugins/Ubuntu/Gestures/PressedOutsideNotifier.h 1970-01-01 00:00:00 +0000 |
3746 | +++ src/app/unity8/plugins/Ubuntu/Gestures/PressedOutsideNotifier.h 2015-03-20 16:26:56 +0000 |
3747 | @@ -0,0 +1,62 @@ |
3748 | +/* |
3749 | + * Copyright (C) 2013 Canonical, Ltd. |
3750 | + * |
3751 | + * This program is free software; you can redistribute it and/or modify |
3752 | + * it under the terms of the GNU General Public License as published by |
3753 | + * the Free Software Foundation; version 3. |
3754 | + * |
3755 | + * This program is distributed in the hope that it will be useful, |
3756 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
3757 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3758 | + * GNU General Public License for more details. |
3759 | + * |
3760 | + * You should have received a copy of the GNU General Public License |
3761 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
3762 | + */ |
3763 | + |
3764 | +#ifndef PRESSED_OUTSIDE_NOTIFIER_H |
3765 | +#define PRESSED_OUTSIDE_NOTIFIER_H |
3766 | + |
3767 | +#include <QQuickItem> |
3768 | + |
3769 | +#include <QQuickWindow> |
3770 | +#include <QPointer> |
3771 | +#include <QTimer> |
3772 | + |
3773 | +#include "UbuntuGesturesGlobal.h" |
3774 | + |
3775 | +/* |
3776 | + Notifies when a point, mouse or touch, is pressed outside its area. |
3777 | + |
3778 | + Only enable it while needed. |
3779 | + */ |
3780 | +class UBUNTUGESTURES_EXPORT PressedOutsideNotifier : public QQuickItem { |
3781 | + Q_OBJECT |
3782 | + |
3783 | +public: |
3784 | + PressedOutsideNotifier(QQuickItem * parent = nullptr); |
3785 | + |
3786 | + // From QObject |
3787 | + bool eventFilter(QObject *watched, QEvent *event) override; |
3788 | + |
3789 | +Q_SIGNALS: |
3790 | + void pressedOutside(); |
3791 | + |
3792 | +protected: |
3793 | + void itemChange(ItemChange change, const ItemChangeData &value) override; |
3794 | + |
3795 | +private Q_SLOTS: |
3796 | + void setupOrTearDownEventFiltering(); |
3797 | + |
3798 | +private: |
3799 | + void setupEventFiltering(); |
3800 | + void tearDownEventFiltering(); |
3801 | + void processFilteredTouchBegin(QTouchEvent *event); |
3802 | + |
3803 | + QPointer<QQuickWindow> m_filteredWindow; |
3804 | + |
3805 | + // Emits pressedOutside() signal on timeout |
3806 | + QTimer m_signalEmissionTimer; |
3807 | +}; |
3808 | + |
3809 | +#endif // PRESSED_OUTSIDE_NOTIFIER_H |
3810 | |
3811 | === added file 'src/app/unity8/plugins/Ubuntu/Gestures/TimeSource.cpp' |
3812 | --- src/app/unity8/plugins/Ubuntu/Gestures/TimeSource.cpp 1970-01-01 00:00:00 +0000 |
3813 | +++ src/app/unity8/plugins/Ubuntu/Gestures/TimeSource.cpp 2015-03-20 16:26:56 +0000 |
3814 | @@ -0,0 +1,49 @@ |
3815 | +/* |
3816 | + * Copyright (C) 2013 - Canonical Ltd. |
3817 | + * |
3818 | + * This program is free software: you can redistribute it and/or modify it |
3819 | + * under the terms of the GNU Lesser General Public License, as |
3820 | + * published by the Free Software Foundation; either version 2.1 or 3.0 |
3821 | + * of the License. |
3822 | + * |
3823 | + * This program is distributed in the hope that it will be useful, but |
3824 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
3825 | + * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR |
3826 | + * PURPOSE. See the applicable version of the GNU Lesser General Public |
3827 | + * License for more details. |
3828 | + * |
3829 | + * You should have received a copy of both the GNU Lesser General Public |
3830 | + * License along with this program. If not, see <http://www.gnu.org/licenses/> |
3831 | + * |
3832 | + * Authored by: Daniel d'Andrada <daniel.dandrada@canonical.com> |
3833 | + */ |
3834 | + |
3835 | +#include "TimeSource.h" |
3836 | + |
3837 | +#include <QElapsedTimer> |
3838 | + |
3839 | +namespace UbuntuGestures { |
3840 | +class RealTimeSourcePrivate { |
3841 | +public: |
3842 | + QElapsedTimer timer; |
3843 | +}; |
3844 | +} |
3845 | + |
3846 | +using namespace UbuntuGestures; |
3847 | + |
3848 | +RealTimeSource::RealTimeSource() |
3849 | + : UbuntuGestures::TimeSource() |
3850 | + , d(new RealTimeSourcePrivate) |
3851 | +{ |
3852 | + d->timer.start(); |
3853 | +} |
3854 | + |
3855 | +RealTimeSource::~RealTimeSource() |
3856 | +{ |
3857 | + delete d; |
3858 | +} |
3859 | + |
3860 | +qint64 RealTimeSource::msecsSinceReference() |
3861 | +{ |
3862 | + return d->timer.elapsed(); |
3863 | +} |
3864 | |
3865 | === added file 'src/app/unity8/plugins/Ubuntu/Gestures/TimeSource.h' |
3866 | --- src/app/unity8/plugins/Ubuntu/Gestures/TimeSource.h 1970-01-01 00:00:00 +0000 |
3867 | +++ src/app/unity8/plugins/Ubuntu/Gestures/TimeSource.h 2015-03-20 16:26:56 +0000 |
3868 | @@ -0,0 +1,54 @@ |
3869 | +/* |
3870 | + * Copyright (C) 2013 - Canonical Ltd. |
3871 | + * |
3872 | + * This program is free software: you can redistribute it and/or modify it |
3873 | + * under the terms of the GNU Lesser General Public License, as |
3874 | + * published by the Free Software Foundation; either version 2.1 or 3.0 |
3875 | + * of the License. |
3876 | + * |
3877 | + * This program is distributed in the hope that it will be useful, but |
3878 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
3879 | + * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR |
3880 | + * PURPOSE. See the applicable version of the GNU Lesser General Public |
3881 | + * License for more details. |
3882 | + * |
3883 | + * You should have received a copy of both the GNU Lesser General Public |
3884 | + * License along with this program. If not, see <http://www.gnu.org/licenses/> |
3885 | + * |
3886 | + * Authored by: Daniel d'Andrada <daniel.dandrada@canonical.com> |
3887 | + */ |
3888 | + |
3889 | +#ifndef UBUNTUGESTURES_TIMESOURCE_H |
3890 | +#define UBUNTUGESTURES_TIMESOURCE_H |
3891 | + |
3892 | +#include "UbuntuGesturesQmlGlobal.h" |
3893 | +#include <QSharedPointer> |
3894 | + |
3895 | +namespace UbuntuGestures { |
3896 | +/* |
3897 | + Interface for a time source. |
3898 | + */ |
3899 | +class UBUNTUGESTURESQML_EXPORT TimeSource { |
3900 | +public: |
3901 | + virtual ~TimeSource() {} |
3902 | + /* Returns the current time in milliseconds since some reference time in the past. */ |
3903 | + virtual qint64 msecsSinceReference() = 0; |
3904 | +}; |
3905 | +typedef QSharedPointer<TimeSource> SharedTimeSource; |
3906 | + |
3907 | +/* |
3908 | + Implementation of a time source |
3909 | + */ |
3910 | +class RealTimeSourcePrivate; |
3911 | +class RealTimeSource : public TimeSource { |
3912 | +public: |
3913 | + RealTimeSource(); |
3914 | + virtual ~RealTimeSource(); |
3915 | + qint64 msecsSinceReference() override; |
3916 | +private: |
3917 | + RealTimeSourcePrivate *d; |
3918 | +}; |
3919 | + |
3920 | +} // namespace UbuntuGestures |
3921 | + |
3922 | +#endif // UBUNTUGESTURES_TIMESOURCE_H |
3923 | |
3924 | === added file 'src/app/unity8/plugins/Ubuntu/Gestures/TouchDispatcher.cpp' |
3925 | --- src/app/unity8/plugins/Ubuntu/Gestures/TouchDispatcher.cpp 1970-01-01 00:00:00 +0000 |
3926 | +++ src/app/unity8/plugins/Ubuntu/Gestures/TouchDispatcher.cpp 2015-03-20 16:26:56 +0000 |
3927 | @@ -0,0 +1,367 @@ |
3928 | +/* |
3929 | + * Copyright (C) 2014 Canonical, Ltd. |
3930 | + * |
3931 | + * This program is free software; you can redistribute it and/or modify |
3932 | + * it under the terms of the GNU General Public License as published by |
3933 | + * the Free Software Foundation; version 3. |
3934 | + * |
3935 | + * This program is distributed in the hope that it will be useful, |
3936 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
3937 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3938 | + * GNU General Public License for more details. |
3939 | + * |
3940 | + * You should have received a copy of the GNU General Public License |
3941 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
3942 | + */ |
3943 | + |
3944 | +#include "TouchDispatcher.h" |
3945 | + |
3946 | +#include <QGuiApplication> |
3947 | +#include <QScopedPointer> |
3948 | +#include <QStyleHints> |
3949 | + |
3950 | +#pragma GCC diagnostic push |
3951 | +#pragma GCC diagnostic ignored "-pedantic" |
3952 | +#include <private/qquickitem_p.h> |
3953 | +#pragma GCC diagnostic pop |
3954 | + |
3955 | +#define TOUCHDISPATCHER_DEBUG 0 |
3956 | + |
3957 | +#if TOUCHDISPATCHER_DEBUG |
3958 | +#include <DebugHelpers.h> |
3959 | +#endif |
3960 | + |
3961 | +TouchDispatcher::TouchDispatcher() |
3962 | + : m_status(NoActiveTouch) |
3963 | + , m_touchMouseId(-1) |
3964 | + , m_touchMousePressTimestamp(0) |
3965 | +{ |
3966 | +} |
3967 | + |
3968 | +void TouchDispatcher::setTargetItem(QQuickItem *target) |
3969 | +{ |
3970 | + if (target != m_targetItem) { |
3971 | + m_targetItem = target; |
3972 | + if (m_status != NoActiveTouch) { |
3973 | + qWarning("[TouchDispatcher] Changing target item in the middle of a touch stream"); |
3974 | + m_status = TargetRejectedTouches; |
3975 | + } |
3976 | + } |
3977 | +} |
3978 | + |
3979 | +void TouchDispatcher::dispatch(QEvent::Type eventType, |
3980 | + QTouchDevice *device, |
3981 | + Qt::KeyboardModifiers modifiers, |
3982 | + const QList<QTouchEvent::TouchPoint> &touchPoints, |
3983 | + QWindow *window, |
3984 | + ulong timestamp) |
3985 | +{ |
3986 | + if (m_targetItem.isNull()) { |
3987 | + qWarning("[TouchDispatcher] Cannot dispatch touch event because target item is null"); |
3988 | + return; |
3989 | + } |
3990 | + |
3991 | + if (eventType == QEvent::TouchBegin) { |
3992 | + dispatchTouchBegin(device, modifiers, touchPoints, window, timestamp); |
3993 | + |
3994 | + } else if (eventType == QEvent::TouchUpdate || eventType == QEvent::TouchEnd) { |
3995 | + |
3996 | + if (m_status == DeliveringTouchEvents) { |
3997 | + dispatchAsTouch(eventType, device, modifiers, touchPoints, window, timestamp); |
3998 | + } else if (m_status == DeliveringMouseEvents) { |
3999 | + dispatchAsMouse(device, modifiers, touchPoints, timestamp); |
4000 | + } else { |
4001 | + Q_ASSERT(m_status == TargetRejectedTouches); |
4002 | + #if TOUCHDISPATCHER_DEBUG |
4003 | + qDebug() << "[TouchDispatcher] Not dispatching touch event to" << m_targetItem.data() |
4004 | + << "because it already rejected the touch stream."; |
4005 | + #endif |
4006 | + // Do nothing |
4007 | + } |
4008 | + |
4009 | + if (eventType == QEvent::TouchEnd) { |
4010 | + m_status = NoActiveTouch; |
4011 | + m_touchMouseId = -1; |
4012 | + } |
4013 | + |
4014 | + } else { |
4015 | + // Should never happen |
4016 | + qCritical() << "[TouchDispatcher] Unexpected event type" << eventType; |
4017 | + Q_ASSERT(false); |
4018 | + return; |
4019 | + } |
4020 | +} |
4021 | + |
4022 | +void TouchDispatcher::dispatchTouchBegin( |
4023 | + QTouchDevice *device, |
4024 | + Qt::KeyboardModifiers modifiers, |
4025 | + const QList<QTouchEvent::TouchPoint> &touchPoints, |
4026 | + QWindow *window, |
4027 | + ulong timestamp) |
4028 | +{ |
4029 | + Q_ASSERT(m_status == NoActiveTouch); |
4030 | + QQuickItem *targetItem = m_targetItem.data(); |
4031 | + |
4032 | + if (!targetItem->isEnabled() || !targetItem->isVisible()) { |
4033 | + #if TOUCHDISPATCHER_DEBUG |
4034 | + qDebug() << "[TouchDispatcher] Cannot dispatch touch event to" << targetItem |
4035 | + << "because it's disabled or invisible."; |
4036 | + #endif |
4037 | + return; |
4038 | + } |
4039 | + |
4040 | + // Map touch points to targetItem coordinates |
4041 | + QList<QTouchEvent::TouchPoint> targetTouchPoints = touchPoints; |
4042 | + transformTouchPoints(targetTouchPoints, QQuickItemPrivate::get(targetItem)->windowToItemTransform()); |
4043 | + |
4044 | + QScopedPointer<QTouchEvent> touchEvent( |
4045 | + createQTouchEvent(QEvent::TouchBegin, device, modifiers, targetTouchPoints, window, timestamp)); |
4046 | + |
4047 | + |
4048 | + #if TOUCHDISPATCHER_DEBUG |
4049 | + qDebug() << "[TouchDispatcher] dispatching" << qPrintable(touchEventToString(touchEvent.data())) |
4050 | + << "to" << targetItem; |
4051 | + #endif |
4052 | + QCoreApplication::sendEvent(targetItem, touchEvent.data()); |
4053 | + |
4054 | + |
4055 | + if (touchEvent->isAccepted()) { |
4056 | + #if TOUCHDISPATCHER_DEBUG |
4057 | + qDebug() << "[TouchDispatcher] Item accepted the touch event."; |
4058 | + #endif |
4059 | + m_status = DeliveringTouchEvents; |
4060 | + } else if (targetItem->acceptedMouseButtons() & Qt::LeftButton) { |
4061 | + #if TOUCHDISPATCHER_DEBUG |
4062 | + qDebug() << "[TouchDispatcher] Item rejected the touch event. Trying a QMouseEvent"; |
4063 | + #endif |
4064 | + // NB: Arbitrarily chose the first touch point to emulate the mouse pointer |
4065 | + QScopedPointer<QMouseEvent> mouseEvent( |
4066 | + touchToMouseEvent(QEvent::MouseButtonPress, targetTouchPoints.at(0), timestamp, |
4067 | + modifiers, false /* transformNeeded */)); |
4068 | + Q_ASSERT(targetTouchPoints.at(0).state() == Qt::TouchPointPressed); |
4069 | + |
4070 | + #if TOUCHDISPATCHER_DEBUG |
4071 | + qDebug() << "[TouchDispatcher] dispatching" << qPrintable(mouseEventToString(mouseEvent.data())) |
4072 | + << "to" << m_targetItem.data(); |
4073 | + #endif |
4074 | + QCoreApplication::sendEvent(targetItem, mouseEvent.data()); |
4075 | + if (mouseEvent->isAccepted()) { |
4076 | + #if TOUCHDISPATCHER_DEBUG |
4077 | + qDebug() << "[TouchDispatcher] Item accepted the QMouseEvent."; |
4078 | + #endif |
4079 | + m_status = DeliveringMouseEvents; |
4080 | + m_touchMouseId = targetTouchPoints.at(0).id(); |
4081 | + |
4082 | + if (checkIfDoubleClicked(timestamp)) { |
4083 | + QScopedPointer<QMouseEvent> doubleClickEvent( |
4084 | + touchToMouseEvent(QEvent::MouseButtonDblClick, targetTouchPoints.at(0), timestamp, |
4085 | + modifiers, false /* transformNeeded */)); |
4086 | + #if TOUCHDISPATCHER_DEBUG |
4087 | + qDebug() << "[TouchDispatcher] dispatching" << qPrintable(mouseEventToString(doubleClickEvent.data())) |
4088 | + << "to" << m_targetItem.data(); |
4089 | + #endif |
4090 | + QCoreApplication::sendEvent(targetItem, doubleClickEvent.data()); |
4091 | + } |
4092 | + |
4093 | + } else { |
4094 | + #if TOUCHDISPATCHER_DEBUG |
4095 | + qDebug() << "[TouchDispatcher] Item rejected the QMouseEvent."; |
4096 | + #endif |
4097 | + m_status = TargetRejectedTouches; |
4098 | + } |
4099 | + } else { |
4100 | + #if TOUCHDISPATCHER_DEBUG |
4101 | + qDebug() << "[TouchDispatcher] Item rejected the touch event and does not accept mouse buttons."; |
4102 | + #endif |
4103 | + m_status = TargetRejectedTouches; |
4104 | + } |
4105 | +} |
4106 | + |
4107 | +void TouchDispatcher::dispatchAsTouch(QEvent::Type eventType, |
4108 | + QTouchDevice *device, |
4109 | + Qt::KeyboardModifiers modifiers, |
4110 | + const QList<QTouchEvent::TouchPoint> &touchPoints, |
4111 | + QWindow *window, |
4112 | + ulong timestamp) |
4113 | +{ |
4114 | + QQuickItem *targetItem = m_targetItem.data(); |
4115 | + |
4116 | + // Map touch points to targetItem coordinates |
4117 | + QList<QTouchEvent::TouchPoint> targetTouchPoints = touchPoints; |
4118 | + transformTouchPoints(targetTouchPoints, QQuickItemPrivate::get(targetItem)->windowToItemTransform()); |
4119 | + |
4120 | + QScopedPointer<QTouchEvent> eventForTargetItem( |
4121 | + createQTouchEvent(eventType, device, modifiers, targetTouchPoints, window, timestamp)); |
4122 | + |
4123 | + |
4124 | + #if TOUCHDISPATCHER_DEBUG |
4125 | + qDebug() << "[TouchDispatcher] dispatching" << qPrintable(touchEventToString(eventForTargetItem.data())) |
4126 | + << "to" << targetItem; |
4127 | + #endif |
4128 | + QCoreApplication::sendEvent(targetItem, eventForTargetItem.data()); |
4129 | +} |
4130 | + |
4131 | +void TouchDispatcher::dispatchAsMouse( |
4132 | + QTouchDevice * /*device*/, |
4133 | + Qt::KeyboardModifiers modifiers, |
4134 | + const QList<QTouchEvent::TouchPoint> &touchPoints, |
4135 | + ulong timestamp) |
4136 | +{ |
4137 | + // TODO: Detect double clicks in order to synthesize QEvent::MouseButtonDblClick events accordingly |
4138 | + |
4139 | + Q_ASSERT(!touchPoints.isEmpty()); |
4140 | + |
4141 | + const QTouchEvent::TouchPoint *touchMouse = nullptr; |
4142 | + |
4143 | + if (m_touchMouseId != -1) { |
4144 | + for (int i = 0; i < touchPoints.count() && !touchMouse; ++i) { |
4145 | + const auto &touchPoint = touchPoints.at(i); |
4146 | + if (touchPoint.id() == m_touchMouseId) { |
4147 | + touchMouse = &touchPoint; |
4148 | + } |
4149 | + } |
4150 | + |
4151 | + Q_ASSERT(touchMouse); |
4152 | + if (!touchMouse) { |
4153 | + // should not happen, but deal with it just in case. |
4154 | + qWarning("[TouchDispatcher] Didn't find touch with id %d, used for mouse pointer emulation.", |
4155 | + m_touchMouseId); |
4156 | + m_touchMouseId = touchPoints.at(0).id(); |
4157 | + touchMouse = &touchPoints.at(0); |
4158 | + } |
4159 | + } else { |
4160 | + // Try to find a new touch for mouse emulation |
4161 | + for (int i = 0; i < touchPoints.count() && !touchMouse; ++i) { |
4162 | + const auto &touchPoint = touchPoints.at(i); |
4163 | + if (touchPoint.state() == Qt::TouchPointPressed) { |
4164 | + touchMouse = &touchPoint; |
4165 | + m_touchMouseId = touchMouse->id(); |
4166 | + } |
4167 | + } |
4168 | + } |
4169 | + |
4170 | + if (touchMouse) { |
4171 | + QEvent::Type eventType; |
4172 | + if (touchMouse->state() == Qt::TouchPointPressed) { |
4173 | + eventType = QEvent::MouseButtonPress; |
4174 | + } if (touchMouse->state() == Qt::TouchPointReleased) { |
4175 | + eventType = QEvent::MouseButtonRelease; |
4176 | + m_touchMouseId = -1; |
4177 | + } else { |
4178 | + eventType = QEvent::MouseMove; |
4179 | + } |
4180 | + |
4181 | + QScopedPointer<QMouseEvent> mouseEvent(touchToMouseEvent(eventType, *touchMouse, timestamp, modifiers, |
4182 | + true /* transformNeeded */)); |
4183 | + |
4184 | + #if TOUCHDISPATCHER_DEBUG |
4185 | + qDebug() << "[TouchDispatcher] dispatching" << qPrintable(mouseEventToString(mouseEvent.data())) |
4186 | + << "to" << m_targetItem.data(); |
4187 | + #endif |
4188 | + QCoreApplication::sendEvent(m_targetItem.data(), mouseEvent.data()); |
4189 | + } |
4190 | +} |
4191 | + |
4192 | +QTouchEvent *TouchDispatcher::createQTouchEvent(QEvent::Type eventType, |
4193 | + QTouchDevice *device, |
4194 | + Qt::KeyboardModifiers modifiers, |
4195 | + const QList<QTouchEvent::TouchPoint> &touchPoints, |
4196 | + QWindow *window, |
4197 | + ulong timestamp) |
4198 | +{ |
4199 | + Qt::TouchPointStates eventStates = 0; |
4200 | + for (int i = 0; i < touchPoints.count(); i++) |
4201 | + eventStates |= touchPoints[i].state(); |
4202 | + // if all points have the same state, set the event type accordingly |
4203 | + switch (eventStates) { |
4204 | + case Qt::TouchPointPressed: |
4205 | + eventType = QEvent::TouchBegin; |
4206 | + break; |
4207 | + case Qt::TouchPointReleased: |
4208 | + eventType = QEvent::TouchEnd; |
4209 | + break; |
4210 | + default: |
4211 | + eventType = QEvent::TouchUpdate; |
4212 | + break; |
4213 | + } |
4214 | + |
4215 | + QTouchEvent *touchEvent = new QTouchEvent(eventType); |
4216 | + touchEvent->setWindow(window); |
4217 | + touchEvent->setTarget(m_targetItem.data()); |
4218 | + touchEvent->setDevice(device); |
4219 | + touchEvent->setModifiers(modifiers); |
4220 | + touchEvent->setTouchPoints(touchPoints); |
4221 | + touchEvent->setTouchPointStates(eventStates); |
4222 | + touchEvent->setTimestamp(timestamp); |
4223 | + touchEvent->accept(); |
4224 | + return touchEvent; |
4225 | +} |
4226 | + |
4227 | +// NB: From QQuickWindow |
4228 | +void TouchDispatcher::transformTouchPoints(QList<QTouchEvent::TouchPoint> &touchPoints, const QTransform &transform) |
4229 | +{ |
4230 | + QMatrix4x4 transformMatrix(transform); |
4231 | + for (int i=0; i<touchPoints.count(); i++) { |
4232 | + QTouchEvent::TouchPoint &touchPoint = touchPoints[i]; |
4233 | + touchPoint.setRect(transform.mapRect(touchPoint.sceneRect())); |
4234 | + touchPoint.setStartPos(transform.map(touchPoint.startScenePos())); |
4235 | + touchPoint.setLastPos(transform.map(touchPoint.lastScenePos())); |
4236 | + touchPoint.setVelocity(transformMatrix.mapVector(touchPoint.velocity()).toVector2D()); |
4237 | + } |
4238 | +} |
4239 | + |
4240 | +// Copied with minor modifications from qtdeclarative/src/quick/items/qquickwindow.cpp |
4241 | +QMouseEvent *TouchDispatcher::touchToMouseEvent( |
4242 | + QEvent::Type type, const QTouchEvent::TouchPoint &p, |
4243 | + ulong timestamp, Qt::KeyboardModifiers modifiers, |
4244 | + bool transformNeeded) |
4245 | +{ |
4246 | + QQuickItem *item = m_targetItem.data(); |
4247 | + |
4248 | + // The touch point local position and velocity are not yet transformed. |
4249 | + QMouseEvent *me = new QMouseEvent(type, transformNeeded ? item->mapFromScene(p.scenePos()) : p.pos(), |
4250 | + p.scenePos(), p.screenPos(), Qt::LeftButton, |
4251 | + (type == QEvent::MouseButtonRelease ? Qt::NoButton : Qt::LeftButton), |
4252 | + modifiers); |
4253 | + me->setAccepted(true); |
4254 | + me->setTimestamp(timestamp); |
4255 | + QVector2D transformedVelocity = p.velocity(); |
4256 | + if (transformNeeded) { |
4257 | + QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); |
4258 | + QMatrix4x4 transformMatrix(itemPrivate->windowToItemTransform()); |
4259 | + transformedVelocity = transformMatrix.mapVector(p.velocity()).toVector2D(); |
4260 | + } |
4261 | + |
4262 | + // Add these later if needed: |
4263 | + //QGuiApplicationPrivate::setMouseEventCapsAndVelocity(me, event->device()->capabilities(), transformedVelocity); |
4264 | + //QGuiApplicationPrivate::setMouseEventSource(me, Qt::MouseEventSynthesizedByQt); |
4265 | + return me; |
4266 | +} |
4267 | + |
4268 | +/* |
4269 | + Copied from qquickwindow.cpp which has: |
4270 | + Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies) |
4271 | + Under GPL 3.0 license. |
4272 | +*/ |
4273 | +bool TouchDispatcher::checkIfDoubleClicked(ulong newPressEventTimestamp) |
4274 | +{ |
4275 | + bool doubleClicked; |
4276 | + |
4277 | + if (m_touchMousePressTimestamp == 0) { |
4278 | + // just initialize the variable |
4279 | + m_touchMousePressTimestamp = newPressEventTimestamp; |
4280 | + doubleClicked = false; |
4281 | + } else { |
4282 | + ulong timeBetweenPresses = newPressEventTimestamp - m_touchMousePressTimestamp; |
4283 | + ulong doubleClickInterval = static_cast<ulong>(qApp->styleHints()-> |
4284 | + mouseDoubleClickInterval()); |
4285 | + doubleClicked = timeBetweenPresses < doubleClickInterval; |
4286 | + if (doubleClicked) { |
4287 | + m_touchMousePressTimestamp = 0; |
4288 | + } else { |
4289 | + m_touchMousePressTimestamp = newPressEventTimestamp; |
4290 | + } |
4291 | + } |
4292 | + |
4293 | + return doubleClicked; |
4294 | +} |
4295 | |
4296 | === added file 'src/app/unity8/plugins/Ubuntu/Gestures/TouchDispatcher.h' |
4297 | --- src/app/unity8/plugins/Ubuntu/Gestures/TouchDispatcher.h 1970-01-01 00:00:00 +0000 |
4298 | +++ src/app/unity8/plugins/Ubuntu/Gestures/TouchDispatcher.h 2015-03-20 16:26:56 +0000 |
4299 | @@ -0,0 +1,89 @@ |
4300 | +/* |
4301 | + * Copyright (C) 2014 Canonical, Ltd. |
4302 | + * |
4303 | + * This program is free software; you can redistribute it and/or modify |
4304 | + * it under the terms of the GNU General Public License as published by |
4305 | + * the Free Software Foundation; version 3. |
4306 | + * |
4307 | + * This program is distributed in the hope that it will be useful, |
4308 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
4309 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
4310 | + * GNU General Public License for more details. |
4311 | + * |
4312 | + * You should have received a copy of the GNU General Public License |
4313 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
4314 | + */ |
4315 | + |
4316 | +#ifndef UBUNTU_TOUCH_DISPATCHER_H |
4317 | +#define UBUNTU_TOUCH_DISPATCHER_H |
4318 | + |
4319 | +#include "UbuntuGesturesQmlGlobal.h" |
4320 | + |
4321 | +#include <QPointer> |
4322 | +#include <QQuickItem> |
4323 | + |
4324 | +/* |
4325 | + Dispatches touches to the given target, converting the touch point |
4326 | + coordinates accordingly. |
4327 | + |
4328 | + Also takes care of synthesizing mouse events in case the target |
4329 | + doesn't work with touch events. |
4330 | + */ |
4331 | +class UBUNTUGESTURESQML_EXPORT TouchDispatcher { |
4332 | +public: |
4333 | + TouchDispatcher(); |
4334 | + |
4335 | + void setTargetItem(QQuickItem *target); |
4336 | + QQuickItem *targetItem() { return m_targetItem; } |
4337 | + |
4338 | + void dispatch(QEvent::Type eventType, |
4339 | + QTouchDevice *device, |
4340 | + Qt::KeyboardModifiers modifiers, |
4341 | + const QList<QTouchEvent::TouchPoint> &touchPoints, |
4342 | + QWindow *window, |
4343 | + ulong timestamp); |
4344 | +private: |
4345 | + void dispatchTouchBegin( |
4346 | + QTouchDevice *device, |
4347 | + Qt::KeyboardModifiers modifiers, |
4348 | + const QList<QTouchEvent::TouchPoint> &touchPoints, |
4349 | + QWindow *window, |
4350 | + ulong timestamp); |
4351 | + void dispatchAsTouch(QEvent::Type eventType, |
4352 | + QTouchDevice *device, |
4353 | + Qt::KeyboardModifiers modifiers, |
4354 | + const QList<QTouchEvent::TouchPoint> &touchPoints, |
4355 | + QWindow *window, |
4356 | + ulong timestamp); |
4357 | + void dispatchAsMouse( |
4358 | + QTouchDevice *device, |
4359 | + Qt::KeyboardModifiers modifiers, |
4360 | + const QList<QTouchEvent::TouchPoint> &touchPoints, |
4361 | + ulong timestamp); |
4362 | + |
4363 | + static void transformTouchPoints(QList<QTouchEvent::TouchPoint> &touchPoints, const QTransform &transform); |
4364 | + QTouchEvent *createQTouchEvent(QEvent::Type eventType, |
4365 | + QTouchDevice *device, |
4366 | + Qt::KeyboardModifiers modifiers, |
4367 | + const QList<QTouchEvent::TouchPoint> &touchPoints, |
4368 | + QWindow *window, |
4369 | + ulong timestamp); |
4370 | + QMouseEvent *touchToMouseEvent(QEvent::Type type, const QTouchEvent::TouchPoint &p, |
4371 | + ulong timestamp, Qt::KeyboardModifiers modifiers, bool transformNeeded = true); |
4372 | + |
4373 | + bool checkIfDoubleClicked(ulong newPressEventTimestamp); |
4374 | + |
4375 | + QPointer<QQuickItem> m_targetItem; |
4376 | + |
4377 | + enum { |
4378 | + NoActiveTouch, |
4379 | + DeliveringTouchEvents, |
4380 | + DeliveringMouseEvents, |
4381 | + TargetRejectedTouches |
4382 | + } m_status; |
4383 | + |
4384 | + int m_touchMouseId; |
4385 | + ulong m_touchMousePressTimestamp; |
4386 | +}; |
4387 | + |
4388 | +#endif // UBUNTU_TOUCH_DISPATCHER_H |
4389 | |
4390 | === added file 'src/app/unity8/plugins/Ubuntu/Gestures/TouchGate.cpp' |
4391 | --- src/app/unity8/plugins/Ubuntu/Gestures/TouchGate.cpp 1970-01-01 00:00:00 +0000 |
4392 | +++ src/app/unity8/plugins/Ubuntu/Gestures/TouchGate.cpp 2015-03-20 16:26:56 +0000 |
4393 | @@ -0,0 +1,253 @@ |
4394 | +/* |
4395 | + * Copyright (C) 2014 Canonical, Ltd. |
4396 | + * |
4397 | + * This program is free software; you can redistribute it and/or modify |
4398 | + * it under the terms of the GNU General Public License as published by |
4399 | + * the Free Software Foundation; version 3. |
4400 | + * |
4401 | + * This program is distributed in the hope that it will be useful, |
4402 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
4403 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
4404 | + * GNU General Public License for more details. |
4405 | + * |
4406 | + * You should have received a copy of the GNU General Public License |
4407 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
4408 | + */ |
4409 | + |
4410 | +#include "TouchGate.h" |
4411 | + |
4412 | +#include <QCoreApplication> |
4413 | +#include <QDebug> |
4414 | + |
4415 | +#include <TouchOwnershipEvent.h> |
4416 | +#include <TouchRegistry.h> |
4417 | + |
4418 | +#if TOUCHGATE_DEBUG |
4419 | +#include <DebugHelpers.h> |
4420 | +#endif |
4421 | + |
4422 | +bool TouchGate::event(QEvent *e) |
4423 | +{ |
4424 | + if (e->type() == TouchOwnershipEvent::touchOwnershipEventType()) { |
4425 | + touchOwnershipEvent(static_cast<TouchOwnershipEvent *>(e)); |
4426 | + return true; |
4427 | + } else { |
4428 | + return QQuickItem::event(e); |
4429 | + } |
4430 | +} |
4431 | + |
4432 | +void TouchGate::touchEvent(QTouchEvent *event) |
4433 | +{ |
4434 | + #if TOUCHGATE_DEBUG |
4435 | + qDebug() << "[TouchGate] got touch event" << qPrintable(touchEventToString(event)); |
4436 | + #endif |
4437 | + event->accept(); |
4438 | + |
4439 | + const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints(); |
4440 | + bool goodToGo = true; |
4441 | + for (int i = 0; i < touchPoints.count(); ++i) { |
4442 | + const QTouchEvent::TouchPoint &touchPoint = touchPoints[i]; |
4443 | + |
4444 | + if (touchPoint.state() == Qt::TouchPointPressed) { |
4445 | + Q_ASSERT(!m_touchInfoMap.contains(touchPoint.id())); |
4446 | + m_touchInfoMap[touchPoint.id()].ownership = OwnershipRequested; |
4447 | + m_touchInfoMap[touchPoint.id()].ended = false; |
4448 | + TouchRegistry::instance()->requestTouchOwnership(touchPoint.id(), this); |
4449 | + |
4450 | + Q_EMIT pressed(); |
4451 | + } |
4452 | + |
4453 | + goodToGo &= m_touchInfoMap.contains(touchPoint.id()) |
4454 | + && m_touchInfoMap[touchPoint.id()].ownership == OwnershipGranted; |
4455 | + |
4456 | + if (touchPoint.state() == Qt::TouchPointReleased && m_touchInfoMap.contains(touchPoint.id())) { |
4457 | + m_touchInfoMap[touchPoint.id()].ended = true; |
4458 | + } |
4459 | + |
4460 | + } |
4461 | + |
4462 | + if (goodToGo) { |
4463 | + if (m_storedEvents.isEmpty()) { |
4464 | + // let it pass through |
4465 | + dispatchTouchEventToTarget(event); |
4466 | + } else { |
4467 | + // Retain the event to ensure TouchGate dispatches them in order. |
4468 | + // Otherwise the current event would come before the stored ones, which are older. |
4469 | + #if TOUCHGATE_DEBUG |
4470 | + qDebug("[TouchGate] Storing event because thouches %s are still pending ownership.", |
4471 | + qPrintable(oldestPendingTouchIdsString())); |
4472 | + #endif |
4473 | + storeTouchEvent(event); |
4474 | + } |
4475 | + } else { |
4476 | + // Retain events that have unowned touches |
4477 | + storeTouchEvent(event); |
4478 | + } |
4479 | +} |
4480 | + |
4481 | +void TouchGate::touchOwnershipEvent(TouchOwnershipEvent *event) |
4482 | +{ |
4483 | + // TODO: Optimization: batch those actions as TouchOwnershipEvents |
4484 | + // might come one right after the other. |
4485 | + |
4486 | + Q_ASSERT(m_touchInfoMap.contains(event->touchId())); |
4487 | + |
4488 | + TouchInfo &touchInfo = m_touchInfoMap[event->touchId()]; |
4489 | + |
4490 | + if (event->gained()) { |
4491 | + #if TOUCHGATE_DEBUG |
4492 | + qDebug() << "[TouchGate] Got ownership of touch " << event->touchId(); |
4493 | + #endif |
4494 | + touchInfo.ownership = OwnershipGranted; |
4495 | + } else { |
4496 | + #if TOUCHGATE_DEBUG |
4497 | + qDebug() << "[TouchGate] Lost ownership of touch " << event->touchId(); |
4498 | + #endif |
4499 | + m_touchInfoMap.remove(event->touchId()); |
4500 | + removeTouchFromStoredEvents(event->touchId()); |
4501 | + } |
4502 | + |
4503 | + dispatchFullyOwnedEvents(); |
4504 | +} |
4505 | + |
4506 | +bool TouchGate::isTouchPointOwned(int touchId) const |
4507 | +{ |
4508 | + return m_touchInfoMap[touchId].ownership == OwnershipGranted; |
4509 | +} |
4510 | + |
4511 | +void TouchGate::storeTouchEvent(const QTouchEvent *event) |
4512 | +{ |
4513 | + #if TOUCHGATE_DEBUG |
4514 | + qDebug() << "[TouchGate] Storing" << qPrintable(touchEventToString(event)); |
4515 | + #endif |
4516 | + |
4517 | + TouchEvent clonedEvent(event); |
4518 | + m_storedEvents.append(std::move(clonedEvent)); |
4519 | +} |
4520 | + |
4521 | +void TouchGate::removeTouchFromStoredEvents(int touchId) |
4522 | +{ |
4523 | + int i = 0; |
4524 | + while (i < m_storedEvents.count()) { |
4525 | + TouchEvent &event = m_storedEvents[i]; |
4526 | + bool removed = event.removeTouch(touchId); |
4527 | + |
4528 | + if (removed && event.touchPoints.isEmpty()) { |
4529 | + m_storedEvents.removeAt(i); |
4530 | + } else { |
4531 | + ++i; |
4532 | + } |
4533 | + } |
4534 | +} |
4535 | + |
4536 | +void TouchGate::dispatchFullyOwnedEvents() |
4537 | +{ |
4538 | + while (!m_storedEvents.isEmpty() && eventIsFullyOwned(m_storedEvents.first())) { |
4539 | + TouchEvent event = m_storedEvents.takeFirst(); |
4540 | + dispatchTouchEventToTarget(event); |
4541 | + } |
4542 | +} |
4543 | + |
4544 | +#if TOUCHGATE_DEBUG |
4545 | +QString TouchGate::oldestPendingTouchIdsString() |
4546 | +{ |
4547 | + Q_ASSERT(!m_storedEvents.isEmpty()); |
4548 | + |
4549 | + QString str; |
4550 | + |
4551 | + const auto &touchPoints = m_storedEvents.first().touchPoints; |
4552 | + for (int i = 0; i < touchPoints.count(); ++i) { |
4553 | + if (!isTouchPointOwned(touchPoints[i].id())) { |
4554 | + if (!str.isEmpty()) { |
4555 | + str.append(", "); |
4556 | + } |
4557 | + str.append(QString::number(touchPoints[i].id())); |
4558 | + } |
4559 | + } |
4560 | + |
4561 | + return str; |
4562 | +} |
4563 | +#endif |
4564 | + |
4565 | +bool TouchGate::eventIsFullyOwned(const TouchGate::TouchEvent &event) const |
4566 | +{ |
4567 | + for (int i = 0; i < event.touchPoints.count(); ++i) { |
4568 | + if (!isTouchPointOwned(event.touchPoints[i].id())) { |
4569 | + return false; |
4570 | + } |
4571 | + } |
4572 | + |
4573 | + return true; |
4574 | +} |
4575 | + |
4576 | +void TouchGate::setTargetItem(QQuickItem *item) |
4577 | +{ |
4578 | + // TODO: changing the target item while dispatch of touch events is taking place will |
4579 | + // create a mess |
4580 | + |
4581 | + if (item == m_dispatcher.targetItem()) |
4582 | + return; |
4583 | + |
4584 | + m_dispatcher.setTargetItem(item); |
4585 | + Q_EMIT targetItemChanged(item); |
4586 | +} |
4587 | + |
4588 | +void TouchGate::dispatchTouchEventToTarget(const TouchEvent &event) |
4589 | +{ |
4590 | + removeTouchInfoForEndedTouches(event.touchPoints); |
4591 | + m_dispatcher.dispatch(event.eventType, |
4592 | + event.device, |
4593 | + event.modifiers, |
4594 | + event.touchPoints, |
4595 | + event.window, |
4596 | + event.timestamp); |
4597 | +} |
4598 | + |
4599 | +void TouchGate::dispatchTouchEventToTarget(QTouchEvent* event) |
4600 | +{ |
4601 | + removeTouchInfoForEndedTouches(event->touchPoints()); |
4602 | + m_dispatcher.dispatch(event->type(), |
4603 | + event->device(), |
4604 | + event->modifiers(), |
4605 | + event->touchPoints(), |
4606 | + event->window(), |
4607 | + event->timestamp()); |
4608 | +} |
4609 | + |
4610 | +void TouchGate::removeTouchInfoForEndedTouches(const QList<QTouchEvent::TouchPoint> &touchPoints) |
4611 | +{ |
4612 | + for (int i = 0; i < touchPoints.size(); ++i) {\ |
4613 | + const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i); |
4614 | + |
4615 | + if (touchPoint.state() == Qt::TouchPointReleased) { |
4616 | + Q_ASSERT(m_touchInfoMap.contains(touchPoint.id())); |
4617 | + Q_ASSERT(m_touchInfoMap[touchPoint.id()].ended); |
4618 | + Q_ASSERT(m_touchInfoMap[touchPoint.id()].ownership == OwnershipGranted); |
4619 | + m_touchInfoMap.remove(touchPoint.id()); |
4620 | + } |
4621 | + } |
4622 | +} |
4623 | + |
4624 | +TouchGate::TouchEvent::TouchEvent(const QTouchEvent *event) |
4625 | + : eventType(event->type()) |
4626 | + , device(event->device()) |
4627 | + , modifiers(event->modifiers()) |
4628 | + , touchPoints(event->touchPoints()) |
4629 | + , target(qobject_cast<QQuickItem*>(event->target())) |
4630 | + , window(event->window()) |
4631 | + , timestamp(event->timestamp()) |
4632 | +{ |
4633 | +} |
4634 | + |
4635 | +bool TouchGate::TouchEvent::removeTouch(int touchId) |
4636 | +{ |
4637 | + bool removed = false; |
4638 | + for (int i = 0; i < touchPoints.count() && !removed; ++i) { |
4639 | + if (touchPoints[i].id() == touchId) { |
4640 | + touchPoints.removeAt(i); |
4641 | + removed = true; |
4642 | + } |
4643 | + } |
4644 | + |
4645 | + return removed; |
4646 | +} |
4647 | |
4648 | === added file 'src/app/unity8/plugins/Ubuntu/Gestures/TouchGate.h' |
4649 | --- src/app/unity8/plugins/Ubuntu/Gestures/TouchGate.h 1970-01-01 00:00:00 +0000 |
4650 | +++ src/app/unity8/plugins/Ubuntu/Gestures/TouchGate.h 2015-03-20 16:26:56 +0000 |
4651 | @@ -0,0 +1,112 @@ |
4652 | +/* |
4653 | + * Copyright (C) 2014 Canonical, Ltd. |
4654 | + * |
4655 | + * This program is free software; you can redistribute it and/or modify |
4656 | + * it under the terms of the GNU General Public License as published by |
4657 | + * the Free Software Foundation; version 3. |
4658 | + * |
4659 | + * This program is distributed in the hope that it will be useful, |
4660 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
4661 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
4662 | + * GNU General Public License for more details. |
4663 | + * |
4664 | + * You should have received a copy of the GNU General Public License |
4665 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
4666 | + */ |
4667 | + |
4668 | +#ifndef UBUNTU_TOUCH_GATE_H |
4669 | +#define UBUNTU_TOUCH_GATE_H |
4670 | + |
4671 | +#include "UbuntuGesturesQmlGlobal.h" |
4672 | +#include "TouchDispatcher.h" |
4673 | + |
4674 | +#include <QQuickItem> |
4675 | +#include <QList> |
4676 | +#include <QMap> |
4677 | + |
4678 | +#define TOUCHGATE_DEBUG 0 |
4679 | + |
4680 | +class TouchOwnershipEvent; |
4681 | + |
4682 | +/* |
4683 | + Blocks the passage of events until ownership over the related touch points is granted. |
4684 | + |
4685 | + Blocked touch events won't be discarded. Instead they will be buffered until ownership |
4686 | + is granted. If ownership is given to another item, the event buffer is cleared. |
4687 | + |
4688 | + A TouchGate is useful as a mediator for items that do not understand, or gracefully handle, |
4689 | + touch canceling. By having a TouchGate in front of them you guarantee that only owned touches (i.e., |
4690 | + touches that won't be canceled later) reaches them. |
4691 | + */ |
4692 | +class UBUNTUGESTURESQML_EXPORT TouchGate : public QQuickItem { |
4693 | + Q_OBJECT |
4694 | + |
4695 | + // Item that's going to receive the touch events that make it through the gate. |
4696 | + Q_PROPERTY(QQuickItem* targetItem READ targetItem WRITE setTargetItem NOTIFY targetItemChanged) |
4697 | + |
4698 | +public: |
4699 | + bool event(QEvent *e) override; |
4700 | + |
4701 | + QQuickItem *targetItem() { return m_dispatcher.targetItem(); } |
4702 | + void setTargetItem(QQuickItem *item); |
4703 | + |
4704 | +Q_SIGNALS: |
4705 | + void targetItemChanged(QQuickItem *item); |
4706 | + |
4707 | + void pressed(); |
4708 | + |
4709 | +protected: |
4710 | + void touchEvent(QTouchEvent *event) override; |
4711 | +private: |
4712 | + class TouchEvent { |
4713 | + public: |
4714 | + TouchEvent(const QTouchEvent *event); |
4715 | + |
4716 | + bool removeTouch(int touchId); |
4717 | + |
4718 | + QEvent::Type eventType; |
4719 | + QTouchDevice *device; |
4720 | + Qt::KeyboardModifiers modifiers; |
4721 | + QList<QTouchEvent::TouchPoint> touchPoints; |
4722 | + QQuickItem *target; |
4723 | + QWindow *window; |
4724 | + ulong timestamp; |
4725 | + }; |
4726 | + |
4727 | + void touchOwnershipEvent(TouchOwnershipEvent *event); |
4728 | + bool isTouchPointOwned(int touchId) const; |
4729 | + void storeTouchEvent(const QTouchEvent *event); |
4730 | + void removeTouchFromStoredEvents(int touchId); |
4731 | + void dispatchFullyOwnedEvents(); |
4732 | + bool eventIsFullyOwned(const TouchEvent &event) const; |
4733 | + |
4734 | + void dispatchTouchEventToTarget(const TouchEvent &event); |
4735 | + void dispatchTouchEventToTarget(QTouchEvent* event); |
4736 | + |
4737 | + void removeTouchInfoForEndedTouches(const QList<QTouchEvent::TouchPoint> &touchPoints); |
4738 | + |
4739 | + #if TOUCHGATE_DEBUG |
4740 | + QString oldestPendingTouchIdsString(); |
4741 | + #endif |
4742 | + |
4743 | + QList<TouchEvent> m_storedEvents; |
4744 | + |
4745 | + enum { |
4746 | + OwnershipUndefined, |
4747 | + OwnershipRequested, |
4748 | + OwnershipGranted, |
4749 | + }; |
4750 | + class TouchInfo { |
4751 | + public: |
4752 | + TouchInfo() {ownership = OwnershipUndefined; ended = false;} |
4753 | + int ownership; |
4754 | + bool ended; |
4755 | + }; |
4756 | + QMap<int, TouchInfo> m_touchInfoMap; |
4757 | + |
4758 | + TouchDispatcher m_dispatcher; |
4759 | + |
4760 | + friend class tst_TouchGate; |
4761 | +}; |
4762 | + |
4763 | +#endif // UBUNTU_TOUCH_GATE_H |
4764 | |
4765 | === added file 'src/app/unity8/plugins/Ubuntu/Gestures/UbuntuGesturesQmlGlobal.h' |
4766 | --- src/app/unity8/plugins/Ubuntu/Gestures/UbuntuGesturesQmlGlobal.h 1970-01-01 00:00:00 +0000 |
4767 | +++ src/app/unity8/plugins/Ubuntu/Gestures/UbuntuGesturesQmlGlobal.h 2015-03-20 16:26:56 +0000 |
4768 | @@ -0,0 +1,23 @@ |
4769 | +/* |
4770 | + * Copyright (C) 2013 Canonical, Ltd. |
4771 | + * |
4772 | + * This program is free software; you can redistribute it and/or modify |
4773 | + * it under the terms of the GNU General Public License as published by |
4774 | + * the Free Software Foundation; version 3. |
4775 | + * |
4776 | + * This program is distributed in the hope that it will be useful, |
4777 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
4778 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
4779 | + * GNU General Public License for more details. |
4780 | + * |
4781 | + * You should have received a copy of the GNU General Public License |
4782 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
4783 | + */ |
4784 | + |
4785 | +#include <QtCore/QtGlobal> |
4786 | + |
4787 | +#if defined(UBUNTUGESTURESQML_LIBRARY) |
4788 | +# define UBUNTUGESTURESQML_EXPORT Q_DECL_EXPORT |
4789 | +#else |
4790 | +# define UBUNTUGESTURESQML_EXPORT Q_DECL_IMPORT |
4791 | +#endif |
4792 | |
4793 | === added file 'src/app/unity8/plugins/Ubuntu/Gestures/plugin.cpp' |
4794 | --- src/app/unity8/plugins/Ubuntu/Gestures/plugin.cpp 1970-01-01 00:00:00 +0000 |
4795 | +++ src/app/unity8/plugins/Ubuntu/Gestures/plugin.cpp 2015-03-20 16:26:56 +0000 |
4796 | @@ -0,0 +1,39 @@ |
4797 | +/* |
4798 | + * Copyright (C) 2013 Canonical, Ltd. |
4799 | + * |
4800 | + * This program is free software; you can redistribute it and/or modify |
4801 | + * it under the terms of the GNU General Public License as published by |
4802 | + * the Free Software Foundation; version 3. |
4803 | + * |
4804 | + * This program is distributed in the hope that it will be useful, |
4805 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
4806 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
4807 | + * GNU General Public License for more details. |
4808 | + * |
4809 | + * You should have received a copy of the GNU General Public License |
4810 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
4811 | + */ |
4812 | + |
4813 | +#include "plugin.h" |
4814 | +#include "AxisVelocityCalculator.h" |
4815 | +#include "Direction.h" |
4816 | +#include "DirectionalDragArea.h" |
4817 | +#include "PressedOutsideNotifier.h" |
4818 | +#include "TouchGate.h" |
4819 | + |
4820 | +#include <qqml.h> |
4821 | + |
4822 | +static QObject* directionSingleton(QQmlEngine* engine, QJSEngine* scriptEngine) { |
4823 | + Q_UNUSED(engine); |
4824 | + Q_UNUSED(scriptEngine); |
4825 | + return new Direction; |
4826 | +} |
4827 | + |
4828 | +void UbuntuGesturesQmlPlugin::registerTypes(const char *uri) |
4829 | +{ |
4830 | + qmlRegisterSingletonType<Direction>(uri, 0, 1, "Direction", directionSingleton); |
4831 | + qmlRegisterType<DirectionalDragArea>(uri, 0, 1, "DirectionalDragArea"); |
4832 | + qmlRegisterType<AxisVelocityCalculator>(uri, 0, 1, "AxisVelocityCalculator"); |
4833 | + qmlRegisterType<PressedOutsideNotifier>(uri, 0, 1, "PressedOutsideNotifier"); |
4834 | + qmlRegisterType<TouchGate>(uri, 0, 1, "TouchGate"); |
4835 | +} |
4836 | |
4837 | === added file 'src/app/unity8/plugins/Ubuntu/Gestures/plugin.h' |
4838 | --- src/app/unity8/plugins/Ubuntu/Gestures/plugin.h 1970-01-01 00:00:00 +0000 |
4839 | +++ src/app/unity8/plugins/Ubuntu/Gestures/plugin.h 2015-03-20 16:26:56 +0000 |
4840 | @@ -0,0 +1,31 @@ |
4841 | +/* |
4842 | + * Copyright (C) 2013 Canonical, Ltd. |
4843 | + * |
4844 | + * This program is free software; you can redistribute it and/or modify |
4845 | + * it under the terms of the GNU General Public License as published by |
4846 | + * the Free Software Foundation; version 3. |
4847 | + * |
4848 | + * This program is distributed in the hope that it will be useful, |
4849 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
4850 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
4851 | + * GNU General Public License for more details. |
4852 | + * |
4853 | + * You should have received a copy of the GNU General Public License |
4854 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
4855 | + */ |
4856 | + |
4857 | +#ifndef PLUGIN_H |
4858 | +#define PLUGIN_H |
4859 | + |
4860 | +#include <QtQml/QQmlExtensionPlugin> |
4861 | + |
4862 | +class UbuntuGesturesQmlPlugin : public QQmlExtensionPlugin |
4863 | +{ |
4864 | + Q_OBJECT |
4865 | + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") |
4866 | +public: |
4867 | + void registerTypes(const char *uri); |
4868 | +}; |
4869 | + |
4870 | + |
4871 | +#endif |
4872 | |
4873 | === added file 'src/app/unity8/plugins/Ubuntu/Gestures/qmldir' |
4874 | --- src/app/unity8/plugins/Ubuntu/Gestures/qmldir 1970-01-01 00:00:00 +0000 |
4875 | +++ src/app/unity8/plugins/Ubuntu/Gestures/qmldir 2015-03-20 16:26:56 +0000 |
4876 | @@ -0,0 +1,3 @@ |
4877 | +module Ubuntu.Gestures |
4878 | +plugin UbuntuGesturesQml |
4879 | +typeinfo Gestures.qmltypes |
4880 | |
4881 | === added file 'src/app/webbrowser/BottomEdgeHandle.qml' |
4882 | --- src/app/webbrowser/BottomEdgeHandle.qml 1970-01-01 00:00:00 +0000 |
4883 | +++ src/app/webbrowser/BottomEdgeHandle.qml 2015-03-20 16:26:56 +0000 |
4884 | @@ -0,0 +1,36 @@ |
4885 | +/* |
4886 | + * Copyright 2014-2015 Canonical Ltd. |
4887 | + * |
4888 | + * This file is part of webbrowser-app. |
4889 | + * |
4890 | + * webbrowser-app is free software; you can redistribute it and/or modify |
4891 | + * it under the terms of the GNU General Public License as published by |
4892 | + * the Free Software Foundation; version 3. |
4893 | + * |
4894 | + * webbrowser-app is distributed in the hope that it will be useful, |
4895 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
4896 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
4897 | + * GNU General Public License for more details. |
4898 | + * |
4899 | + * You should have received a copy of the GNU General Public License |
4900 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
4901 | + */ |
4902 | + |
4903 | +import QtQuick 2.0 |
4904 | +import Ubuntu.Gestures 0.1 |
4905 | + |
4906 | +DirectionalDragArea { |
4907 | + direction: Direction.Upwards |
4908 | + |
4909 | + // default values taken from unity8’s EdgeDragArea component |
4910 | + maxDeviation: units.gu(3) |
4911 | + wideningAngle: 50 |
4912 | + distanceThreshold: units.gu(1.5) |
4913 | + minSpeed: 0 |
4914 | + maxSilenceTime: 200 |
4915 | + compositionTime: 60 |
4916 | + |
4917 | + readonly property real dragFraction: dragging ? Math.min(1.0, Math.max(0.0, sceneDistance / parent.height)) : 0.0 |
4918 | + readonly property var thresholds: [0.05, 0.18, 0.36, 0.54, 1.0] |
4919 | + readonly property int stage: thresholds.map(function(t) { return dragFraction <= t }).indexOf(true) |
4920 | +} |
4921 | |
4922 | === modified file 'src/app/webbrowser/Browser.qml' |
4923 | --- src/app/webbrowser/Browser.qml 2015-02-05 13:09:42 +0000 |
4924 | +++ src/app/webbrowser/Browser.qml 2015-03-20 16:26:56 +0000 |
4925 | @@ -1,5 +1,5 @@ |
4926 | /* |
4927 | - * Copyright 2013-2014 Canonical Ltd. |
4928 | + * Copyright 2013-2015 Canonical Ltd. |
4929 | * |
4930 | * This file is part of webbrowser-app. |
4931 | * |
4932 | @@ -17,6 +17,7 @@ |
4933 | */ |
4934 | |
4935 | import QtQuick 2.0 |
4936 | +import QtQuick.Window 2.0 |
4937 | import com.canonical.Oxide 1.4 as Oxide |
4938 | import Ubuntu.Components 1.1 |
4939 | import webbrowserapp.private 0.1 |
4940 | @@ -78,19 +79,33 @@ |
4941 | ] |
4942 | |
4943 | Item { |
4944 | - id: mainView |
4945 | - |
4946 | anchors.fill: parent |
4947 | - visible: !historyViewContainer.visible && !tabsViewContainer.visible |
4948 | + |
4949 | + TabChrome { |
4950 | + id: invisibleTabChrome |
4951 | + visible: false |
4952 | + anchors { |
4953 | + top: parent.top |
4954 | + left: parent.left |
4955 | + right: parent.right |
4956 | + } |
4957 | + } |
4958 | + |
4959 | + Rectangle { |
4960 | + // Background for the recent view |
4961 | + anchors.fill: invisibleTabChrome |
4962 | + visible: recentView.visible |
4963 | + color: "#312f2c" |
4964 | + } |
4965 | |
4966 | FocusScope { |
4967 | id: tabContainer |
4968 | anchors { |
4969 | left: parent.left |
4970 | right: parent.right |
4971 | - top: chrome.bottom |
4972 | + top: recentView.visible ? invisibleTabChrome.bottom : chrome.bottom |
4973 | } |
4974 | - height: parent.height - chrome.visibleHeight - osk.height |
4975 | + height: parent.height - osk.height - (recentView.visible ? invisibleTabChrome.height : chrome.visibleHeight) |
4976 | } |
4977 | |
4978 | Loader { |
4979 | @@ -124,6 +139,8 @@ |
4980 | Chrome { |
4981 | id: chrome |
4982 | |
4983 | + visible: !recentView.visible |
4984 | + |
4985 | webview: browser.currentWebview |
4986 | searchUrl: browser.searchEngine ? browser.searchEngine.template : "" |
4987 | |
4988 | @@ -181,12 +198,17 @@ |
4989 | objectName: "tabs" |
4990 | text: i18n.tr("Open tabs") |
4991 | iconName: "browser-tabs" |
4992 | - onTriggered: tabsViewComponent.createObject(tabsViewContainer) |
4993 | + enabled: formFactor != "mobile" |
4994 | + onTriggered: { |
4995 | + recentView.state = "shown" |
4996 | + recentToolbar.state = "shown" |
4997 | + } |
4998 | }, |
4999 | Action { |
5000 | objectName: "newtab" |
FAILED: Continuous integration, rev:888 jenkins. qa.ubuntu. com/job/ webbrowser- app-ci/ 1419/ jenkins. qa.ubuntu. com/job/ generic- deb-autopilot- vivid-touch/ 1080 jenkins. qa.ubuntu. com/job/ generic- mediumtests- vivid/505 jenkins. qa.ubuntu. com/job/ webbrowser- app-vivid- amd64-ci/ 177 jenkins. qa.ubuntu. com/job/ webbrowser- app-vivid- armhf-ci/ 177 jenkins. qa.ubuntu. com/job/ webbrowser- app-vivid- armhf-ci/ 177/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ webbrowser- app-vivid- i386-ci/ 177 jenkins. qa.ubuntu. com/job/ generic- deb-autopilot- runner- vivid-mako/ 959 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- vivid-armhf/ 1078 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- vivid-armhf/ 1078/artifact/ work/output/ *zip*/output. zip s-jenkins. ubuntu- ci:8080/ job/touch- flash-device/ 17614 jenkins. qa.ubuntu. com/job/ autopilot- testrunner- otto-vivid/ 409 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- vivid-amd64/ 613 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- vivid-amd64/ 613/artifact/ work/output/ *zip*/output. zip
http://
Executed test runs:
UNSTABLE: http://
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/webbrowser- app-ci/ 1419/rebuild
http://