on xenial help sign in
Merge upstream
7d8a676Ying-Chun Liu
.bazaar/plugins/makecheck_unity_phablet.py+1−1
@@ -18,7 +18,7 @@ from bzrlib import branch, errors
18from bzrlib.urlutils import dirname, local_path_from_url
18from bzrlib.urlutils import dirname, local_path_from_url
19
19
20def execute_makecheck(local_branch, master_branch, old_revision_number, old_revision_id, future_revision_number, future_revision_id, tree_delta, future_tree):
20def execute_makecheck(local_branch, master_branch, old_revision_number, old_revision_id, future_revision_number, future_revision_id, tree_delta, future_tree):
21− if not master_branch.basis_tree().has_filename("Shell.qml"):
21+ if not master_branch.basis_tree().has_filename("qml/Shell.qml"):
22 return
22 return
23
23
24 os.chdir(local_path_from_url(master_branch.base))
24 os.chdir(local_path_from_url(master_branch.base))
build+3
@@ -41,17 +41,20 @@ install_dependencies() {
41 sudo apt-get install devscripts \
41 sudo apt-get install devscripts \
42 equivs \
42 equivs \
43 gsettings-desktop-schemas \
43 gsettings-desktop-schemas \
44+ gsettings-ubuntu-schemas \
44 qmenumodel-qml \
45 qmenumodel-qml \
45 qtdeclarative5-dee-plugin \
46 qtdeclarative5-dee-plugin \
46 qtdeclarative5-gsettings1.0 \
47 qtdeclarative5-gsettings1.0 \
47 qtdeclarative5-ubuntu-ui-toolkit-plugin \
48 qtdeclarative5-ubuntu-ui-toolkit-plugin \
48 qtdeclarative5-ubuntu-settings-components \
49 qtdeclarative5-ubuntu-settings-components \
50+ qtdeclarative5-ubuntu-thumbnailer0.1 \
49 unity-notifications-impl \
51 unity-notifications-impl \
50 qtdeclarative5-xmllistmodel-plugin \
52 qtdeclarative5-xmllistmodel-plugin \
51 ubuntu-mobile-icons \
53 ubuntu-mobile-icons \
52 unity-scope-home \
54 unity-scope-home \
53 unity-lens-applications \
55 unity-lens-applications \
54 unity-plugin-scopes \
56 unity-plugin-scopes \
57+ xvfb \
55 || exit 5
58 || exit 5
56}
59}
57
60
cmake/modules/QmlTest.cmake+77−17
@@ -58,13 +58,22 @@ macro(add_manual_qml_test SUBPATH COMPONENT_NAME)
58
58
59endmacro(add_manual_qml_test)
59endmacro(add_manual_qml_test)
60
60
61+macro(add_qml_benchmark SUBPATH COMPONENT_NAME ITERATIONS)
62+ add_qml_test_internal(${SUBPATH} ${COMPONENT_NAME} ${ITERATIONS})
63+endmacro(add_qml_benchmark)
64+
61macro(add_qml_test SUBPATH COMPONENT_NAME)
65macro(add_qml_test SUBPATH COMPONENT_NAME)
66+ add_qml_test_internal(${SUBPATH} ${COMPONENT_NAME} 0)
67+endmacro(add_qml_test)
68+
69+macro(add_qml_test_internal SUBPATH COMPONENT_NAME ITERATIONS)
62 set(options NO_ADD_TEST NO_TARGETS)
70 set(options NO_ADD_TEST NO_TARGETS)
63 set(multi_value_keywords IMPORT_PATHS TARGETS PROPERTIES ENVIRONMENT)
71 set(multi_value_keywords IMPORT_PATHS TARGETS PROPERTIES ENVIRONMENT)
64
72
65 cmake_parse_arguments(qmltest "${options}" "" "${multi_value_keywords}" ${ARGN})
73 cmake_parse_arguments(qmltest "${options}" "" "${multi_value_keywords}" ${ARGN})
66
74
67 set(qmltest_TARGET test${COMPONENT_NAME})
75 set(qmltest_TARGET test${COMPONENT_NAME})
76+ set(qmltest_xvfb_TARGET xvfbtest${COMPONENT_NAME})
68 set(qmltest_FILE ${SUBPATH}/tst_${COMPONENT_NAME})
77 set(qmltest_FILE ${SUBPATH}/tst_${COMPONENT_NAME})
69
78
70 set(qmltestrunner_imports "")
79 set(qmltestrunner_imports "")
@@ -82,24 +91,76 @@ macro(add_qml_test SUBPATH COMPONENT_NAME)
82
91
83 string(TOLOWER "${CMAKE_GENERATOR}" cmake_generator_lower)
92 string(TOLOWER "${CMAKE_GENERATOR}" cmake_generator_lower)
84 if(cmake_generator_lower STREQUAL "unix makefiles")
93 if(cmake_generator_lower STREQUAL "unix makefiles")
85− set(qmltest_command
86− env ${qmltest_ENVIRONMENT}
87− ${qmltestrunner_exe} -input ${CMAKE_CURRENT_SOURCE_DIR}/${qmltest_FILE}.qml
88− ${qmltestrunner_imports}
89− -o ${CMAKE_BINARY_DIR}/${qmltest_TARGET}.xml,xunitxml
90− -o -,txt
91− $(FUNCTION)
94+ set(function_ARGS $(FUNCTION))
92− )
93 else()
95 else()
96+ set(function_ARGS "")
97+ endif()
98+
99+ if (${ITERATIONS} GREATER 0)
100+ set(ITERATIONS_STRING "-iterations" ${ITERATIONS})
101+ else()
102+ set(ITERATIONS_STRING "")
103+ endif()
104+
94− set(qmltest_command
105+ set(qmltest_command
95− env ${qmltest_ENVIRONMENT}
106+ env ${qmltest_ENVIRONMENT}
107+ ${qmltestrunner_exe} -input ${CMAKE_CURRENT_SOURCE_DIR}/${qmltest_FILE}.qml
108+ ${qmltestrunner_imports}
109+ ${ITERATIONS_STRING}
110+ -o ${CMAKE_BINARY_DIR}/${qmltest_TARGET}.xml,xunitxml
111+ -o -,txt
112+ ${function_ARGS}
113+ )
114+ find_program( HAVE_GCC gcc )
115+ if (NOT ${HAVE_GCC} STREQUAL "")
116+ exec_program( gcc ARGS "-dumpmachine" OUTPUT_VARIABLE ARCH_TRIPLET )
117+ set(LD_PRELOAD_PATH "LD_PRELOAD=/usr/lib/${ARCH_TRIPLET}/mesa/libGL.so.1")
118+ endif()
119+ set(qmltest_xvfb_command
120+ env ${qmltest_ENVIRONMENT} ${LD_PRELOAD_PATH}
121+ xvfb-run --server-args "-screen 0 1024x768x24" --auto-servernum
96− ${qmltestrunner_exe} -input ${CMAKE_CURRENT_SOURCE_DIR}/${qmltest_FILE}.qml
122+ ${qmltestrunner_exe} -input ${CMAKE_CURRENT_SOURCE_DIR}/${qmltest_FILE}.qml
97− ${qmltestrunner_imports}
123+ ${qmltestrunner_imports}
98− -o ${CMAKE_BINARY_DIR}/${qmltest_TARGET}.xml,xunitxml
124+ -o ${CMAKE_BINARY_DIR}/${qmltest_TARGET}.xml,xunitxml
99− -o -,txt
125+ -o -,txt
100− )
126+ ${function_ARGS}
127+ )
128+
129+ add_qmltest_target(${qmltest_TARGET} "${qmltest_command}" TRUE ${qmltest_NO_ADD_TEST})
130+ add_qmltest_target(${qmltest_xvfb_TARGET} "${qmltest_xvfb_command}" ${qmltest_NO_TARGETS} TRUE)
131+ add_manual_qml_test(${SUBPATH} ${COMPONENT_NAME} ${ARGN})
132+endmacro(add_qml_test_internal)
133+
134+macro(add_binary_qml_test CLASS_NAME LD_PATH DEPS)
135+ set(testCommand
136+ LD_LIBRARY_PATH=${LD_PATH}
137+ ${CMAKE_CURRENT_BINARY_DIR}/${CLASS_NAME}TestExec
138+ -o ${CMAKE_BINARY_DIR}/${CLASSNAME}Test.xml,xunitxml
139+ -o -,txt)
140+
141+ add_qmltest_target(test${CLASS_NAME} "${testCommand}" FALSE TRUE)
142+ add_dependencies(test${CLASS_NAME} ${CLASS_NAME}TestExec ${DEPS})
143+
144+ find_program( HAVE_GCC gcc )
145+ if (NOT ${HAVE_GCC} STREQUAL "")
146+ exec_program( gcc ARGS "-dumpmachine" OUTPUT_VARIABLE ARCH_TRIPLET )
147+ set(LD_PRELOAD_PATH "LD_PRELOAD=/usr/lib/${ARCH_TRIPLET}/mesa/libGL.so.1")
101 endif()
148 endif()
149+ set(xvfbtestCommand
150+ ${LD_PRELOAD_PATH}
151+ LD_LIBRARY_PATH=${LD_PATH}
152+ xvfb-run --server-args "-screen 0 1024x768x24" --auto-servernum
153+ ${CMAKE_CURRENT_BINARY_DIR}/${CLASS_NAME}TestExec
154+ -o ${CMAKE_BINARY_DIR}/${CLASS_NAME}Test.xml,xunitxml
155+ -o -,txt)
102
156
157+ add_qmltest_target(xvfbtest${CLASS_NAME} "${xvfbtestCommand}" FALSE TRUE)
158+ add_dependencies(qmluitests xvfbtest${CLASS_NAME})
159+
160+ add_manual_qml_test(. ${CLASS_NAME} IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
161+endmacro(add_binary_qml_test)
162+
163+macro(add_qmltest_target qmltest_TARGET qmltest_command qmltest_NO_TARGETS qmltest_NO_ADD_TEST)
103 add_custom_target(${qmltest_TARGET} ${qmltest_command})
164 add_custom_target(${qmltest_TARGET} ${qmltest_command})
104
165
105 if(NOT "${qmltest_PROPERTIES}" STREQUAL "")
166 if(NOT "${qmltest_PROPERTIES}" STREQUAL "")
@@ -130,5 +191,4 @@ macro(add_qml_test SUBPATH COMPONENT_NAME)
130 endif()
191 endif()
131 endif("${qmltest_NO_TARGETS}" STREQUAL "FALSE")
192 endif("${qmltest_NO_TARGETS}" STREQUAL "FALSE")
132
193
133− add_manual_qml_test(${SUBPATH} ${COMPONENT_NAME} ${ARGN})
194+endmacro(add_qmltest_target)
134−endmacro(add_qml_test)
data/unity8.conf+2−1
@@ -1,7 +1,7 @@
1description "Unity Shell v8"
1description "Unity Shell v8"
2author "Ricardo Mendoza <ricmm@ubuntu.com>"
2author "Ricardo Mendoza <ricmm@ubuntu.com>"
3
3
4−emits scope-ui-starting
4+emits scope-ui-starting indicator-services-start
5
5
6start on ((xsession SESSION=ubuntu-touch) or (xsession SESSION=ubuntu-touch-surfaceflinger)) and started dbus
6start on ((xsession SESSION=ubuntu-touch) or (xsession SESSION=ubuntu-touch-surfaceflinger)) and started dbus
7stop on desktop-end
7stop on desktop-end
@@ -42,6 +42,7 @@ pre-start script
42 fi
42 fi
43
43
44 initctl emit scope-ui-starting
44 initctl emit scope-ui-starting
45+ initctl emit indicator-services-start
45end script
46end script
46
47
47exec ${BINARY:-unity8} $ARGS
48exec ${BINARY:-unity8} $ARGS
debian/changelog+221
@@ -1,3 +1,224 @@
1+unity8 (7.85+14.04.20140416-0ubuntu1) trusty; urgency=low
2+
3+ [ Albert Astals ]
4+ * Fix last item X position Fixes clicking on the last item sometimes
5+ not working (LP: #1301871)
6+ * Use upstart in ./run Makes it so that you can use the lock button on
7+ the device without getting that nasty hwc crash
8+ * Remove AnimationControllerWithSignals.
9+ * Use the correct delegate base item for the Carousel test
10+ * Some simplification in DashContent Kill the ScopeDelegateMapper in
11+ favour of a simple if (that will eventually go away). Removal of all
12+ the fake scopes in the tests that added nothing of value to the
13+ tests. Removal of movementEnded signal that was unused. Removal of
14+ movementStarted and positionedAtBeginning signals that were being
15+ used as function calls. Rework DashContent tests so they what the
16+ function does what it is supposed to do instead of just making sure
17+ QML signals work .
18+ * Improve Card creation time by adding loaders that make sure only
19+ what's needed is loaded In my computer it goes from RESULT :
20+ qmltestrunner::benchmark_time:"cardTitleArtSubtitleMascotSummaryMode
21+ l": 3.217 msecs per iteration (total: 3,218, iterations: 1000)
22+ RESULT :
23+ qmltestrunner::benchmark_time:"cardTitleArtSubtitleMascotModel":
24+ 1.647 msecs per iteration (total: 1,648, iterations: 1000) RESULT :
25+ qmltestrunner::benchmark_time:"cardTitleArtSubtitleModel": 1.514
26+ msecs per iteration (total: 1,515, iterations: 1000) RESULT :
27+ qmltestrunner::benchmark_time:"cardTitleArtModel": 1.471 msecs per
28+ iteration (total: 1,471, iterations: 1000) RESULT :
29+ qmltestrunner::benchmark_time:"cardArtModel": 1.447 msecs per
30+ iteration (total: 1,448, iterations: 1000) RESULT :
31+ qmltestrunner::benchmark_time:"cardTitleModel": 1.276 msecs per
32+ iteration (total: 1,276, iterations: 1000) to RESULT :
33+ qmltestrunner::benchmark_time:"cardTitleArtSubtitleMascotSummaryMode
34+ l": 2.916 msecs per iteration (total: 2,917, iterations: 1000)
35+ RESULT :
36+ qmltestrunner::benchmark_time:"cardTitleArtSubtitleMascotModel":
37+ 1.504 msecs per iteration (total: 1,504, iterations: 1000) RESULT :
38+ qmltestrunner::benchmark_time:"cardTitleArtSubtitleModel": 1.060
39+ msecs per iteration (total: 1,061, iterations: 1000) RESULT :
40+ qmltestrunner::benchmark_time:"cardTitleArtModel": 1.052 msecs per
41+ iteration (total: 1,053, iterations: 1000) RESULT :
42+ qmltestrunner::benchmark_time:"cardArtModel": 0.727 msecs per
43+ iteration (total: 728, iterations: 1000) RESULT :
44+ qmltestrunner::benchmark_time:"cardTitleModel": 0.817 msecs per
45+ iteration (total: 818, iterations: 1000) (LP: #1297197)
46+
47+ [ Allan LeSage ]
48+ * DashApps emulator get_applications should return a list ordered by
49+ visible y, x.
50+
51+ [ Andrea Cimitan ]
52+ * Workaround for lp1301309 until fixes for palette in ui toolkit (LP:
53+ #1301309)
54+
55+ [ Leo Arias ]
56+ * Reverted the change that returns application cards instead of
57+ titles.
58+
59+ [ Nick Dedekind ]
60+ * Indicator services started by unity8 upstart configuration rather
61+ than manual emmision from indicator manager.
62+
63+ [ Mirco Müller ]
64+ * Fix notification ap-test assertions.
65+
66+ [ Michael Terry ]
67+ * Use new tablet and phone backgrounds from Design.
68+
69+ [ Michael Zanetti ]
70+ * workaround the QTestLogger assertion issue with make tryXyz and our
71+ custom uqmlscene
72+
73+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Wed, 16 Apr 2014 13:45:01 +0000
74+
75+unity8 (7.85+14.04.20140415.2-0ubuntu1) trusty; urgency=low
76+
77+ [ Michael Terry ]
78+ * When an application requests focus, handle it in Shell.qml by hiding
79+ the greeter and stopping any edge demo. (LP: #1227753)
80+
81+ [ Leo Arias ]
82+ * Use subprocess.check_call when caling url-dispatcher, so an error
83+ will be raised if it fails.
84+ * Test application life cycle with fake apps, instead of messaging and
85+ address book.
86+
87+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Tue, 15 Apr 2014 12:47:11 +0000
88+
89+unity8 (7.85+14.04.20140410.1-0ubuntu1) trusty; urgency=medium
90+
91+ [ Didier Roche ]
92+ * Resync trunk with previous revert upload
93+
94+ [ Michał Sawicz ]
95+ * Set the Qt.ImhNoPredictiveText flag on wifi password field, fixes
96+ lp:1291575 (LP: #1291575)
97+
98+ [ Albert Astals ]
99+ * Take into account the originY when specifying the delegate ranges
100+ Fixes bug #1300302 (LP: #1300302)
101+
102+ [ CI bot ]
103+ * Resync trunk
104+
105+ [ Allan LeSage ]
106+ * Swiping open an indicator must show its correct title--protect
107+ against lp:1253804 . (LP: #1253804)
108+
109+ [ Alexander Sack ]
110+ * Fix TypeError: issue seen in system_integration autopilot test on
111+ image 279. (LP: #1303685)
112+
113+ [ Bill Filler ]
114+ * Set the Qt.ImhNoPredictiveText flag on wifi password field, fixes
115+ lp:1291575 (LP: #1291575)
116+
117+ [ Leo Arias ]
118+ * Added a search autopilot helper.
119+
120+ [ Michael Terry ]
121+ * Provide a all-in-one script for getting a device to an unlocked
122+ state.
123+
124+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Thu, 10 Apr 2014 10:03:31 +0000
125+
126+unity8 (7.85+14.04.20140404.is.7.85+14.04.20140403.1-0ubuntu1) trusty; urgency=medium
127+
128+ * Revert to previous version as it's linked to latest sdk change which
129+ is making gallery-app AP tests failing on the CI dashboard
130+
131+ -- Didier Roche <didrocks@ubuntu.com> Tue, 08 Apr 2014 13:53:47 +0200
132+
133+unity8 (7.85+14.04.20140404-0ubuntu1) trusty; urgency=low
134+
135+ [ Albert Astals ]
136+ * Adapt to new TabBar
137+
138+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Fri, 04 Apr 2014 15:03:00 +0000
139+
140+unity8 (7.85+14.04.20140403.1-0ubuntu1) trusty; urgency=low
141+
142+ [ Michael Terry ]
143+ * Re-enable test_networkmanager_integration autopilot test on phone
144+ platforms
145+
146+ [ CI bot ]
147+ * Resync trunk
148+
149+ [ Leo Arias ]
150+ * Reverted the open_preview autopilot helper to return a Preview
151+ object.
152+
153+ [ Albert Astals ]
154+ * If not running in Mir load the "fake" application manager (LP:
155+ #1301547)
156+ * Remove unused properties from DashRenderer
157+
158+ [ Michael Zanetti ]
159+ * Fix tests after right edge merge. Drop old stages tests. Fix right
160+ edge tests if someone doesn't have the GRID_UNIT_PX exported. make
161+ GenericScopeView test more robust that broke because the ordering
162+ changed
163+ * add "make xvfbtestSomething" target to run qml tests in xvfb
164+ * make the "make test" commit hook work again
165+
166+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Thu, 03 Apr 2014 10:38:53 +0000
167+
168+unity8 (7.85+14.04.20140401.3-0ubuntu1) trusty; urgency=medium
169+
170+ [ Michał Sawicz ]
171+ * Bump version to ensure incompatibility with previous Unity.Application
172+ implementations.
173+ * We'll only have the unity-mir and mock Ubuntu.Application plugins
174+ now, no need for mangling the import paths.
175+
176+ [ Michal Hruby ]
177+ * Remove the albumart image provider. (LP: #1262711)
178+ * Don't reset search string after 2 seconds. (LP: #1297246)
179+
180+ [ James Henstridge ]
181+ * Remove the albumart image provider. (LP: #1262711)
182+
183+ [ Albert Astals ]
184+ * Carousel: Add test to make sure we only create the needed delegates
185+ and not more
186+ * LVWPH: Remove processEvents() call from updatePolish() It causes
187+ some reentrancy issues and in some times you end up in polishItems()
188+ with items that have been deleted because you called processEvents()
189+ This means i need a small tweak in itemGeometryChanged to not
190+ reposition items if we are inside a setContentHeight call and two
191+ small tweaks to tests since now things happen in a different order
192+ and numbers are different (though equivalent) (LP: #1297240)
193+ * Card.qml binding loops are gone. hooray \o/ Also made the aspect
194+ properties readonly
195+
196+ [ Mirco Müller ]
197+ * A potential fix for "Cannot read property 'state' of null"-failure
198+ on Jenkins with the VisualSnapDecisionsQueue QML-test of
199+ notifications.
200+
201+ [ Michael Terry ]
202+ * Pass user's preference for auto-brightness on to powerd. (LP:
203+ #1273174)
204+
205+ [ Michael Zanetti ]
206+ * Registers a dummy QObject as QTestRootObject in uqmlscene in order
207+ to fix make trySomething with Qt 5.2.
208+
209+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Tue, 01 Apr 2014 22:56:52 +0000
210+
211+unity8 (7.84+14.04.20140327.1-0ubuntu2) trusty; urgency=medium
212+
213+ * For now, have libunity-private depending on libunity-core-6.0-9 as the
214+ gsettings schema is here. The dependency wasn't direct and dropped from
215+ Touch image #271. Consequently, unity8 didn't start (gsettings
216+ segfaulting).
217+ Proper strategy will be to include the schema in another package to only
218+ pull it.
219+
220+ -- Didier Roche <didrocks@ubuntu.com> Tue, 01 Apr 2014 09:52:14 +0200
221+
1unity8 (7.84+14.04.20140327.1-0ubuntu1) trusty; urgency=low
222unity8 (7.84+14.04.20140327.1-0ubuntu1) trusty; urgency=low
2
223
3 [ Michał Sawicz ]
224 [ Michał Sawicz ]
debian/control+12−8
@@ -13,13 +13,10 @@ Build-Depends: cmake,
13 libgles2-mesa-dev[armhf],
13 libgles2-mesa-dev[armhf],
14 libgsettings-qt-dev,
14 libgsettings-qt-dev,
15 libhud-client2-dev,
15 libhud-client2-dev,
16− libnih-dbus-dev,
17− libnih-dev,
18 libpulse-dev,
16 libpulse-dev,
19 libqmenumodel-dev (>= 0.2.7),
17 libqmenumodel-dev (>= 0.2.7),
20− libunity-api-dev (>= 7.80.4),
18+ libunity-api-dev (>= 7.80.6),
21 libunity-mir-dev,
19 libunity-mir-dev,
22− libupstart-dev,
23 libusermetricsoutput1-dev,
20 libusermetricsoutput1-dev,
24 libxcb1-dev,
21 libxcb1-dev,
25 libglib2.0-dev,
22 libglib2.0-dev,
@@ -80,12 +77,15 @@ Depends: gsettings-desktop-schemas,
80 qtdeclarative5-dee-plugin,
77 qtdeclarative5-dee-plugin,
81 qtdeclarative5-gsettings1.0,
78 qtdeclarative5-gsettings1.0,
82 qtdeclarative5-ubuntu-settings-components,
79 qtdeclarative5-ubuntu-settings-components,
80+ qtdeclarative5-ubuntu-thumbnailer0.1 | ubuntu-thumbnailer-impl,
81+ ubuntu-thumbnailer-impl-0,
83 qtdeclarative5-ubuntu-ui-toolkit-plugin,
82 qtdeclarative5-ubuntu-ui-toolkit-plugin,
84 qtdeclarative5-unity-notifications-plugin | unity-notifications-impl,
83 qtdeclarative5-unity-notifications-plugin | unity-notifications-impl,
85 qtdeclarative5-xmllistmodel-plugin,
84 qtdeclarative5-xmllistmodel-plugin,
85+ unity-application-impl-2,
86 unity-launcher-impl-3,
86 unity-launcher-impl-3,
87 unity-notifications-impl-2,
87 unity-notifications-impl-2,
88− unity8-fake-env | qtubuntu-shell,
88+ unity8-fake-env | unity-application-impl,
89 unity8-private (= ${binary:Version}),
89 unity8-private (= ${binary:Version}),
90 unity8-private | unity-launcher-impl,
90 unity8-private | unity-launcher-impl,
91 unity-plugin-scopes | unity-scopes-impl,
91 unity-plugin-scopes | unity-scopes-impl,
@@ -97,7 +97,8 @@ Depends: gsettings-desktop-schemas,
97Recommends: unity-scope-click,
97Recommends: unity-scope-click,
98 unity-scope-mediascanner2,
98 unity-scope-mediascanner2,
99 unity-scope-scopes,
99 unity-scope-scopes,
100−Breaks: indicator-network (<< 0.5.1)
100+Breaks: indicator-network (<< 0.5.1),
101+ ubuntu-touch-session (<< 0.107),
101Replaces: ubuntu-touch-session (<< 0.82~)
102Replaces: ubuntu-touch-session (<< 0.82~)
102Description: Unity 8 shell
103Description: Unity 8 shell
103 The Unity 8 shell is the primary user interface for Ubuntu devices.
104 The Unity 8 shell is the primary user interface for Ubuntu devices.
@@ -134,7 +135,8 @@ Multi-Arch: same
134Pre-Depends: ${misc:Pre-Depends},
135Pre-Depends: ${misc:Pre-Depends},
135Depends: ${misc:Depends},
136Depends: ${misc:Depends},
136 ${shlibs:Depends},
137 ${shlibs:Depends},
137−Provides: qtubuntu-shell,
138+Provides: unity-application-impl,
139+ unity-application-impl-2,
138Description: Fake environment for running Unity 8 shell
140Description: Fake environment for running Unity 8 shell
139 Provides fake implementations of some QML modules used by Unity 8 shell
141 Provides fake implementations of some QML modules used by Unity 8 shell
140 (e.g Ubuntu.Application) so that you can run it in a sandboxed environment.
142 (e.g Ubuntu.Application) so that you can run it in a sandboxed environment.
@@ -143,8 +145,10 @@ Package: unity8-private
143Architecture: any
145Architecture: any
144Multi-Arch: same
146Multi-Arch: same
145Pre-Depends: ${misc:Pre-Depends},
147Pre-Depends: ${misc:Pre-Depends},
148+Depends: gsettings-ubuntu-schemas,
146−Depends: ${misc:Depends},
149+ ${misc:Depends},
147 ${shlibs:Depends},
150 ${shlibs:Depends},
151+ libunity-core-6.0-9,
148Provides: unity-launcher-impl,
152Provides: unity-launcher-impl,
149 unity-launcher-impl-3,
153 unity-launcher-impl-3,
150Description: Unity 8 private libs
154Description: Unity 8 private libs
debian/unity8-autopilot.install+1
@@ -1 +1,2 @@
1usr/lib/python*/*/unity*
1usr/lib/python*/*/unity*
2+usr/share/unity8/unlock-device
debian/unity8-fake-env.install+1
@@ -1,3 +1,4 @@
1+usr/lib/*/unity8/qml/nonmirplugins
1usr/lib/*/unity8/qml/mocks
2usr/lib/*/unity8/qml/mocks
2usr/lib/*/unity8/qml/scopefakes
3usr/lib/*/unity8/qml/scopefakes
3usr/share/unity8/mocks
4usr/share/unity8/mocks
debian/unity8.install+1−1
@@ -10,6 +10,6 @@ usr/share/unity8/Launcher
10usr/share/unity8/Notifications
10usr/share/unity8/Notifications
11usr/share/unity8/Panel
11usr/share/unity8/Panel
12usr/share/unity8/Shell.qml
12usr/share/unity8/Shell.qml
13−usr/share/unity8/SideStage
13+usr/share/unity8/Stages
14usr/share/unity8/graphics
14usr/share/unity8/graphics
15data/unity8.conf usr/share/upstart/sessions/
15data/unity8.conf usr/share/upstart/sessions/
include/paths.h.in+10
@@ -57,6 +57,16 @@ inline QStringList overrideImportPaths() {
57 return paths;
57 return paths;
58}
58}
59
59
60+inline QStringList nonMirImportPaths() {
61+ QStringList paths;
62+ if (isRunningInstalled()) {
63+ paths << QString("@CMAKE_INSTALL_PREFIX@/@SHELL_INSTALL_QML@/nonmirplugins");
64+ } else {
65+ paths << QString("@CMAKE_BINARY_DIR@/nonmirplugins");
66+ }
67+ return paths;
68+}
69+
60inline QStringList fallbackImportPaths() {
70inline QStringList fallbackImportPaths() {
61 QStringList paths;
71 QStringList paths;
62 if (isRunningInstalled()) {
72 if (isRunningInstalled()) {
plugins/DashViews/listviewwithpageheader.cpp+1−6
@@ -1097,7 +1097,7 @@ void ListViewWithPageHeader::itemGeometryChanged(QQuickItem * /*item*/, const QR
1097{
1097{
1098 const qreal heightDiff = newGeometry.height() - oldGeometry.height();
1098 const qreal heightDiff = newGeometry.height() - oldGeometry.height();
1099 if (heightDiff != 0) {
1099 if (heightDiff != 0) {
1100− if (oldGeometry.y() + oldGeometry.height() + m_clipItem->y() <= contentY() && !m_visibleItems.isEmpty()) {
1100+ if (!m_inContentHeightKeepHeaderShown && oldGeometry.y() + oldGeometry.height() + m_clipItem->y() <= contentY() && !m_visibleItems.isEmpty()) {
1101 ListItem *firstItem = m_visibleItems.first();
1101 ListItem *firstItem = m_visibleItems.first();
1102 firstItem->setY(firstItem->y() - heightDiff);
1102 firstItem->setY(firstItem->y() - heightDiff);
1103 adjustMinYExtent();
1103 adjustMinYExtent();
@@ -1293,11 +1293,6 @@ void ListViewWithPageHeader::updatePolish()
1293 refill();
1293 refill();
1294
1294
1295 if (m_contentHeightDirty) {
1295 if (m_contentHeightDirty) {
1296− // We need to make sure all bindings have updated otherwise
1297− // we may end up with bindings updating when we call setContentHeight
1298− // and then everything gets out of sync, i.e. testHeaderPositionBug1240118
1299− QCoreApplication::instance()->processEvents();
1300−
1301 qreal contentHeight;
1296 qreal contentHeight;
1302 if (m_visibleItems.isEmpty()) {
1297 if (m_visibleItems.isEmpty()) {
1303 contentHeight = m_headerItem ? m_headerItem->height() : 0;
1298 contentHeight = m_headerItem ? m_headerItem->height() : 0;
plugins/Unity/Indicators/CMakeLists.txt−3
@@ -6,7 +6,6 @@ project(IndicatorsQml)
6pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
6pkg_check_modules(GLIB REQUIRED glib-2.0>=2.32)
7pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
7pkg_check_modules(GIO REQUIRED gio-2.0>=2.32)
8pkg_check_modules(QMENUMODEL REQUIRED qmenumodel)
8pkg_check_modules(QMENUMODEL REQUIRED qmenumodel)
9−pkg_check_modules(LIBUPSTART REQUIRED libupstart libnih libnih-dbus dbus-1)
10
9
11find_package(Qt5Core REQUIRED)
10find_package(Qt5Core REQUIRED)
12find_package(Qt5Quick REQUIRED)
11find_package(Qt5Quick REQUIRED)
@@ -19,7 +18,6 @@ include_directories(
19 ${GLIB_INCLUDE_DIRS}
18 ${GLIB_INCLUDE_DIRS}
20 ${GIO_INCLUDE_DIRS}
19 ${GIO_INCLUDE_DIRS}
21 ${QMENUMODEL_INCLUDE_DIRS}
20 ${QMENUMODEL_INCLUDE_DIRS}
22− ${LIBUPSTART_INCLUDE_DIRS}
23)
21)
24
22
25set(IndicatorsQML_SOURCES
23set(IndicatorsQML_SOURCES
@@ -54,7 +52,6 @@ target_link_libraries(IndicatorsQml
54 ${GLIB_LIBRARIES}
52 ${GLIB_LIBRARIES}
55 ${GIO_LIBRARIES}
53 ${GIO_LIBRARIES}
56 ${QMENUMODEL_LDFLAGS}
54 ${QMENUMODEL_LDFLAGS}
57− ${LIBUPSTART_LIBRARIES}
58)
55)
59
56
60qt5_use_modules(IndicatorsQml Core Qml Quick)
57qt5_use_modules(IndicatorsQml Core Qml Quick)
plugins/Unity/Indicators/indicatorsmanager.cpp−33
@@ -21,9 +21,6 @@
21#include <QSettings>
21#include <QSettings>
22#include <QDebug>
22#include <QDebug>
23
23
24−#include <upstart.h>
25−#include <nih/alloc.h>
26−
27#include <paths.h>
24#include <paths.h>
28
25
29
26
@@ -216,36 +213,6 @@ void IndicatorsManager::setLoaded(bool loaded)
216 {
213 {
217 m_loaded = loaded;
214 m_loaded = loaded;
218 Q_EMIT loadedChanged(m_loaded);
215 Q_EMIT loadedChanged(m_loaded);
219−
220− auto upstartsession = qgetenv("UPSTART_SESSION");
221− NihDBusProxy * upstart = NULL;
222−
223− if (!upstartsession.isNull()) {
224− DBusConnection * conn = NULL;
225− conn = dbus_connection_open(upstartsession.constData(), NULL);
226− if (conn != NULL) {
227− upstart = nih_dbus_proxy_new(NULL, conn,
228− NULL,
229− DBUS_PATH_UPSTART,
230− NULL, NULL);
231− dbus_connection_unref(conn);
232− }
233− }
234−
235− if (upstart != NULL) {
236− int event_sent = 0;
237− if (m_loaded) {
238− event_sent = upstart_emit_event_sync(NULL, upstart, "indicator-services-start", NULL, 0);
239− } else {
240− event_sent = upstart_emit_event_sync(NULL, upstart, "indicator-services-end", NULL, 0);
241− }
242−
243− if (event_sent != 0) {
244− qWarning() << "Unable to send indicator event to Upstart";
245− }
246− nih_unref(upstart, NULL);
247− upstart = NULL;
248− }
249 }
216 }
250}
217}
251
218
plugins/Unity/Indicators/indicatorsmanager.h−2
@@ -28,8 +28,6 @@
28#include <QHash>
28#include <QHash>
29#include <QSharedPointer>
29#include <QSharedPointer>
30
30
31−typedef struct nih_dbus_proxy NihDBusProxy;
32−
33class UNITYINDICATORS_EXPORT IndicatorsManager : public QObject
31class UNITYINDICATORS_EXPORT IndicatorsManager : public QObject
34{
32{
35 Q_OBJECT
33 Q_OBJECT
plugins/Utils/CMakeLists.txt+2−3
@@ -11,13 +11,12 @@ include_directories(
11)
11)
12
12
13set(QMLPLUGIN_SRC
13set(QMLPLUGIN_SRC
14− albumartprovider.cpp
15 bottombarvisibilitycommunicatorshell.cpp
14 bottombarvisibilitycommunicatorshell.cpp
16− mediaartcache.cpp
17 qlimitproxymodelqml.cpp
15 qlimitproxymodelqml.cpp
18 qsortfilterproxymodelqml.cpp
16 qsortfilterproxymodelqml.cpp
19 timeformatter.cpp
17 timeformatter.cpp
20 unitymenumodelpaths.cpp
18 unitymenumodelpaths.cpp
19+ easingcurve.cpp
21 plugin.cpp
20 plugin.cpp
22 )
21 )
23
22
@@ -33,7 +32,7 @@ target_link_libraries(Utils-qml ${GIO_LDFLAGS})
33# files directly in targets.
32# files directly in targets.
34set_target_properties(Utils-qml PROPERTIES COMPILE_FLAGS -fvisibility=default)
33set_target_properties(Utils-qml PROPERTIES COMPILE_FLAGS -fvisibility=default)
35
34
36−qt5_use_modules(Utils-qml Qml Quick DBus Network XmlPatterns Concurrent)
35+qt5_use_modules(Utils-qml Qml Quick DBus Network XmlPatterns Gui Concurrent)
37
36
38# export the qmldir qmltypes and plugin files
37# export the qmldir qmltypes and plugin files
39export_qmlfiles(Utils Utils)
38export_qmlfiles(Utils Utils)
plugins/Utils/albumartprovider.cppdeleted−236
@@ -1,236 +0,0 @@
1−/*
2− * Copyright 2013 Canonical Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU Lesser General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU Lesser General Public License for more details.
12− *
13− * You should have received a copy of the GNU Lesser General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Authors: Jussi Pakkanen <jussi.pakkanen@canonical.com>
17− * Pawel Stolowski <pawel.stolowski@canonical.com>
18−*/
19−
20−#include "albumartprovider.h"
21−#include <QString>
22−#include <QNetworkAccessManager>
23−#include <QEventLoop>
24−#include <QNetworkRequest>
25−#include <QNetworkReply>
26−#include <QXmlQuery>
27−#include <QTemporaryFile>
28−#include <QBuffer>
29−#include <QDebug>
30−#include <QFile>
31−#include <QStringList>
32−#include <QByteArray>
33−#include <QImage>
34−#include <QUrl>
35−#include <QUrlQuery>
36−#include <QtConcurrent>
37−
38−using namespace std;
39−
40−const std::string AlbumArtProvider::DEFAULT_ALBUM_ART = "/usr/share/unity/icons/album_missing.png";
41−const std::string AlbumArtProvider::UNITY_LENS_SCHEMA = "com.canonical.Unity.Lenses";
42−
43−static QByteArray download(QString url) {
44− QScopedPointer<QNetworkAccessManager> am(new QNetworkAccessManager);
45− QNetworkReply *reply = am->get(QNetworkRequest(QUrl(url)));
46− QEventLoop loop;
47− QObject::connect(am.data(), &QNetworkAccessManager::finished, &loop, &QEventLoop::quit);
48− loop.exec();
49−
50− if (reply->error() != QNetworkReply::NoError) {
51− qWarning() << "Error downloading from the network:" << reply->errorString();
52− return QByteArray();
53− }
54− return reply->readAll();
55−}
56−
57−AlbumArtProvider::AlbumArtProvider()
58− : QQuickImageProvider(QQmlImageProviderBase::Image, QQmlImageProviderBase::ForceAsynchronousImageLoading),
59− m_settings(nullptr)
60−{
61− auto schemas = g_settings_list_schemas();
62− if (schemas) {
63− for (int i = 0; schemas[i]; i++) {
64− if (g_strcmp0(schemas[i], UNITY_LENS_SCHEMA.c_str()) == 0) {
65− m_settings = g_settings_new(UNITY_LENS_SCHEMA.c_str());
66− break;
67− }
68− }
69− }
70− if (m_settings == nullptr) {
71− qWarning() << "Missing" << QString::fromStdString(UNITY_LENS_SCHEMA) << "schema";
72− }
73−}
74−
75−AlbumArtProvider::~AlbumArtProvider()
76−{
77− if (m_settings)
78− g_object_unref(m_settings);
79−}
80−
81−std::string AlbumArtProvider::get_lastfm_url(const albuminfo &ai) {
82− QString artist = QString::fromStdString(ai.artist);
83− QString album = QString::fromStdString(ai.album);
84− // audioscrobbler wants it without any / (as it would break the URL)
85− artist.remove('/');
86− album.remove('/');
87−
88− /// @todo: this is the old API which will probably get axed at some point in the future
89− /// The new 2.0 API requires an API key, but supports JSON output, etc, so switching
90− /// to it should be done ASAP.
91− QString request = QString("http://ws.audioscrobbler.com/1.0/album/%1/%2/info.xml").arg(artist)
92− .arg(album)
93− .toHtmlEscaped();
94− QFuture<QByteArray> future = QtConcurrent::run(download, request);
95− QByteArray arr = future.result();
96− if(arr.size() == 0) {
97− return "";
98− }
99− QXmlQuery query;
100− QBuffer tmp;
101− tmp.setData(arr);
102− tmp.open(QIODevice::ReadOnly);
103− query.bindVariable("reply", &tmp);
104− query.setQuery("doc($reply)/album/coverart/large[1]/text()");
105− QString image;
106− query.evaluateTo(&image);
107− image = image.trimmed();
108−
109− // return empty if last.fm tries to offer their default
110− // fallback artwork
111− if (image.contains("catalogue/noimage")) {
112− return "";
113− }
114−
115− return image.toStdString();
116−}
117−
118−bool AlbumArtProvider::download_and_store(const std::string &image_url, const std::string &output_file) {
119− QString url = QString::fromStdString(image_url);
120− QString fileName = QString::fromStdString(output_file);
121− QFuture<QByteArray> future = QtConcurrent::run(download, url);
122− QByteArray arr = future.result();
123− if (arr.size() == 0) {
124− return false;
125− }
126− QFile file(fileName);
127− if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
128− qWarning() << "Could not open file for writing:" << file.errorString();
129− return false;
130− }
131− if (file.write(arr) == -1){
132− qWarning() << "Could not write the image:" << file.error();
133− return false;
134− }
135−
136− return true;
137−}
138−
139−void AlbumArtProvider::fix_format(const std::string &fname) {
140− // MediaArtSpec requires jpg. Convert to it if necessary.
141− FILE *f = fopen(fname.c_str(), "r");
142− if(!f)
143− return;
144− unsigned char buf[2];
145− fread(buf, 1, 2, f);
146− fclose(f);
147− if(buf[0] == 0xff && buf[1] == 0xd8) {
148− return;
149− }
150− QImage im(fname.c_str());
151− im.save(fname.c_str(), "JPEG");
152−}
153−
154−std::string AlbumArtProvider::get_image(const std::string &artist, const std::string &album) {
155− albuminfo info;
156−
157− info.artist = artist;
158− info.album = album;
159−
160− if(info.album.empty() || info.artist.empty()) {
161− return DEFAULT_ALBUM_ART;
162− }
163− if(cache.has_art(info.artist, info.album)) {
164− // Image may have expired from cache between these two lines.
165− // It might expire before we return. C'est la vie.
166− return cache.get_art_file(info.artist, info.album);
167− }
168−
169− if (m_settings != nullptr && g_strcmp0(g_settings_get_string(m_settings, "remote-content-search"), "all") !=0) {
170− qDebug() << "Remote content disabled";
171− return DEFAULT_ALBUM_ART;
172− }
173−
174− std::string image_url = get_lastfm_url(info);
175− if(image_url.empty()) {
176− return DEFAULT_ALBUM_ART;
177− }
178− QTemporaryFile tempFile;
179− tempFile.open();
180− tempFile.setAutoRemove(true);
181− QString fname = tempFile.fileName();
182− std::string tmpname = fname.toUtf8().data();
183−
184− if(!download_and_store(image_url, tmpname)) {
185− return DEFAULT_ALBUM_ART;
186− }
187− fix_format(tmpname.c_str());
188− QFile f(fname);
189− if(!f.open(QIODevice::ReadWrite)) {
190− return DEFAULT_ALBUM_ART;
191− }
192− QByteArray arr = f.readAll();
193− f.close();
194−
195− cache.add_art(info.artist, info.album, arr.data(), arr.size());
196− try {
197− std::string res = cache.get_art_file(info.artist, info.album);
198− if(res.empty())
199− return DEFAULT_ALBUM_ART;
200− return res;
201− } catch(...) {
202− }
203− return DEFAULT_ALBUM_ART;
204−}
205−
206−QImage AlbumArtProvider::requestImage(const QString &id, QSize *realSize, const QSize &requestedSize) {
207− Q_UNUSED(requestedSize)
208−
209− QUrlQuery query(id);
210− if (!query.hasQueryItem(QStringLiteral("artist")) || !query.hasQueryItem(QStringLiteral("album"))) {
211− qWarning() << "Invalid albumart uri:" << id;
212− return QImage(QString::fromStdString(DEFAULT_ALBUM_ART));
213− }
214− const QString artist = query.queryItemValue(QStringLiteral("artist"), QUrl::FullyEncoded);
215− const QString album = query.queryItemValue(QStringLiteral("album"), QUrl::FullyEncoded);
216−
217− std::string tgt_path;
218− try {
219− tgt_path = get_image(artist.toStdString(), album.toStdString());
220− if(!tgt_path.empty()) {
221− QImage image;
222− image.load(QString::fromStdString(tgt_path));
223− // FIXME: Rescale to requested size preserving aspect.
224− *realSize = image.size();
225− return image;
226− }
227− } catch(std::exception &e) {
228− qDebug() << "Album art loader failed: " << e.what();
229− } catch(...) {
230− qDebug() << "Unknown error when generating image.";
231− }
232−
233− QImage fallback(QString::fromStdString(DEFAULT_ALBUM_ART));
234− *realSize = fallback.size();
235− return fallback;
236−}
plugins/Utils/albumartprovider.hdeleted−56
@@ -1,56 +0,0 @@
1−/*
2− * Copyright 2013 Canonical Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU Lesser General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU Lesser General Public License for more details.
12− *
13− * You should have received a copy of the GNU Lesser General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Authors: Jussi Pakkanen <jussi.pakkanen@canonical.com>
17−*/
18−
19−#ifndef ALBUMART_PROVIDER_H_
20−#define ALBUMART_PROVIDER_H_
21−
22−#include <string>
23−#include <stdexcept>
24−#include <QQuickImageProvider>
25−#include <gio/gio.h>
26−
27−#include "mediaartcache.h"
28−
29−struct albuminfo {
30− std::string artist;
31− std::string album;
32−};
33−
34−class AlbumArtProvider : public QQuickImageProvider
35−{
36−public:
37− AlbumArtProvider();
38− ~AlbumArtProvider();
39− QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize);
40−
41− std::string get_image(const std::string &artist, const std::string &album);
42−
43− const static std::string DEFAULT_ALBUM_ART;
44− const static std::string UNITY_LENS_SCHEMA;
45−private:
46− MediaArtCache cache;
47− GSettings *m_settings;
48−
49− void fix_format(const std::string &fname);
50−
51− std::string get_lastfm_url(const albuminfo &ai);
52−
53− bool download_and_store(const std::string &image_url, const std::string &output_file);
54−};
55−
56−#endif
plugins/Utils/easingcurve.cppnew file+67
@@ -0,0 +1,67 @@
1+/*
2+ * Copyright 2014 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Authors: Michael Zanetti <michael.zanetti@canonical.com>
17+*/
18+
19+#include "easingcurve.h"
20+
21+
22+EasingCurve::EasingCurve(QObject *parent):
23+ QObject(parent)
24+{
25+
26+}
27+
28+QEasingCurve::Type EasingCurve::type() const
29+{
30+ return m_easingCurve.type();
31+}
32+
33+void EasingCurve::setType(const QEasingCurve::Type &type)
34+{
35+ m_easingCurve.setType(type);
36+ Q_EMIT typeChanged();
37+}
38+
39+qreal EasingCurve::period() const
40+{
41+ return m_easingCurve.period();
42+}
43+
44+void EasingCurve::setPeriod(qreal period)
45+{
46+ m_easingCurve.setPeriod(period);
47+ Q_EMIT periodChanged();
48+}
49+
50+qreal EasingCurve::progress() const
51+{
52+ return m_progress;
53+}
54+
55+void EasingCurve::setProgress(qreal progress)
56+{
57+ if (m_progress != progress) {
58+ m_progress = progress;
59+ m_value = m_easingCurve.valueForProgress(m_progress);
60+ Q_EMIT progressChanged();
61+ }
62+}
63+
64+qreal EasingCurve::value() const
65+{
66+ return m_value;
67+}
plugins/Utils/easingcurve.hnew file+70
@@ -0,0 +1,70 @@
1+/*
2+ * Copyright 2014 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Authors: Michael Zanetti <michael.zanetti@canonical.com>
17+*/
18+
19+#ifndef EASINGCURVE_H
20+#define EASINGCURVE_H
21+
22+#include <QObject>
23+#include <QEasingCurve>
24+
25+/**
26+ * @brief The EasingCurve class
27+ *
28+ * This class exposes the QEasingCurve C++ API to QML.
29+ * This is useful for user interactive animations. While the QML Animation types
30+ * all require a "from", "to" and "duration", this one is based on "period" and
31+ * "progress". So you can control the position of the aimation by changing the
32+ * progress, also going back and forward in the animation. Depending on the type
33+ * of the easing curve, value will return the transformed progress.
34+ */
35+
36+class EasingCurve: public QObject
37+{
38+ Q_OBJECT
39+ Q_ENUMS(QEasingCurve::Type)
40+ Q_PROPERTY(QEasingCurve::Type type READ type WRITE setType NOTIFY typeChanged)
41+ Q_PROPERTY(qreal period READ period WRITE setPeriod NOTIFY periodChanged)
42+ Q_PROPERTY(qreal progress READ progress WRITE setProgress NOTIFY progressChanged)
43+ Q_PROPERTY(qreal value READ value NOTIFY progressChanged)
44+
45+public:
46+ EasingCurve(QObject *parent = 0);
47+
48+ QEasingCurve::Type type() const;
49+ void setType(const QEasingCurve::Type &type);
50+
51+ qreal period() const;
52+ void setPeriod(qreal period);
53+
54+ qreal progress() const;
55+ void setProgress(qreal progress);
56+
57+ qreal value() const;
58+
59+Q_SIGNALS:
60+ void typeChanged();
61+ void periodChanged();
62+ void progressChanged();
63+
64+private:
65+ QEasingCurve m_easingCurve;
66+ qreal m_progress;
67+ qreal m_value;
68+};
69+
70+#endif
plugins/Utils/mediaartcache.cppdeleted−178
@@ -1,178 +0,0 @@
1−/*
2− * This file is part of the Ubuntu TV Media Scanner
3− * Copyright (C) 2012-2013 Canonical Ltd.
4− *
5− * This program is free software: you can redistribute it and/or modify
6− * it under the terms of the GNU Lesser General Public License version 3 as
7− * published by the Free Software Foundation.
8− *
9− * This program is distributed in the hope that it will be useful,
10− * but WITHOUT ANY WARRANTY; without even the implied warranty of
11− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12− * GNU Lesser General Public License for more details.
13− *
14− * You should have received a copy of the GNU Lesser General Public License
15− * along with this program. If not, see <http://www.gnu.org/licenses/>.
16− *
17− * Authored by: Jussi Pakkanen <jussi.pakkanen@canonical.com>
18− */
19−
20−#include"mediaartcache.h"
21−
22−#include<dirent.h>
23−#include<sys/stat.h>
24−#include<fcntl.h>
25−#include<unistd.h>
26−#include<errno.h>
27−#include<stdio.h>
28−#include<string.h>
29−#include<stdlib.h>
30−#include<assert.h>
31−#include<utime.h>
32−
33−#include<glib.h>
34−
35−#include<vector>
36−#include<algorithm>
37−#include<stdexcept>
38−
39−using namespace std;
40−
41−static string md5(const string &str) {
42− const unsigned char *buf = (const unsigned char *)str.c_str();
43− char *normalized = g_utf8_normalize((const gchar*)buf, str.size(), G_NORMALIZE_ALL);
44− string final;
45− gchar *result;
46−
47− if(normalized) {
48− buf = (const unsigned char*)normalized;
49− }
50− gssize bytes = str.length();
51−
52− result = g_compute_checksum_for_data(G_CHECKSUM_MD5, buf, bytes);
53− final = result;
54− g_free((gpointer)normalized);
55− g_free(result);
56− return final;
57−}
58−
59−MediaArtCache::MediaArtCache() {
60− string xdg_base = g_get_user_cache_dir();
61−
62− if (xdg_base == "") {
63− string s("Could not determine cache dir.");
64− throw runtime_error(s);
65− }
66− int ec = mkdir(xdg_base.c_str(), S_IRUSR | S_IWUSR | S_IXUSR);
67− if (ec < 0 && errno != EEXIST) {
68− string s("Could not create base dir.");
69− throw runtime_error(s);
70− }
71− root_dir = xdg_base + "/media-art";
72− ec = mkdir(root_dir.c_str(), S_IRUSR | S_IWUSR | S_IXUSR);
73− if (ec < 0 && errno != EEXIST) {
74− string s("Could not create cache dir.");
75− throw runtime_error(s);
76− }
77−}
78−
79−bool MediaArtCache::has_art(const std::string &artist, const std::string &album) const {
80− string fname = get_art_file(artist, album);
81− return access(fname.c_str(), R_OK) == 0;
82−}
83−
84−void MediaArtCache::add_art(const std::string &artist, const std::string &album,
85− char *data, unsigned int datalen) {
86− string abs_fname = get_full_filename(artist, album);
87− GError *err = nullptr;
88− if(!g_file_set_contents(abs_fname.c_str(), data, datalen, &err)) {
89− string e("Could not write file ");
90− e += abs_fname;
91− e += ": ";
92− e += err->message;
93− g_error_free(err);
94− throw runtime_error(e);
95− }
96−}
97−
98−string MediaArtCache::get_art_file(const std::string &artist, const std::string &album) const {
99− string abs_fname = get_full_filename(artist, album);
100−
101− if (access(abs_fname.c_str(), R_OK) == 0) {
102− utime(abs_fname.c_str(), nullptr); // update access times to current time
103− return abs_fname;
104− }
105− return "";
106−}
107−
108−std::string MediaArtCache::get_full_filename(const std::string &artist, const std::string & album) const {
109− return root_dir + "/" + compute_base_name(artist, album);
110−}
111−
112−
113−std::string MediaArtCache::compute_base_name(const std::string &artist, const std::string &album) const {
114− string type = "album";
115− string h1 = md5(artist);
116− string h2 = md5(album);
117− return type + "-" + h1 + "-" + h2 + ".jpg";
118−}
119−
120−
121−void MediaArtCache::clear() const {
122− DIR *d = opendir(root_dir.c_str());
123− if(!d) {
124− string s = "Something went wrong.";
125− throw runtime_error(s);
126− }
127− struct dirent *entry, *de;
128− entry = (dirent*)malloc(sizeof(dirent) + NAME_MAX);
129− while(readdir_r(d, entry, &de) == 0 && de) {
130− string basename = entry->d_name;
131− if (basename == "." || basename == "..")
132− continue;
133− string fname = root_dir + "/" + basename;
134− if(remove(fname.c_str()) < 0) {
135− // This is not really an error worth
136− // halting everything for.
137− fprintf(stderr, "Could not delete file %s: %s.\n", fname.c_str(),
138− strerror(errno));
139− }
140− }
141− free(entry);
142− closedir(d);
143−}
144−
145−void MediaArtCache::prune() {
146− vector<pair<double, string>> mtimes;
147− DIR *d = opendir(root_dir.c_str());
148− if(!d) {
149− string s = "Something went wrong.";
150− throw runtime_error(s);
151− }
152− struct dirent *entry, *de;
153− entry = (dirent*)malloc(sizeof(dirent) + NAME_MAX);
154− while(readdir_r(d, entry, &de) == 0 && de) {
155− string basename = entry->d_name;
156− if (basename == "." || basename == "..")
157− continue;
158− string fname = root_dir + "/" + basename;
159− struct stat sbuf;
160− if(stat(fname.c_str(), &sbuf) != 0) {
161− continue;
162− }
163− // Use mtime because atime is not guaranteed to work if, for example
164− // the filesystem is mounted with noatime or relatime.
165− mtimes.push_back(make_pair(sbuf.st_mtim.tv_sec + sbuf.st_mtim.tv_nsec/1000000000.0, fname));
166− }
167− free(entry);
168− closedir(d);
169− if (mtimes.size() <= MAX_SIZE)
170− return;
171− sort(mtimes.begin(), mtimes.end());
172− for(size_t i=0; i < mtimes.size()-MAX_SIZE; i++) {
173− if(remove(mtimes[i].second.c_str()) < 0) {
174− fprintf(stderr, "Could not remove file %s: %s.\n",
175− mtimes[i].second.c_str(), strerror(errno));
176− }
177− }
178−}
plugins/Utils/mediaartcache.hdeleted−54
@@ -1,54 +0,0 @@
1−/*
2− * This file is part of the Ubuntu TV Media Scanner
3− * Copyright (C) 2012-2013 Canonical Ltd.
4− *
5− * This program is free software: you can redistribute it and/or modify
6− * it under the terms of the GNU Lesser General Public License version 3 as
7− * published by the Free Software Foundation.
8− *
9− * This program is distributed in the hope that it will be useful,
10− * but WITHOUT ANY WARRANTY; without even the implied warranty of
11− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12− * GNU Lesser General Public License for more details.
13− *
14− * You should have received a copy of the GNU Lesser General Public License
15− * along with this program. If not, see <http://www.gnu.org/licenses/>.
16− *
17− * Authored by: Jussi Pakkanen <jussi.pakkanen@canonical.com>
18− */
19−
20−#ifndef MEDIAARTCACHE_H
21−#define MEDIAARTCACHE_H
22−
23−#include<string>
24−
25−/*
26− * A class to store thumbnails for files according to
27− * https://wiki.gnome.org/MediaArtStorageSpec
28− *
29− * As this class deals mostly with the filesystem, all
30− * errors are reported with runtime_error exceptions.
31− */
32−
33−
34−class MediaArtCache {
35−private:
36− std::string root_dir;
37−
38− std::string compute_base_name(const std::string &artist, const std::string &album) const;
39− std::string get_full_filename(const std::string &artist, const std::string & album) const;
40−
41−public:
42− static const unsigned int MAX_SIZE = 200;
43−
44− MediaArtCache();
45− bool has_art(const std::string &artist, const std::string &album) const;
46− void add_art(const std::string &artist, const std::string &album,
47− char *data, unsigned int datalen);
48− std::string get_art_file(const std::string &artist, const std::string &album) const;
49− void clear() const;
50− void prune();
51− std::string get_cache_dir() const { return root_dir; }
52−};
53−
54−#endif
plugins/Utils/plugin.cpp+2−14
@@ -26,12 +26,12 @@
26#include "plugin.h"
26#include "plugin.h"
27
27
28// local
28// local
29−#include "albumartprovider.h"
30#include "bottombarvisibilitycommunicatorshell.h"
29#include "bottombarvisibilitycommunicatorshell.h"
31#include "qlimitproxymodelqml.h"
30#include "qlimitproxymodelqml.h"
32#include "qsortfilterproxymodelqml.h"
31#include "qsortfilterproxymodelqml.h"
33#include "timeformatter.h"
32#include "timeformatter.h"
34#include "unitymenumodelpaths.h"
33#include "unitymenumodelpaths.h"
34+#include "easingcurve.h"
35
35
36static const char* BOTTOM_BAR_VISIBILITY_COMMUNICATOR_DBUS_PATH = "/BottomBarVisibilityCommunicator";
36static const char* BOTTOM_BAR_VISIBILITY_COMMUNICATOR_DBUS_PATH = "/BottomBarVisibilityCommunicator";
37static const char* DBUS_SERVICE = "com.canonical.Shell.BottomBarVisibilityCommunicator";
37static const char* DBUS_SERVICE = "com.canonical.Shell.BottomBarVisibilityCommunicator";
@@ -46,25 +46,13 @@ void UtilsPlugin::registerTypes(const char *uri)
46 qmlRegisterType<TimeFormatter>(uri, 0, 1, "TimeFormatter");
46 qmlRegisterType<TimeFormatter>(uri, 0, 1, "TimeFormatter");
47 qmlRegisterType<GDateTimeFormatter>(uri, 0, 1, "GDateTimeFormatter");
47 qmlRegisterType<GDateTimeFormatter>(uri, 0, 1, "GDateTimeFormatter");
48 qmlRegisterUncreatableType<BottomBarVisibilityCommunicatorShell>(uri, 0, 1, "BottomBarVisibilityCommunicatorShell", "Can't create BottomBarVisibilityCommunicatorShell");
48 qmlRegisterUncreatableType<BottomBarVisibilityCommunicatorShell>(uri, 0, 1, "BottomBarVisibilityCommunicatorShell", "Can't create BottomBarVisibilityCommunicatorShell");
49+ qmlRegisterType<EasingCurve>(uri, 0, 1, "EasingCurve");
49}
50}
50
51
51void UtilsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
52void UtilsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
52{
53{
53 QQmlExtensionPlugin::initializeEngine(engine, uri);
54 QQmlExtensionPlugin::initializeEngine(engine, uri);
54
55
55− try
56− {
57− engine->addImageProvider(QLatin1String("albumart"), new AlbumArtProvider);
58− }
59− catch (const std::runtime_error &e)
60− {
61− qWarning() << "Failed to register image provider for albumart:" << e.what();
62− }
63− catch (...)
64− {
65− qWarning() << "Failed to register image provider for albumart (unknown error)";
66− }
67−
68 QDBusConnection::sessionBus().registerService(DBUS_SERVICE);
56 QDBusConnection::sessionBus().registerService(DBUS_SERVICE);
69 BottomBarVisibilityCommunicatorShell *bottomBarVisibilityCommunicator = &BottomBarVisibilityCommunicatorShell::instance();
57 BottomBarVisibilityCommunicatorShell *bottomBarVisibilityCommunicator = &BottomBarVisibilityCommunicatorShell::instance();
70 QDBusConnection::sessionBus().registerObject(BOTTOM_BAR_VISIBILITY_COMMUNICATOR_DBUS_PATH, bottomBarVisibilityCommunicator, QDBusConnection::ExportAllContents);
58 QDBusConnection::sessionBus().registerObject(BOTTOM_BAR_VISIBILITY_COMMUNICATOR_DBUS_PATH, bottomBarVisibilityCommunicator, QDBusConnection::ExportAllContents);
po/ast.po+5−22
@@ -8,15 +8,14 @@ msgstr ""
8"Project-Id-Version: unity8\n"
8"Project-Id-Version: unity8\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2014-03-25 16:48+0100\n"
10"POT-Creation-Date: 2014-03-25 16:48+0100\n"
11−"PO-Revision-Date: 2014-03-01 16:51+0000\n"
11+"PO-Revision-Date: 2014-04-04 02:16+0000\n"
12"Last-Translator: Xuacu Saturio <xuacusk8@gmail.com>\n"
12"Last-Translator: Xuacu Saturio <xuacusk8@gmail.com>\n"
13"Language-Team: Asturian <ast@li.org>\n"
13"Language-Team: Asturian <ast@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17−"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2014-03-28 06:48+0000\n"
17+"X-Launchpad-Export-Date: 2014-04-08 07:13+0000\n"
19−"X-Generator: Launchpad (build 16967)\n"
18+"X-Generator: Launchpad (build 16976)\n"
20
19
21#: plugins/Unity/Launcher/launcheritem.cpp:43
20#: plugins/Unity/Launcher/launcheritem.cpp:43
22#: plugins/Unity/Launcher/launcheritem.cpp:73
21#: plugins/Unity/Launcher/launcheritem.cpp:73
@@ -91,12 +90,12 @@ msgstr "Ver menos"
91
90
92#: qml/Dash/DashApps.qml:38
91#: qml/Dash/DashApps.qml:38
93msgid "Recent"
92msgid "Recent"
94−msgstr ""
93+msgstr "Reciente"
95
94
96#: qml/Dash/PreviewListView.qml:52
95#: qml/Dash/PreviewListView.qml:52
97#, qt-format
96#, qt-format
98msgid "%1 Preview"
97msgid "%1 Preview"
99−msgstr ""
98+msgstr "Vista previa de %1"
100
99
101#: qml/Dash/Previews/PreviewActionCombo.qml:34
100#: qml/Dash/Previews/PreviewActionCombo.qml:34
102msgid "More..."
101msgid "More..."
@@ -167,19 +166,3 @@ msgstr "Guetar"
167#, qt-format
166#, qt-format
168msgid "Please enter %1"
167msgid "Please enter %1"
169msgstr "Introduz %1"
168msgstr "Introduz %1"
170−
171−#, qt-format
172−#~ msgid "(%1)"
173−#~ msgstr "(%1)"
174−
175−#~ msgid "Comments:"
176−#~ msgstr "Comentarios:"
177−
178−#~ msgid "Review"
179−#~ msgstr "Revisar"
180−
181−#, qt-format
182−#~ msgid "%1 review"
183−#~ msgid_plural "%1 reviews"
184−#~ msgstr[0] "%1 reseña"
185−#~ msgstr[1] "%1 reseñes"
po/bg.po+4−21
@@ -8,14 +8,13 @@ msgstr ""
8"Project-Id-Version: unity\n"
8"Project-Id-Version: unity\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2014-03-25 16:48+0100\n"
10"POT-Creation-Date: 2014-03-25 16:48+0100\n"
11−"PO-Revision-Date: 2014-01-28 19:22+0000\n"
11+"PO-Revision-Date: 2014-04-01 14:08+0000\n"
12"Last-Translator: Atanas Kovachki <Unknown>\n"
12"Last-Translator: Atanas Kovachki <Unknown>\n"
13"Language-Team: Bulgarian <bg@li.org>\n"
13"Language-Team: Bulgarian <bg@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17−"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2014-03-28 06:48+0000\n"
17+"X-Launchpad-Export-Date: 2014-04-03 06:06+0000\n"
19"X-Generator: Launchpad (build 16967)\n"
18"X-Generator: Launchpad (build 16967)\n"
20
19
21#: plugins/Unity/Launcher/launcheritem.cpp:43
20#: plugins/Unity/Launcher/launcheritem.cpp:43
@@ -94,7 +93,7 @@ msgstr ""
94
93
95#: qml/Dash/DashApps.qml:38
94#: qml/Dash/DashApps.qml:38
96msgid "Recent"
95msgid "Recent"
97−msgstr ""
96+msgstr "Последни"
98
97
99#: qml/Dash/PreviewListView.qml:52
98#: qml/Dash/PreviewListView.qml:52
100#, qt-format
99#, qt-format
@@ -107,7 +106,7 @@ msgstr ""
107
106
108#: qml/Dash/Previews/PreviewActionCombo.qml:34
107#: qml/Dash/Previews/PreviewActionCombo.qml:34
109msgid "Less..."
108msgid "Less..."
110−msgstr ""
109+msgstr "По-малко..."
111
110
112#: qml/Dash/Previews/PreviewRatingInput.qml:81
111#: qml/Dash/Previews/PreviewRatingInput.qml:81
113msgid "Rate this"
112msgid "Rate this"
@@ -170,19 +169,3 @@ msgstr "Търсене"
170#, qt-format
169#, qt-format
171msgid "Please enter %1"
170msgid "Please enter %1"
172msgstr "Моля, въведете %1"
171msgstr "Моля, въведете %1"
173−
174−#~ msgid "Review"
175−#~ msgstr "Отзив"
176−
177−#, qt-format
178−#~ msgid "%1 review"
179−#~ msgid_plural "%1 reviews"
180−#~ msgstr[0] "%1 отзив"
181−#~ msgstr[1] "%1 отзива"
182−
183−#, qt-format
184−#~ msgid "(%1)"
185−#~ msgstr "(%1)"
186−
187−#~ msgid "Comments:"
188−#~ msgstr "Коментари:"
po/br.ponew file+164
@@ -0,0 +1,164 @@
1+# Breton translation for unity8
2+# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014
3+# This file is distributed under the same license as the unity8 package.
4+# FIRST AUTHOR <EMAIL@ADDRESS>, 2014.
5+#
6+msgid ""
7+msgstr ""
8+"Project-Id-Version: unity8\n"
9+"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10+"POT-Creation-Date: 2014-03-25 16:48+0100\n"
11+"PO-Revision-Date: 2014-04-15 18:15+0000\n"
12+"Last-Translator: Rouxel <vincent.rouxel@mailoo.org>\n"
13+"Language-Team: Breton <br@li.org>\n"
14+"MIME-Version: 1.0\n"
15+"Content-Type: text/plain; charset=UTF-8\n"
16+"Content-Transfer-Encoding: 8bit\n"
17+"X-Launchpad-Export-Date: 2014-04-16 07:12+0000\n"
18+"X-Generator: Launchpad (build 16976)\n"
19+
20+#: plugins/Unity/Launcher/launcheritem.cpp:43
21+#: plugins/Unity/Launcher/launcheritem.cpp:73
22+msgid "Lock to Launcher"
23+msgstr ""
24+
25+#: plugins/Unity/Launcher/launcheritem.cpp:73
26+msgid "Unlock from Launcher"
27+msgstr ""
28+
29+#: qml/Components/EdgeDemoOverlay.qml:151
30+msgid "Skip intro"
31+msgstr ""
32+
33+#: qml/Components/EdgeDemo.qml:113
34+msgid "Right edge"
35+msgstr ""
36+
37+#: qml/Components/EdgeDemo.qml:114
38+msgid "Try swiping from the right edge to unlock the phone"
39+msgstr ""
40+
41+#: qml/Components/EdgeDemo.qml:145
42+msgid "Top edge"
43+msgstr ""
44+
45+#: qml/Components/EdgeDemo.qml:146
46+msgid "Try swiping from the top edge to access the indicators"
47+msgstr ""
48+
49+#: qml/Components/EdgeDemo.qml:171
50+msgid "Close"
51+msgstr "Serriñ"
52+
53+#: qml/Components/EdgeDemo.qml:172
54+msgid "Swipe up again to close the settings screen"
55+msgstr ""
56+
57+#: qml/Components/EdgeDemo.qml:199
58+msgid "Left edge"
59+msgstr ""
60+
61+#: qml/Components/EdgeDemo.qml:200
62+msgid "Swipe from the left to reveal the launcher for quick access to apps"
63+msgstr ""
64+
65+#: qml/Components/EdgeDemo.qml:227
66+msgid "Well done"
67+msgstr ""
68+
69+#: qml/Components/EdgeDemo.qml:228
70+msgid ""
71+"You have now mastered the edge gestures and can start using the "
72+"phone<br><br>Tap on the screen to start"
73+msgstr ""
74+
75+#: qml/Components/PageHeader.qml:365
76+msgid "Recent searches"
77+msgstr ""
78+
79+#: qml/Components/SeeMore.qml:35
80+msgid "See more"
81+msgstr ""
82+
83+#: qml/Components/SeeMore.qml:58
84+msgid "See less"
85+msgstr ""
86+
87+#: qml/Dash/DashApps.qml:38
88+msgid "Recent"
89+msgstr ""
90+
91+#: qml/Dash/PreviewListView.qml:52
92+#, qt-format
93+msgid "%1 Preview"
94+msgstr ""
95+
96+#: qml/Dash/Previews/PreviewActionCombo.qml:34
97+msgid "More..."
98+msgstr ""
99+
100+#: qml/Dash/Previews/PreviewActionCombo.qml:34
101+msgid "Less..."
102+msgstr ""
103+
104+#: qml/Dash/Previews/PreviewRatingInput.qml:81
105+msgid "Rate this"
106+msgstr ""
107+
108+#: qml/Dash/Previews/PreviewRatingInput.qml:126
109+msgid "Add a review"
110+msgstr ""
111+
112+#: qml/Dash/Previews/PreviewRatingInput.qml:166
113+msgid "Send"
114+msgstr ""
115+
116+#: qml/Greeter/Lockscreen.qml:149
117+msgid "Emergency Call"
118+msgstr ""
119+
120+#: qml/Greeter/PassphraseLockscreen.qml:59
121+#, qt-format
122+msgid "Hello %1"
123+msgstr ""
124+
125+#: qml/Greeter/PassphraseLockscreen.qml:59
126+msgid "Hello"
127+msgstr ""
128+
129+#: qml/Hud/HudParametrizedActionsPage.qml:132
130+msgid "Confirm"
131+msgstr ""
132+
133+#: qml/Hud/Hud.qml:61 qml/Hud/Hud.qml:99 qml/Hud/Hud.qml:372
134+msgid "Type or say a command"
135+msgstr ""
136+
137+#: qml/Hud/Hud.qml:84
138+msgid "Loading. Please Wait..."
139+msgstr ""
140+
141+#: qml/Hud/Hud.qml:89
142+msgid "Speak Now..."
143+msgstr ""
144+
145+#: qml/Hud/Hud.qml:94
146+msgid "Speaking..."
147+msgstr ""
148+
149+#: qml/Notifications/NotificationMenuItemFactory.qml:91
150+msgid "Show password"
151+msgstr ""
152+
153+#: qml/Notifications/NotificationMenuItemFactory.qml:103
154+msgid "Please enter SIM PIN"
155+msgstr ""
156+
157+#: qml/Panel/SearchIndicator.qml:27
158+msgid "Search"
159+msgstr ""
160+
161+#: qml/Shell.qml:417
162+#, qt-format
163+msgid "Please enter %1"
164+msgstr ""
po/es.po+4−21
@@ -8,14 +8,13 @@ msgstr ""
8"Project-Id-Version: unity\n"
8"Project-Id-Version: unity\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2014-03-25 16:48+0100\n"
10"POT-Creation-Date: 2014-03-25 16:48+0100\n"
11−"PO-Revision-Date: 2014-02-20 05:43+0000\n"
11+"PO-Revision-Date: 2014-03-28 22:37+0000\n"
12"Last-Translator: Adolfo Jayme <fitoschido@gmail.com>\n"
12"Last-Translator: Adolfo Jayme <fitoschido@gmail.com>\n"
13"Language-Team: Spanish <es@li.org>\n"
13"Language-Team: Spanish <es@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17−"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2014-03-28 06:49+0000\n"
17+"X-Launchpad-Export-Date: 2014-03-29 07:34+0000\n"
19"X-Generator: Launchpad (build 16967)\n"
18"X-Generator: Launchpad (build 16967)\n"
20
19
21#: plugins/Unity/Launcher/launcheritem.cpp:43
20#: plugins/Unity/Launcher/launcheritem.cpp:43
@@ -92,12 +91,12 @@ msgstr "Ver menos"
92
91
93#: qml/Dash/DashApps.qml:38
92#: qml/Dash/DashApps.qml:38
94msgid "Recent"
93msgid "Recent"
95−msgstr ""
94+msgstr "Recientes"
96
95
97#: qml/Dash/PreviewListView.qml:52
96#: qml/Dash/PreviewListView.qml:52
98#, qt-format
97#, qt-format
99msgid "%1 Preview"
98msgid "%1 Preview"
100−msgstr ""
99+msgstr "Previsualización de %1"
101
100
102#: qml/Dash/Previews/PreviewActionCombo.qml:34
101#: qml/Dash/Previews/PreviewActionCombo.qml:34
103msgid "More..."
102msgid "More..."
@@ -168,19 +167,3 @@ msgstr "Buscar"
168#, qt-format
167#, qt-format
169msgid "Please enter %1"
168msgid "Please enter %1"
170msgstr "Introduzca %1"
169msgstr "Introduzca %1"
171−
172−#~ msgid "Comments:"
173−#~ msgstr "Comentarios:"
174−
175−#~ msgid "Review"
176−#~ msgstr "Reseñar"
177−
178−#, qt-format
179−#~ msgid "%1 review"
180−#~ msgid_plural "%1 reviews"
181−#~ msgstr[0] "%1 reseña"
182−#~ msgstr[1] "%1 reseñas"
183−
184−#, qt-format
185−#~ msgid "(%1)"
186−#~ msgstr "(%1)"
po/eu.po+4−21
@@ -8,14 +8,13 @@ msgstr ""
8"Project-Id-Version: unity8\n"
8"Project-Id-Version: unity8\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2014-03-25 16:48+0100\n"
10"POT-Creation-Date: 2014-03-25 16:48+0100\n"
11−"PO-Revision-Date: 2014-03-17 20:56+0000\n"
11+"PO-Revision-Date: 2014-03-30 12:44+0000\n"
12"Last-Translator: Ibai Oihanguren Sala <Unknown>\n"
12"Last-Translator: Ibai Oihanguren Sala <Unknown>\n"
13"Language-Team: Basque <eu@li.org>\n"
13"Language-Team: Basque <eu@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17−"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2014-03-28 06:48+0000\n"
17+"X-Launchpad-Export-Date: 2014-03-31 06:44+0000\n"
19"X-Generator: Launchpad (build 16967)\n"
18"X-Generator: Launchpad (build 16967)\n"
20
19
21#: plugins/Unity/Launcher/launcheritem.cpp:43
20#: plugins/Unity/Launcher/launcheritem.cpp:43
@@ -91,12 +90,12 @@ msgstr "Ikusi gutxiago"
91
90
92#: qml/Dash/DashApps.qml:38
91#: qml/Dash/DashApps.qml:38
93msgid "Recent"
92msgid "Recent"
94−msgstr ""
93+msgstr "Azkenak"
95
94
96#: qml/Dash/PreviewListView.qml:52
95#: qml/Dash/PreviewListView.qml:52
97#, qt-format
96#, qt-format
98msgid "%1 Preview"
97msgid "%1 Preview"
99−msgstr ""
98+msgstr "%1 aurrebista"
100
99
101#: qml/Dash/Previews/PreviewActionCombo.qml:34
100#: qml/Dash/Previews/PreviewActionCombo.qml:34
102msgid "More..."
101msgid "More..."
@@ -167,19 +166,3 @@ msgstr "Bilatu"
167#, qt-format
166#, qt-format
168msgid "Please enter %1"
167msgid "Please enter %1"
169msgstr "Sartu %1"
168msgstr "Sartu %1"
170−
171−#, qt-format
172−#~ msgid "(%1)"
173−#~ msgstr "(%1)"
174−
175−#~ msgid "Comments:"
176−#~ msgstr "Iruzkinak:"
177−
178−#~ msgid "Review"
179−#~ msgstr "Egin kritika"
180−
181−#, qt-format
182−#~ msgid "%1 review"
183−#~ msgid_plural "%1 reviews"
184−#~ msgstr[0] "kritika %1"
185−#~ msgstr[1] "%1 kritika"
po/fi.po+5−22
@@ -8,15 +8,14 @@ msgstr ""
8"Project-Id-Version: unity\n"
8"Project-Id-Version: unity\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2014-03-25 16:48+0100\n"
10"POT-Creation-Date: 2014-03-25 16:48+0100\n"
11−"PO-Revision-Date: 2014-03-09 19:22+0000\n"
11+"PO-Revision-Date: 2014-04-05 09:57+0000\n"
12"Last-Translator: Jiri Grönroos <Unknown>\n"
12"Last-Translator: Jiri Grönroos <Unknown>\n"
13"Language-Team: Finnish <fi@li.org>\n"
13"Language-Team: Finnish <fi@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17−"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2014-03-28 06:48+0000\n"
17+"X-Launchpad-Export-Date: 2014-04-08 07:13+0000\n"
19−"X-Generator: Launchpad (build 16967)\n"
18+"X-Generator: Launchpad (build 16976)\n"
20
19
21#: plugins/Unity/Launcher/launcheritem.cpp:43
20#: plugins/Unity/Launcher/launcheritem.cpp:43
22#: plugins/Unity/Launcher/launcheritem.cpp:73
21#: plugins/Unity/Launcher/launcheritem.cpp:73
@@ -89,12 +88,12 @@ msgstr "Näytä vähemmän"
89
88
90#: qml/Dash/DashApps.qml:38
89#: qml/Dash/DashApps.qml:38
91msgid "Recent"
90msgid "Recent"
92−msgstr ""
91+msgstr "Viimeisimmät"
93
92
94#: qml/Dash/PreviewListView.qml:52
93#: qml/Dash/PreviewListView.qml:52
95#, qt-format
94#, qt-format
96msgid "%1 Preview"
95msgid "%1 Preview"
97−msgstr ""
96+msgstr "%1 esikatselu"
98
97
99#: qml/Dash/Previews/PreviewActionCombo.qml:34
98#: qml/Dash/Previews/PreviewActionCombo.qml:34
100msgid "More..."
99msgid "More..."
@@ -165,19 +164,3 @@ msgstr "Hae"
165#, qt-format
164#, qt-format
166msgid "Please enter %1"
165msgid "Please enter %1"
167msgstr "Kirjoita %1"
166msgstr "Kirjoita %1"
168−
169−#~ msgid "Comments:"
170−#~ msgstr "Kommentit:"
171−
172−#~ msgid "Review"
173−#~ msgstr "Arvostelu"
174−
175−#, qt-format
176−#~ msgid "%1 review"
177−#~ msgid_plural "%1 reviews"
178−#~ msgstr[0] "%1 arvostelu"
179−#~ msgstr[1] "%1 arvostelua"
180−
181−#, qt-format
182−#~ msgid "(%1)"
183−#~ msgstr "(%1)"
po/gl.po+5−22
@@ -8,15 +8,14 @@ msgstr ""
8"Project-Id-Version: unity\n"
8"Project-Id-Version: unity\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2014-03-25 16:48+0100\n"
10"POT-Creation-Date: 2014-03-25 16:48+0100\n"
11−"PO-Revision-Date: 2014-03-08 06:58+0000\n"
11+"PO-Revision-Date: 2014-04-08 07:04+0000\n"
12"Last-Translator: Marcos Lans <Unknown>\n"
12"Last-Translator: Marcos Lans <Unknown>\n"
13"Language-Team: Galician <gl@li.org>\n"
13"Language-Team: Galician <gl@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17−"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2014-03-28 06:48+0000\n"
17+"X-Launchpad-Export-Date: 2014-04-09 07:31+0000\n"
19−"X-Generator: Launchpad (build 16967)\n"
18+"X-Generator: Launchpad (build 16976)\n"
20
19
21#: plugins/Unity/Launcher/launcheritem.cpp:43
20#: plugins/Unity/Launcher/launcheritem.cpp:43
22#: plugins/Unity/Launcher/launcheritem.cpp:73
21#: plugins/Unity/Launcher/launcheritem.cpp:73
@@ -89,12 +88,12 @@ msgstr "Ver menos"
89
88
90#: qml/Dash/DashApps.qml:38
89#: qml/Dash/DashApps.qml:38
91msgid "Recent"
90msgid "Recent"
92−msgstr ""
91+msgstr "Recentes"
93
92
94#: qml/Dash/PreviewListView.qml:52
93#: qml/Dash/PreviewListView.qml:52
95#, qt-format
94#, qt-format
96msgid "%1 Preview"
95msgid "%1 Preview"
97−msgstr ""
96+msgstr "%1 previsualización"
98
97
99#: qml/Dash/Previews/PreviewActionCombo.qml:34
98#: qml/Dash/Previews/PreviewActionCombo.qml:34
100msgid "More..."
99msgid "More..."
@@ -165,19 +164,3 @@ msgstr "Buscar"
165#, qt-format
164#, qt-format
166msgid "Please enter %1"
165msgid "Please enter %1"
167msgstr "Introduza %1"
166msgstr "Introduza %1"
168−
169−#, qt-format
170−#~ msgid "(%1)"
171−#~ msgstr "(%1)"
172−
173−#~ msgid "Comments:"
174−#~ msgstr "Comentarios:"
175−
176−#~ msgid "Review"
177−#~ msgstr "Opinión"
178−
179−#, qt-format
180−#~ msgid "%1 review"
181−#~ msgid_plural "%1 reviews"
182−#~ msgstr[0] "%1 opinión"
183−#~ msgstr[1] "%1 opinións"
po/ru.po+12−29
@@ -8,16 +8,14 @@ msgstr ""
8"Project-Id-Version: unity\n"
8"Project-Id-Version: unity\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2014-03-25 16:48+0100\n"
10"POT-Creation-Date: 2014-03-25 16:48+0100\n"
11−"PO-Revision-Date: 2014-01-26 15:56+0000\n"
11+"PO-Revision-Date: 2014-04-03 13:07+0000\n"
12"Last-Translator: Zonov Roman <Unknown>\n"
12"Last-Translator: Zonov Roman <Unknown>\n"
13"Language-Team: Russian <ru@li.org>\n"
13"Language-Team: Russian <ru@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17−"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
18−"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
19−"X-Launchpad-Export-Date: 2014-03-28 06:49+0000\n"
17+"X-Launchpad-Export-Date: 2014-04-04 07:12+0000\n"
20−"X-Generator: Launchpad (build 16967)\n"
18+"X-Generator: Launchpad (build 16976)\n"
21
19
22#: plugins/Unity/Launcher/launcheritem.cpp:43
20#: plugins/Unity/Launcher/launcheritem.cpp:43
23#: plugins/Unity/Launcher/launcheritem.cpp:73
21#: plugins/Unity/Launcher/launcheritem.cpp:73
@@ -78,6 +76,8 @@ msgid ""
78"You have now mastered the edge gestures and can start using the "
76"You have now mastered the edge gestures and can start using the "
79"phone<br><br>Tap on the screen to start"
77"phone<br><br>Tap on the screen to start"
80msgstr ""
78msgstr ""
79+"Вы научились пользоваться Ubuntu и можете начать использовать свой "
80+"телефон<br><br>Нажмите на экран"
81
81
82#: qml/Components/PageHeader.qml:365
82#: qml/Components/PageHeader.qml:365
83msgid "Recent searches"
83msgid "Recent searches"
@@ -85,28 +85,28 @@ msgstr "Последние поисковые запросы"
85
85
86#: qml/Components/SeeMore.qml:35
86#: qml/Components/SeeMore.qml:35
87msgid "See more"
87msgid "See more"
88−msgstr ""
88+msgstr "Подробнее"
89
89
90#: qml/Components/SeeMore.qml:58
90#: qml/Components/SeeMore.qml:58
91msgid "See less"
91msgid "See less"
92−msgstr ""
92+msgstr "Вкратце"
93
93
94#: qml/Dash/DashApps.qml:38
94#: qml/Dash/DashApps.qml:38
95msgid "Recent"
95msgid "Recent"
96−msgstr ""
96+msgstr "Недавние"
97
97
98#: qml/Dash/PreviewListView.qml:52
98#: qml/Dash/PreviewListView.qml:52
99#, qt-format
99#, qt-format
100msgid "%1 Preview"
100msgid "%1 Preview"
101−msgstr ""
101+msgstr "Просмотр %1"
102
102
103#: qml/Dash/Previews/PreviewActionCombo.qml:34
103#: qml/Dash/Previews/PreviewActionCombo.qml:34
104msgid "More..."
104msgid "More..."
105−msgstr ""
105+msgstr "Ещё..."
106
106
107#: qml/Dash/Previews/PreviewActionCombo.qml:34
107#: qml/Dash/Previews/PreviewActionCombo.qml:34
108msgid "Less..."
108msgid "Less..."
109−msgstr ""
109+msgstr "Меньше..."
110
110
111#: qml/Dash/Previews/PreviewRatingInput.qml:81
111#: qml/Dash/Previews/PreviewRatingInput.qml:81
112msgid "Rate this"
112msgid "Rate this"
@@ -159,7 +159,7 @@ msgstr "Показать пароль"
159
159
160#: qml/Notifications/NotificationMenuItemFactory.qml:103
160#: qml/Notifications/NotificationMenuItemFactory.qml:103
161msgid "Please enter SIM PIN"
161msgid "Please enter SIM PIN"
162−msgstr ""
162+msgstr "Введите PIN код"
163
163
164#: qml/Panel/SearchIndicator.qml:27
164#: qml/Panel/SearchIndicator.qml:27
165msgid "Search"
165msgid "Search"
@@ -169,20 +169,3 @@ msgstr "Поиск"
169#, qt-format
169#, qt-format
170msgid "Please enter %1"
170msgid "Please enter %1"
171msgstr "Пожалуйста, введите %1"
171msgstr "Пожалуйста, введите %1"
172−
173−#, qt-format
174−#~ msgid "(%1)"
175−#~ msgstr "(%1)"
176−
177−#~ msgid "Comments:"
178−#~ msgstr "Комментарии:"
179−
180−#~ msgid "Review"
181−#~ msgstr "Отзыв"
182−
183−#, qt-format
184−#~ msgid "%1 review"
185−#~ msgid_plural "%1 reviews"
186−#~ msgstr[0] "%1 отзыв"
187−#~ msgstr[1] "%1 отзыва"
188−#~ msgstr[2] "%1 отзывов"
po/ug.po+8−24
@@ -8,15 +8,14 @@ msgstr ""
8"Project-Id-Version: unity\n"
8"Project-Id-Version: unity\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2014-03-25 16:48+0100\n"
10"POT-Creation-Date: 2014-03-25 16:48+0100\n"
11−"PO-Revision-Date: 2014-02-15 12:09+0000\n"
11+"PO-Revision-Date: 2014-04-22 05:46+0000\n"
12−"Last-Translator: Gheyret T.Kenji <Unknown>\n"
12+"Last-Translator: abdurahman <Unknown>\n"
13"Language-Team: Uyghur <ug@li.org>\n"
13"Language-Team: Uyghur <ug@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17−"Plural-Forms: nplurals=1; plural=0;\n"
18−"X-Launchpad-Export-Date: 2014-03-28 06:49+0000\n"
17+"X-Launchpad-Export-Date: 2014-04-22 08:29+0000\n"
19−"X-Generator: Launchpad (build 16967)\n"
18+"X-Generator: Launchpad (build 16985)\n"
20
19
21#: plugins/Unity/Launcher/launcheritem.cpp:43
20#: plugins/Unity/Launcher/launcheritem.cpp:43
22#: plugins/Unity/Launcher/launcheritem.cpp:73
21#: plugins/Unity/Launcher/launcheritem.cpp:73
@@ -89,20 +88,20 @@ msgstr "ئازراق كۆرۈش"
89
88
90#: qml/Dash/DashApps.qml:38
89#: qml/Dash/DashApps.qml:38
91msgid "Recent"
90msgid "Recent"
92−msgstr ""
91+msgstr "يېقىنقى"
93
92
94#: qml/Dash/PreviewListView.qml:52
93#: qml/Dash/PreviewListView.qml:52
95#, qt-format
94#, qt-format
96msgid "%1 Preview"
95msgid "%1 Preview"
97−msgstr ""
96+msgstr "%1 ئالدىن كۆرۈپ بىقىش"
98
97
99#: qml/Dash/Previews/PreviewActionCombo.qml:34
98#: qml/Dash/Previews/PreviewActionCombo.qml:34
100msgid "More..."
99msgid "More..."
101−msgstr ""
100+msgstr "تېخىمۇ كۆپ…"
102
101
103#: qml/Dash/Previews/PreviewActionCombo.qml:34
102#: qml/Dash/Previews/PreviewActionCombo.qml:34
104msgid "Less..."
103msgid "Less..."
105−msgstr ""
104+msgstr "ئازراق..."
106
105
107#: qml/Dash/Previews/PreviewRatingInput.qml:81
106#: qml/Dash/Previews/PreviewRatingInput.qml:81
108msgid "Rate this"
107msgid "Rate this"
@@ -165,18 +164,3 @@ msgstr "ئىزدە"
165#, qt-format
164#, qt-format
166msgid "Please enter %1"
165msgid "Please enter %1"
167msgstr "%1 نى كىرگۈزۈڭ"
166msgstr "%1 نى كىرگۈزۈڭ"
168−
169−#, qt-format
170−#~ msgid "%1 review"
171−#~ msgid_plural "%1 reviews"
172−#~ msgstr[0] "%1 باھا"
173−
174−#, qt-format
175−#~ msgid "(%1)"
176−#~ msgstr "(%1)"
177−
178−#~ msgid "Comments:"
179−#~ msgstr "ئىنكاس:"
180−
181−#~ msgid "Review"
182−#~ msgstr "تەكشۈر"
qml/CMakeLists.txt+1−1
@@ -13,7 +13,7 @@ set(QML_DIRS
13 Hud
13 Hud
14 Panel
14 Panel
15 Launcher
15 Launcher
16− SideStage
16+ Stages
17 Notifications
17 Notifications
18 )
18 )
19
19
qml/Components/AnimationControllerWithSignals.qmldeleted−64
@@ -1,64 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−
19−AnimationController {
20− signal animationCompletedAtBeginning()
21− signal animationCompletedAtEnd()
22− readonly property bool completed: progress == 1.0 || progress == 0.0
23− property bool completing: false
24−
25− function __completeAtBeginning() {
26− completing = false;
27− animationCompletedAtBeginning();
28− }
29−
30− function __completeAtEnd() {
31− completing = false;
32− animationCompletedAtEnd();
33− }
34−
35− function completeToBeginningWithSignal() {
36− if (progress == 0.0) {
37− __completeAtBeginning()
38− } else {
39− completing = true;
40− completeToBeginning();
41− }
42− }
43−
44− function completeToEndWithSignal() {
45− if (progress == 1.0) {
46− __completeAtEnd()
47− } else {
48− completing = true;
49− completeToEnd();
50− }
51− }
52−
53− onProgressChanged: {
54− if (!completing) {
55− return;
56− }
57−
58− if (progress == 0.0) {
59− __completeAtBeginning()
60− } else if (progress == 1.0) {
61− __completeAtEnd()
62− }
63− }
64−}
qml/Components/ApplicationManagerWrapper.qmldeleted−169
@@ -1,169 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import Unity.Application 0.1
19−
20−Item {
21− id: root
22−
23− property alias mainStageApplications: mainStageModel
24− property alias sideStageApplications: sideStageModel
25− property variant mainStageFocusedApplication: null
26− property variant sideStageFocusedApplication: null
27− property bool sideStageEnabled: true
28− property bool keyboardVisible: ApplicationManager.keyboardVisible
29− property int keyboardHeight: ApplicationManager.keyboardHeight
30−
31− property bool fake: ApplicationManager.fake ? ApplicationManager.fake : false
32−
33− signal focusRequested(string appId)
34−
35− ApplicationsModelStageFiltered {
36− id: mainStageModel
37− stage: ApplicationInfo.MainStage
38− }
39−
40− ApplicationsModelStageFiltered {
41− id: sideStageModel
42− stage: ApplicationInfo.SideStage
43− }
44−
45− Connections {
46− target: ApplicationManager
47− onFocusedApplicationIdChanged: {
48− var app = ApplicationManager.findApplication(ApplicationManager.focusedApplicationId)
49− if (!app) { //nothing at all focused, so clear all
50− mainStageFocusedApplication = null;
51− sideStageFocusedApplication = null;
52− } else {
53− if (app.stage == ApplicationInfo.MainStage) {
54− mainStageFocusedApplication = app;
55− // possible the side stage app being unfocused fired this signal, so check for it
56− if (sideStageFocusedApplication && !sideStageFocusedApplication.focused)
57− sideStageFocusedApplication = null;
58− } else {
59− sideStageFocusedApplication = app;
60− // possible the main stage app being unfocused fired this signal, so check for it
61− if (mainStageFocusedApplication && !mainStageFocusedApplication.focused)
62− mainStageFocusedApplication = null;
63− }
64− }
65− }
66−
67− onFocusRequested: {
68− // if no side stage enabled, override application's stage parameter
69− var app = ApplicationManager.findApplication(appId);
70− if (app && app.stage === ApplicationInfo.SideStage && !sideStageEnabled) {
71− app.stage = ApplicationInfo.MainStage;
72− }
73−
74− root.focusRequested(appId);
75− }
76− }
77−
78− function activateApplication(desktopFile, argument) {
79− var appId;
80−
81− // HACK: Applications identified sometimes with with appId, but mostly with desktopFile.
82− // TODO: convert entire shell to use appId only.
83− if (desktopFile.indexOf(".desktop") >= 0) {
84− appId = desktopFileToAppId(desktopFile);
85− } else {
86− appId = desktopFile;
87− }
88−
89− var application = ApplicationManager.findApplication(appId);
90− if (application !== null) {
91− return application;
92− }
93−
94− var execFlags = sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
95−
96− if (argument) {
97− return ApplicationManager.startApplication(appId, execFlags, [argument]);
98− } else {
99− return ApplicationManager.startApplication(appId, execFlags);
100− }
101− }
102−
103− function stopApplication(application) {
104− var appId;
105−
106− // HACK: Applications identified sometimes with with appId, but mostly with desktopFile.
107− // TODO: convert entire shell to use appId only.
108− if (typeof application == "string") {
109− appId = application;
110− } else {
111− appId = desktopFileToAppId(application.desktopFile);
112− }
113−
114− ApplicationManager.stopApplication(appId);
115− }
116−
117− function focusApplication(application) {
118− if (application == null || application == undefined) {
119− return;
120− }
121−
122− ApplicationManager.focusApplication(application.appId);
123− }
124−
125− function unfocusCurrentApplication() {
126− ApplicationManager.unfocusCurrentApplication();
127− }
128−
129− function moveRunningApplicationStackPosition(from, to, stage) {
130− if (from == to || from < 0 || to < 0) return;
131−
132− if (stage == ApplicationInfo.SideStage) {
133− sideStageModel.move(from, to);
134− } else {
135− mainStageModel.move(from, to);
136− }
137− }
138−
139− function getApplicationFromDesktopFile(desktopFile, stage) {
140− var appId;
141−
142− // HACK: Applications identified sometimes with with appId, but mostly with desktopFile.
143− // TODO: convert entire shell to use appId only.
144− if (desktopFile.indexOf(".desktop") >= 0) {
145− appId = desktopFileToAppId(desktopFile);
146− } else {
147− appId = desktopFile;
148− }
149−
150− for (var i = 0, len = ApplicationManager.count; i < len; i++ ) {
151− var app = ApplicationManager.get(i);
152−
153− // if stage not specified, return whichever app running on either stage
154− if (app.appId == appId && (stage == undefined || app.stage == stage)) {
155− return app;
156− }
157− }
158− }
159−
160− function desktopFileToAppId(desktopFile) {
161− var right = desktopFile.lastIndexOf(".desktop");
162− var left = desktopFile.lastIndexOf("/");
163− if (left == -1 || right == -1 || left == right) {
164− console.log("ApplicationManagerWrapper: unable to extract appId from '" + desktopFile + "'");
165− return "";
166− }
167− return desktopFile.substring(left+1, right);
168− }
169−}
qml/Components/ApplicationScreenshot.qmldeleted−61
@@ -1,61 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import Ubuntu.Components 0.1
19−import Unity.Application 0.1
20−
21−Item {
22− id: applicationScreenshot
23−
24− property var application: null
25− property bool withBackground: false
26− property bool ready: applicationImage.ready || withBackground
27−
28− function setApplication(application) {
29− applicationScreenshot.application = application
30− }
31−
32− function clearApplication() {
33− applicationScreenshot.withBackground = false;
34− applicationScreenshot.application = null
35− applicationScreenshot.scheduleUpdate();
36− }
37−
38− function scheduleUpdate() {
39− applicationImage.scheduleUpdate()
40− }
41−
42− function updateFromCache() {
43− applicationImage.updateFromCache()
44− }
45−
46− Rectangle {
47− id: background
48− anchors.fill: parent
49− color: "white" // FIXME should use normal background color of Suru theme
50− visible: applicationScreenshot.withBackground
51− }
52−
53− ApplicationImage {
54− id: applicationImage
55− objectName: "screenshot image"
56− width: applicationScreenshot.application ? parent.width : 0
57− height: applicationScreenshot.application ? parent.height : 0
58− visible: applicationScreenshot.application != null && ready
59− source: ApplicationManager.findApplication((application) ? application.appId : "")
60− }
61−}
qml/Components/ApplicationsModelStageFiltered.qmldeleted−49
@@ -1,49 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import Utils 0.1
19−import Unity.Application 0.1
20−
21−SortFilterProxyModel {
22− id: root
23− property int stage
24−
25− function get(index) {
26− return model.get(mapRowToSource(index));
27− }
28−
29− function move(from, to) {
30− var realFrom = mapRowToSource(from);
31− if (realFrom == -1) {
32− console.log("ERROR; invalid from=" + from + " index in move operation");
33− return;
34− }
35− var realTo = mapRowToSource(to);
36− if (realTo == -1) {
37− // assuming wanting to move to end of list
38− realTo = model.count - 1;
39− }
40−
41− if (realFrom == realTo) return;
42− model.move(realFrom, realTo);
43− }
44−
45− model: ApplicationManager
46− dynamicSortFilter: true
47− filterRole: ApplicationManager.RoleStage
48− filterRegExp: RegExp(stage)
49−}
qml/Components/Carousel.qml+82−82
@@ -75,6 +75,7 @@ Item {
75 The scaling of the items is controlled by the variable continuousIndex, described below. */
75 The scaling of the items is controlled by the variable continuousIndex, described below. */
76 ListView {
76 ListView {
77 id: listView
77 id: listView
78+ objectName: "listView"
78
79
79 property int highlightIndex: -1
80 property int highlightIndex: -1
80 property real minimumTileWidth: 0
81 property real minimumTileWidth: 0
@@ -148,15 +149,19 @@ Item {
148 maximumFlickVelocity: Math.max(2500 * Math.pow(realWidth / referenceWidth, 1.5), 2500) // 2500 is platform default
149 maximumFlickVelocity: Math.max(2500 * Math.pow(realWidth / referenceWidth, 1.5), 2500) // 2500 is platform default
149 orientation: ListView.Horizontal
150 orientation: ListView.Horizontal
150
151
151− function itemClicked(index, delegateItem) {
152+ function getXFromContinuousIndex(index) {
152− listView.currentIndex = index
153− var x = CarouselJS.getXFromContinuousIndex(index,
153+ return CarouselJS.getXFromContinuousIndex(index,
154 realWidth,
154 realWidth,
155− realContentWidth,
155+ footerItem.x,
156 tileWidth,
156 tileWidth,
157 gapToMiddlePhase,
157 gapToMiddlePhase,
158 gapToEndPhase,
158 gapToEndPhase,
159 carousel.drawBuffer)
159 carousel.drawBuffer)
160+ }
161+
162+ function itemClicked(index, delegateItem) {
163+ listView.currentIndex = index
164+ var x = getXFromContinuousIndex(index);
160
165
161 if (Math.abs(x - contentX) < 1 && delegateItem !== undefined) {
166 if (Math.abs(x - contentX) < 1 && delegateItem !== undefined) {
162 /* We're clicking the selected item and
167 /* We're clicking the selected item and
@@ -174,13 +179,7 @@ Item {
174 }
179 }
175
180
176 function itemPressAndHold(index, delegateItem) {
181 function itemPressAndHold(index, delegateItem) {
177− var x = CarouselJS.getXFromContinuousIndex(index,
182+ var x = getXFromContinuousIndex(index);
178− realWidth,
179− realContentWidth,
180− tileWidth,
181− gapToMiddlePhase,
182− gapToEndPhase,
183− carousel.drawBuffer);
184
183
185 if (Math.abs(x - contentX) < 1 && delegateItem !== undefined) {
184 if (Math.abs(x - contentX) < 1 && delegateItem !== undefined) {
186 /* We're pressAndHold the selected item and
185 /* We're pressAndHold the selected item and
@@ -209,22 +208,17 @@ Item {
209 newContentX = disabledNewContentX
208 newContentX = disabledNewContentX
210 }
209 }
211 onMovementEnded: {
210 onMovementEnded: {
212− if (realContentX > 0 && realContentX < realContentWidth - realWidth)
211+ if (realContentX > 0)
213 stepAnimation.start()
212 stepAnimation.start()
214 }
213 }
215
214
216 SmoothedAnimation {
215 SmoothedAnimation {
217 id: stepAnimation
216 id: stepAnimation
217+ objectName: "stepAnimation"
218
218
219 target: listView
219 target: listView
220 property: "contentX"
220 property: "contentX"
221− to: CarouselJS.getXFromContinuousIndex(listView.selectedIndex,
221+ to: listView.getXFromContinuousIndex(listView.selectedIndex)
222− listView.realWidth,
223− listView.realContentWidth,
224− listView.tileWidth,
225− listView.gapToMiddlePhase,
226− listView.gapToEndPhase,
227− carousel.drawBuffer)
228 duration: 450
222 duration: 450
229 velocity: 200
223 velocity: 200
230 easing.type: Easing.InOutQuad
224 easing.type: Easing.InOutQuad
@@ -261,77 +255,83 @@ Item {
261 listView.gapToEndPhase,
255 listView.gapToEndPhase,
262 listView.translationXViewFactor)
256 listView.translationXViewFactor)
263
257
258+ delegate: tileWidth > 0 && tileHeight > 0 ? loaderComponent : undefined
259+
260+ Component {
261+ id: loaderComponent
262+
264− delegate: Loader {
263+ Loader {
265− property bool explicitlyScaled: explicitScaleFactor == carousel.selectedItemScaleFactor
264+ property bool explicitlyScaled: explicitScaleFactor == carousel.selectedItemScaleFactor
266− property real explicitScaleFactor: explicitScale ? carousel.selectedItemScaleFactor : 1.0
265+ property real explicitScaleFactor: explicitScale ? carousel.selectedItemScaleFactor : 1.0
267− readonly property bool explicitScale: (!listView.moving ||
266+ readonly property bool explicitScale: (!listView.moving ||
268− listView.realContentX <= 0 ||
267+ listView.realContentX <= 0 ||
269− listView.realContentX >= listView.realContentWidth - listView.realWidth) &&
268+ listView.realContentX >= listView.realContentWidth - listView.realWidth) &&
270− listView.newContentX === listView.disabledNewContentX &&
269+ listView.newContentX === listView.disabledNewContentX &&
271− index === listView.selectedIndex
270+ index === listView.selectedIndex
272− readonly property real cachedTiles: listView.realPathItemCount + carousel.drawBuffer / listView.tileWidth
271+ readonly property real cachedTiles: listView.realPathItemCount + carousel.drawBuffer / listView.tileWidth
273− readonly property real distance: listView.continuousIndex - index
272+ readonly property real distance: listView.continuousIndex - index
274− readonly property real itemTranslationScale: CarouselJS.getItemScale(0.5,
273+ readonly property real itemTranslationScale: CarouselJS.getItemScale(0.5,
275− (index + 0.5), // good approximation of scale while changing selected item
274+ (index + 0.5), // good approximation of scale while changing selected item
276− listView.count,
275+ listView.count,
277− listView.visibleTilesScaleFactor)
276+ listView.visibleTilesScaleFactor)
278− readonly property real itemScale: CarouselJS.getItemScale(distance,
277+ readonly property real itemScale: CarouselJS.getItemScale(distance,
279− listView.continuousIndex,
278+ listView.continuousIndex,
280− listView.count,
279+ listView.count,
281− listView.visibleTilesScaleFactor)
280+ listView.visibleTilesScaleFactor)
282− readonly property real translationX: CarouselJS.getItemTranslation(index,
281+ readonly property real translationX: CarouselJS.getItemTranslation(index,
283− listView.selectedIndex,
282+ listView.selectedIndex,
284− distance,
283+ distance,
285− itemScale,
284+ itemScale,
286− itemTranslationScale,
285+ itemTranslationScale,
287− listView.maximumItemTranslation)
286+ listView.maximumItemTranslation)
288−
287+
289− readonly property real xTransform: listView.viewTranslation + translationX * listView.scaleFactor
288+ readonly property real xTransform: listView.viewTranslation + translationX * listView.scaleFactor
290− readonly property real center: x - listView.contentX + xTransform - drawBuffer + (width/2)
289+ readonly property real center: x - listView.contentX + xTransform - drawBuffer + (width/2)
291−
290+
292− width: listView.tileWidth
291+ width: listView.tileWidth
293− height: listView.tileHeight
292+ height: listView.tileHeight
294− scale: itemScale * explicitScaleFactor
293+ scale: itemScale * explicitScaleFactor
295− sourceComponent: itemComponent
294+ sourceComponent: itemComponent
296− z: cachedTiles - Math.abs(index - listView.selectedIndex)
295+ z: cachedTiles - Math.abs(index - listView.selectedIndex)
297−
296+
298− transform: Translate {
297+ transform: Translate {
299− x: xTransform
298+ x: xTransform
300− }
299+ }
301
300
302− Behavior on explicitScaleFactor {
301+ Behavior on explicitScaleFactor {
303− SequentialAnimation {
302+ SequentialAnimation {
304− ScriptAction {
303+ ScriptAction {
305− script: if (!explicitScale)
304+ script: if (!explicitScale)
306− explicitlyScaled = false
305+ explicitlyScaled = false
307− }
306+ }
308− NumberAnimation {
307+ NumberAnimation {
309− duration: explicitScaleFactor === 1.0 ? 250 : 150
308+ duration: explicitScaleFactor === 1.0 ? 250 : 150
310− easing.type: Easing.InOutQuad
309+ easing.type: Easing.InOutQuad
311− }
310+ }
312− ScriptAction {
311+ ScriptAction {
313− script: if (explicitScale)
312+ script: if (explicitScale)
314− explicitlyScaled = true
313+ explicitlyScaled = true
314+ }
315 }
315 }
316 }
316 }
317− }
318
317
319− onLoaded: {
318+ onLoaded: {
320− item.explicitlyScaled = Qt.binding(function() { return explicitlyScaled; })
319+ item.explicitlyScaled = Qt.binding(function() { return explicitlyScaled; })
321− item.model = Qt.binding(function() { return model; })
320+ item.model = Qt.binding(function() { return model; })
322− }
321+ }
323
322
324− MouseArea {
323+ MouseArea {
325− id: mouseArea
324+ id: mouseArea
326
325
327− anchors.fill: parent
326+ anchors.fill: parent
328
327
329− onClicked: {
328+ onClicked: {
330− listView.itemClicked(index, item)
329+ listView.itemClicked(index, item)
331− }
330+ }
332
331
333− onPressAndHold: {
332+ onPressAndHold: {
334− listView.itemPressAndHold(index, item)
333+ listView.itemPressAndHold(index, item)
334+ }
335 }
335 }
336 }
336 }
337 }
337 }
qml/Components/FilterGrid.qml+1
@@ -59,6 +59,7 @@ Item {
59 property alias cellHeight: iconTileGrid.cellHeight
59 property alias cellHeight: iconTileGrid.cellHeight
60 property alias delegateCreationBegin: iconTileGrid.delegateCreationBegin
60 property alias delegateCreationBegin: iconTileGrid.delegateCreationBegin
61 property alias delegateCreationEnd: iconTileGrid.delegateCreationEnd
61 property alias delegateCreationEnd: iconTileGrid.delegateCreationEnd
62+ readonly property alias originY: iconTileGrid.originY
62 readonly property alias flicking: iconTileGrid.flicking
63 readonly property alias flicking: iconTileGrid.flicking
63 readonly property alias moving: iconTileGrid.moving
64 readonly property alias moving: iconTileGrid.moving
64 readonly property alias pressDelay: iconTileGrid.pressDelay
65 readonly property alias pressDelay: iconTileGrid.pressDelay
qml/Components/ResponsiveFlowView.qml+3−10
@@ -27,9 +27,8 @@ Item {
27 property alias verticalSpacing: flow.verticalSpacing
27 property alias verticalSpacing: flow.verticalSpacing
28 property alias horizontalSpacing: flow.horizontalSpacing
28 property alias horizontalSpacing: flow.horizontalSpacing
29 property int referenceDelegateWidth
29 property int referenceDelegateWidth
30− property alias firstModel: repeater1.model
30+ property alias model: repeater.model
31− property alias secondModel: repeater2.model
32− property alias delegate: repeater1.delegate
31+ property alias delegate: repeater.delegate
33 readonly property int cellWidth: referenceDelegateWidth + horizontalSpacing
32 readonly property int cellWidth: referenceDelegateWidth + horizontalSpacing
34 readonly property int cellHeight: referenceDelegateWidth + verticalSpacing
33 readonly property int cellHeight: referenceDelegateWidth + verticalSpacing
35 property alias move: flow.move
34 property alias move: flow.move
@@ -69,13 +68,7 @@ Item {
69 property int margin: allocatableVerticalSpace - columns * horizontalSpacing
68 property int margin: allocatableVerticalSpace - columns * horizontalSpacing
70
69
71 Repeater {
70 Repeater {
72− id: repeater1
71+ id: repeater
73− model: (root.model) ? root.model[0] : null
74− }
75− Repeater {
76− id: repeater2
77− model: (root.model) ? root.model[1] : null
78− delegate: repeater1.delegate
79 }
72 }
80 }
73 }
81}
74}
qml/Components/ResponsiveGridView.qml+1
@@ -40,6 +40,7 @@ Item {
40 readonly property alias flicking: gridView.flicking
40 readonly property alias flicking: gridView.flicking
41 readonly property alias moving: gridView.moving
41 readonly property alias moving: gridView.moving
42 readonly property alias pressDelay: gridView.pressDelay
42 readonly property alias pressDelay: gridView.pressDelay
43+ readonly property alias originY: gridView.originY
43 property alias delegateCreationBegin: gridView.delegateCreationBegin
44 property alias delegateCreationBegin: gridView.delegateCreationBegin
44 property alias delegateCreationEnd: gridView.delegateCreationEnd
45 property alias delegateCreationEnd: gridView.delegateCreationEnd
45 property alias highlightIndex: gridView.highlightIndex
46 property alias highlightIndex: gridView.highlightIndex
qml/Components/Stage.qmldeleted−571
@@ -1,571 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import Unity.Application 0.1
19−import Ubuntu.Components 0.1
20−import Ubuntu.Gestures 0.1
21−
22−/*
23− Responsible for application switching.
24−
25− Drag from the right edge to switch to the next application.
26− While being dragged, this component will show an animation for the user to
27− visualize that he's changing the stacking of applications (next/new app comes from
28− right edge and current/old app fades out).
29− */
30−Showable {
31− id: stage
32−
33− width: units.gu(40)
34− height: units.gu(71)
35−
36− property int type: ApplicationInfo.MainStage
37− property var applicationManager
38− readonly property var applications:
39− type == ApplicationInfo.MainStage ? applicationManager.mainStageApplications
40− : applicationManager.sideStageApplications
41− property bool fullyShown: false
42− property bool fullyHidden: true
43− readonly property var focusedApplication:
44− type == ApplicationInfo.MainStage ? applicationManager.mainStageFocusedApplication
45− : applicationManager.sideStageFocusedApplication
46− property bool shouldUseScreenshots: true
47−
48− /* Will be true whenever application screenshots are being shown.
49− i.e. during application window transitions */
50− readonly property bool usingScreenshots: {
51− if (newApplicationScreenshot.application != null && oldApplicationScreenshot.application != null) {
52− return newApplicationScreenshot.ready && oldApplicationScreenshot.ready;
53− } else {
54− return newApplicationScreenshot.ready || oldApplicationScreenshot.ready;
55− }
56− }
57−
58− property bool rightEdgeEnabled: true
59− property bool switchingFromFullscreenToFullscreen: false
60−
61− property alias rightEdgeDraggingAreaWidth: rightEdgeDraggingArea.width
62−
63− // Y coordinate for non-fullscreen applications
64− property int normalApplicationY: 0
65−
66− function activateApplication(desktopFile, addDelay) {
67− var application = applicationManager.getApplicationFromDesktopFile(desktopFile, stage.type);
68− if (application == null) {
69− return;
70− } else if (application == stage.focusedApplication) {
71− stage.show();
72− return;
73− }
74−
75− showStartingApplicationAnimation.prepare(application);
76− if (!stage.fullyShown) {
77− // if launching this app reveals the stage, make new application screenshot visible
78− newApplicationScreenshot.visible = true;
79− stage.show();
80− } else {
81− showStartingApplicationAnimation.start();
82− }
83−
84− if (addDelay) {
85− stage.__startingApplicationDesktopFile = desktopFile;
86− }
87− }
88−
89− Connections {
90− target: applicationManager
91− onFocusRequested: activateApplication(appId)
92− }
93−
94− /* Keep a reference to the focused application so that we can safely
95− unfocus it when the stage is not fully shown and refocus it when the stage
96− is fully shown again.
97− */
98− property var focusedApplicationWhenUsingScreenshots
99− onShouldUseScreenshotsChanged: {
100− if (showStartingApplicationAnimation.running || switchToApplicationAnimation.running) {
101− return;
102− }
103−
104− if (shouldUseScreenshots) {
105− stage.__focusApplicationUsingScreenshots(stage.focusedApplication);
106− } else {
107− if (stage.focusedApplicationWhenUsingScreenshots) {
108− stage.__focusActualApplication(stage.focusedApplicationWhenUsingScreenshots);
109− }
110− }
111− }
112−
113−
114− property Item newApplicationScreenshot: ApplicationScreenshot {
115− parent: stage
116− width: stage.width
117− height: stage.height - y
118− }
119− property Item oldApplicationScreenshot: ApplicationScreenshot {
120− parent: stage
121− width: stage.width
122− height: stage.height - y
123− }
124−
125− function __getApplicationIndex(application) {
126− if (!application)
127− return -1
128− for (var i = 0; i < stage.applications.count; i++ ) {
129− if (stage.applications.get(i).desktopFile == application.desktopFile) {
130− return i
131− }
132− }
133− return -1
134− }
135−
136−
137− property string __startingApplicationDesktopFile
138−
139− Timer {
140− id: delayedHideScreenshots
141− interval: 500 // on Galaxy Nexus, Telephony app is slowest application to draw a frame so needs this delay
142− onTriggered: {
143− __hideScreenshots();
144− }
145− }
146−
147− Timer {
148− id: hideScreenshotsAfterBriefDelay
149− // Since Mir & Qt have independent event loops, Mir often applies a focus request a frame later than when Qt
150− // asks for it. To work around, delay removing the screenshots a touch so Mir has definitely shown the app.
151− interval: 10
152− onTriggered: stage.__hideScreenshots();
153− }
154−
155− function __hideScreenshots() {
156− newApplicationScreenshot.clearApplication();
157− oldApplicationScreenshot.clearApplication();
158− newApplicationScreenshot.visible = false;
159− oldApplicationScreenshot.visible = false;
160− }
161−
162− function __focusApplication(application) {
163− if (!application) return
164− if (shouldUseScreenshots) {
165− stage.__focusApplicationUsingScreenshots(application);
166− } else {
167− stage.__focusActualApplication(application);
168− }
169− }
170−
171−
172− function __focusActualApplication(application) {
173− if (application == null) {
174− return;
175− }
176−
177− if (stage.focusedApplication == application) {
178− /* FIXME: this case is reached when showing the sidestage, we
179− need to refocus the side stage app in order to show it.
180− FIXME: this case is reached when doing a right edge swipe
181− while in the dash to bring back the stage.
182− FIXME: this case is reached when showStartingApplicationAnimation
183− finishes after the newly started application has been focused
184− automatically by the ApplicationManager.
185− */
186− if (stage.type == ApplicationInfo.SideStage) {
187− applicationManager.focusApplication(application);
188− }
189− if (!delayedHideScreenshots.running) {
190− hideScreenshotsAfterBriefDelay.start();
191− }
192− } else {
193− /* FIXME: calling ApplicationManager::focusApplication does not focus
194− the application immediately nor show the application's surface
195− immediately. Delay hiding the screenshots until it has done so.
196− */
197− delayedHideScreenshots.stop();
198− applicationManager.focusApplication(application);
199− }
200− stage.focusedApplicationWhenUsingScreenshots = null;
201− }
202−
203− function __focusApplicationUsingScreenshots(application) {
204− delayedHideScreenshots.stop();
205− if (application == null) {
206− return;
207− }
208−
209− if (newApplicationScreenshot.application != null) {
210− stage.focusedApplicationWhenUsingScreenshots = application;
211− return;
212− }
213−
214− newApplicationScreenshot.setApplication(application);
215− newApplicationScreenshot.scheduleUpdate();
216− newApplicationScreenshot.opacity = 1.0;
217− newApplicationScreenshot.x = 0.0;
218− newApplicationScreenshot.y = __getApplicationY(application);
219− newApplicationScreenshot.scale = 1.0;
220− newApplicationScreenshot.visible = true;
221− stage.focusedApplicationWhenUsingScreenshots = application;
222− stage.switchingFromFullscreenToFullscreen = false;
223− }
224−
225− function __getApplicationY(application) {
226− return Qt.binding( function(){
227− return (application && application.fullscreen
228− && application.stage !== ApplicationInfo.SideStage)
229− ? 0 : normalApplicationY;
230− } )
231− }
232−
233− /* FIXME: should hide the screenshots when the focused application's surface
234− is visible. There is no API in QtUbuntu to support that yet.
235− */
236− Connections {
237− target: stage
238− onFocusedApplicationChanged: {
239− if (stage.focusedApplication != null) {
240− /* Move application to the beginning of the list of running applications */
241− var index = stage.__getApplicationIndex(stage.focusedApplication);
242− applicationManager.moveRunningApplicationStackPosition(index, 0, stage.type);
243−
244− var startingApplication = applicationManager.getApplicationFromDesktopFile(
245− stage.__startingApplicationDesktopFile, stage.type);
246−
247− if (stage.focusedApplication == startingApplication) {
248− stage.__startingApplicationDesktopFile = "";
249−
250− /* Screenshots might still be in use at this point and their
251− use can be stopped before delayedHideScreenshots is complete.
252− Prepare for that case by setting appropriately
253− stage.focusedApplicationWhenUsingScreenshots.
254− */
255− stage.focusedApplicationWhenUsingScreenshots = stage.focusedApplication;
256−
257− /* FIXME: should hide the screenshots when the focused application's window
258− is visible. There is no API in qtubuntu to support that yet. Therefore
259− we delay their hiding to avoid a state where neither the screenshot nor
260− the application's window is visible.
261− */
262− delayedHideScreenshots.start();
263− } else {
264− stage.focusedApplicationWhenUsingScreenshots = null;
265− hideScreenshotsAfterBriefDelay.start();
266− }
267− }
268− }
269− }
270−
271− Connections {
272− target: stage.applications
273− onCountChanged: { __discardObsoleteScreenshots(); }
274− ignoreUnknownSignals: true
275− }
276− onFullyHiddenChanged: { __discardObsoleteScreenshots(); }
277− function __discardObsoleteScreenshots() {
278− if (stage.fullyHidden && stage.applications && stage.applications.count == 0) {
279− stage.focusedApplicationWhenUsingScreenshots = undefined;
280− stage.__hideScreenshots();
281− }
282− }
283−
284−
285− SequentialAnimation {
286− id: showStartingApplicationAnimation
287−
288− function prepare(newApplication) {
289− var oldApplication = stage.focusedApplication;
290− newApplicationScreenshot.setApplication(newApplication);
291− newApplicationScreenshot.updateFromCache();
292− oldApplicationScreenshot.setApplication(oldApplication);
293− oldApplicationScreenshot.scheduleUpdate();
294− newApplicationScreenshot.withBackground = true; // FIXME: should be withBackground = newApplication.state == Application.Starting
295− newApplicationScreenshot.y = __getApplicationY(newApplication);
296− oldApplicationScreenshot.y = __getApplicationY(oldApplication);
297− stage.focusedApplicationWhenUsingScreenshots = newApplication;
298− stage.switchingFromFullscreenToFullscreen = false;
299− }
300−
301− PropertyAction {target: newApplicationScreenshot; property: "visible"; value: false}
302− PropertyAction {target: oldApplicationScreenshot; property: "visible"; value: false}
303− PropertyAction {target: newApplicationScreenshot; property: "z"; value: 0}
304− PropertyAction {target: oldApplicationScreenshot; property: "z"; value: 1}
305− PropertyAction {target: newApplicationScreenshot; property: "scale"; value: 0.8}
306− PropertyAction {target: oldApplicationScreenshot; property: "scale"; value: 1.0}
307− PropertyAction {target: newApplicationScreenshot; property: "opacity"; value: 0.0}
308− PropertyAction {target: oldApplicationScreenshot; property: "opacity"; value: 1.0}
309− PropertyAction {target: newApplicationScreenshot; property: "x"; value: 0.0}
310− PropertyAction {target: oldApplicationScreenshot; property: "x"; value: 0.0}
311−
312− ParallelAnimation {
313− PropertyAction {target: newApplicationScreenshot; property: "visible"; value: true}
314− NumberAnimation {
315− target: newApplicationScreenshot
316− property: "scale"
317− duration: 500
318− easing.type: Easing.OutQuad
319− to: 1.0
320− }
321− NumberAnimation {
322− target: newApplicationScreenshot
323− property: "opacity"
324− duration: 500
325− easing.type: Easing.OutQuad
326− to: 1.0
327− }
328−
329− PropertyAction {target: oldApplicationScreenshot; property: "visible"; value: true}
330−
331− NumberAnimation {
332− target: oldApplicationScreenshot
333− property: "x"
334− duration: 400
335− easing.type: Easing.OutQuad
336− to: stage.width
337− }
338− }
339−
340− ScriptAction {
341− script: {
342− // FIXME: only here to support case where application does
343− // not exist yet but is about to
344− if (stage.focusedApplicationWhenUsingScreenshots) {
345− stage.__focusApplication(stage.focusedApplicationWhenUsingScreenshots);
346− }
347− }
348− }
349− }
350−
351−
352− SequentialAnimation {
353− id: switchToApplicationAnimation
354−
355− function prepare(newApplication) {
356− var oldApplication = stage.focusedApplication;
357− newApplicationScreenshot.setApplication(newApplication);
358− newApplicationScreenshot.updateFromCache();
359− oldApplicationScreenshot.setApplication(oldApplication);
360− oldApplicationScreenshot.scheduleUpdate();
361− newApplicationScreenshot.withBackground = false;
362− newApplicationScreenshot.y = __getApplicationY(newApplication);
363− oldApplicationScreenshot.y = __getApplicationY(oldApplication);
364− stage.focusedApplicationWhenUsingScreenshots = newApplication;
365− stage.switchingFromFullscreenToFullscreen = newApplication && newApplication.fullscreen;
366− }
367−
368− PropertyAction {target: newApplicationScreenshot; property: "visible"; value: false}
369− PropertyAction {target: oldApplicationScreenshot; property: "visible"; value: false}
370− PropertyAction {target: newApplicationScreenshot; property: "z"; value: 1}
371− PropertyAction {target: oldApplicationScreenshot; property: "z"; value: 0}
372− PropertyAction {target: newApplicationScreenshot; property: "scale"; value: 1.0}
373− PropertyAction {target: oldApplicationScreenshot; property: "scale"; value: 1.0}
374− PropertyAction {target: newApplicationScreenshot; property: "opacity"; value: 1.0}
375− PropertyAction {target: oldApplicationScreenshot; property: "opacity"; value: 1.0}
376− PropertyAction {target: newApplicationScreenshot; property: "x"; value: stage.width}
377− PropertyAction {target: oldApplicationScreenshot; property: "x"; value: 0.0}
378− PropertyAction {target: newApplicationScreenshot; property: "visible"; value: true}
379− PropertyAction {target: oldApplicationScreenshot; property: "visible"; value: true}
380−
381− ParallelAnimation {
382− NumberAnimation {
383− target: oldApplicationScreenshot
384− property: "scale"
385− duration: 500
386− easing.type: Easing.OutQuad
387− to: 0.8
388− }
389− NumberAnimation {
390− target: oldApplicationScreenshot
391− property: "opacity"
392− duration: 500
393− easing.type: Easing.OutQuad
394− to: 0.0
395− }
396−
397− NumberAnimation {
398− target: newApplicationScreenshot
399− property: "x"
400− duration: 500
401− easing.type: Easing.OutQuad
402− to: 0
403− }
404− }
405−
406− ScriptAction {
407− script: {
408− // move previously focused application to the end of the list of running applications
409− var oldApplication = oldApplicationScreenshot.application;
410− if (oldApplication != null) {
411− var oldApplicationIndex = stage.__getApplicationIndex(oldApplicationScreenshot.application);
412− applicationManager.moveRunningApplicationStackPosition(
413− oldApplicationIndex, stage.applications.count - 1, stage.type);
414− }
415−
416− stage.__focusApplication(stage.focusedApplicationWhenUsingScreenshots);
417− }
418− }
419− }
420−
421−
422− function __dragValueToProgress(dragValue, startValue, applicationsCount) {
423− if (applicationsCount == 0) {
424− return 0;
425− } else if (applicationsCount == 1) {
426− // prevent the current app from moving more than 1/4 of the screen
427− var elasticValue = __elastic(dragValue, startValue/4);
428− return elasticValue / startValue;
429− } else {
430− return dragValue / startValue;
431− }
432− }
433−
434− function __elastic(x, limit) {
435− return limit * ( 1 - Math.pow( ( limit - 1 ) / limit, x ) )
436− }
437−
438− property real __startValue: stage.width
439−
440− AnimationControllerWithSignals {
441− id: switchToApplicationAnimationController
442−
443− onAnimationCompletedAtBeginning: {
444− animation = null;
445− stage.__focusApplication(oldApplicationScreenshot.application);
446− }
447− }
448−
449− EdgeDragEvaluator {
450− id: dragEvaluator
451− trackedPosition: rightEdgeDraggingArea.touchX
452− direction: rightEdgeDraggingArea.direction
453− maxDragDistance: stage.width
454− }
455−
456− EdgeDragArea {
457− id: rightEdgeDraggingArea
458−
459− enabled: {
460− if (status == DirectionalDragArea.WaitingForTouch) {
461− return switchToApplicationAnimationController.completed
462− && stage.applications.count > 0 && stage.rightEdgeEnabled
463− && !delayedHideScreenshots.running
464− } else {
465− return true
466− }
467− }
468− width: units.gu(2)
469− anchors.top: parent.top
470− anchors.bottom: parent.bottom
471− anchors.right: parent.right
472−
473− direction: Direction.Leftwards
474−
475− onDistanceChanged: {
476− if (status !== DirectionalDragArea.Recognized)
477− return
478−
479− var targetProgress = __dragValueToProgress(-distance, __startValue,
480− stage.applications.count)
481− var delta = targetProgress - switchToApplicationAnimationController.progress
482−
483− // When the gesture finally gets recognized, the finger will likely be
484− // reasonably far from the edge. If we made the progress immediately
485− // follow the finger position it would be visually unpleasant as there
486− // would be a jump to meet the finger's current position.
487− //
488− // The trick is not to go all the way (1.0) as it would cause the
489− // aforementioned jump.
490− switchToApplicationAnimationController.progress += 0.4 * delta
491− }
492−
493− property int lastStatus: -1
494−
495− onStatusChanged: {
496− if (status == DirectionalDragArea.Undecided) {
497− onUndecided();
498− } else if (status == DirectionalDragArea.WaitingForTouch) {
499− if (lastStatus == DirectionalDragArea.Undecided) {
500− // got rejected
501− switchToApplicationAnimationController.completeToBeginningWithSignal();
502− } else {
503− onGestureEnded();
504− }
505− }
506− lastStatus = status;
507− }
508−
509− function onUndecided() {
510− dragEvaluator.reset();
511−
512− var nextApplication = null;
513− if (stage.applications.count > 1) {
514− nextApplication = stage.applications.get(1);
515− }
516− switchToApplicationAnimationController.progress = 0
517− switchToApplicationAnimation.prepare(nextApplication);
518− switchToApplicationAnimationController.animation = switchToApplicationAnimation;
519−
520− }
521−
522− function onGestureEnded() {
523− if (stage.applications.count > 1 && dragEvaluator.shouldAutoComplete()) {
524− switchToApplicationAnimationController.completeToEndWithSignal();
525− } else {
526− switchToApplicationAnimationController.completeToBeginningWithSignal();
527− }
528− }
529− }
530−
531−
532− // FIXME: very bad hack to detect when an application was closed or crashed
533− property var __previouslyFocusedApplication
534− Connections {
535− target: stage
536− onFocusedApplicationChanged: {
537− if (stage.focusedApplication == null
538− && __getApplicationIndex(stage.__previouslyFocusedApplication) == -1) {
539− stage.__onFocusedApplicationClosed();
540− }
541− stage.__previouslyFocusedApplication = stage.focusedApplication;
542− }
543− }
544−
545− function __onFocusedApplicationClosed() {
546− if (!stage.shown) {
547− return;
548− }
549−
550− if (stage.applications.count > 0) {
551− // Do not change the focused application from within onFocusedApplicationChanged.
552− // This causes binding loop warnings besides being a bad practice in general
553− // (setting a property from within its "changed" signal handling)
554− // Hence the timer.
555− focusTopmostAppTimer.start();
556− } else {
557− stage.hide();
558− }
559− }
560−
561− Timer {
562− id: focusTopmostAppTimer
563− interval: 1; repeat: false
564− onTriggered: {
565− if (stage.applications.count > 0) {
566− stage.__focusApplication(stage.applications.get(0));
567− }
568− }
569− }
570−
571−}
qml/Components/ZoomableImage.qml−2
@@ -122,14 +122,12 @@ Item {
122 onPinchUpdated: {
122 onPinchUpdated: {
123 image.scale = pinch.scale;
123 image.scale = pinch.scale;
124 pinch.accepted = true;
124 pinch.accepted = true;
125− console.log("Pinch Updated pinch.scale = "+pinch.scale+" image.scale = "+image.scale);
126 }
125 }
127
126
128 onPinchFinished: {
127 onPinchFinished: {
129 image.scale = pinch.scale;
128 image.scale = pinch.scale;
130 flickable.returnToBounds();
129 flickable.returnToBounds();
131 pinch.accepted = true;
130 pinch.accepted = true;
132− console.log("Pinch Finished pinch.scale = "+pinch.scale+" image.scale = "+image.scale);
133 }
131 }
134 }
132 }
135
133
qml/Dash/Apps/RunningApplicationTile.qml+14−24
@@ -23,7 +23,6 @@ import "../../Components/ListItems"
23AbstractButton {
23AbstractButton {
24 id: root
24 id: root
25 property var application
25 property var application
26− property bool __sideStageEnabled: shell.applicationManager.sideStageEnabled
27
26
28 signal requestedApplicationActivation(var application)
27 signal requestedApplicationActivation(var application)
29 signal requestedApplicationTermination(var application)
28 signal requestedApplicationTermination(var application)
@@ -34,7 +33,7 @@ AbstractButton {
34 // To avoid "binding loop" warning
33 // To avoid "binding loop" warning
35 height: shapedApplicationImage.height + labelContainer.height
34 height: shapedApplicationImage.height + labelContainer.height
36
35
37− width: shapedApplicationImage.width
36+ width: shapedApplicationImage.width <= units.gu(11) ? units.gu(11) : height
38
37
39 property bool terminationModeEnabled: false
38 property bool terminationModeEnabled: false
40
39
@@ -51,32 +50,23 @@ AbstractButton {
51 }
50 }
52 }
51 }
53
52
54− function updateScreenshotFromCache() {
55− applicationImage.updateFromCache();
56− }
57−
58− // FIXME: should use UbuntuShape from SDK
53+ UbuntuShape {
59− UbuntuShapeForItem {
60 id: shapedApplicationImage
54 id: shapedApplicationImage
61− anchors {
62− top: parent.top
63− horizontalCenter: parent.horizontalCenter
55+ anchors { top: parent.top; horizontalCenter: parent.horizontalCenter }
64− }
65
56
66− // FIXME: width and height should be defined according to the
67− // application window's aspect ratio.
68− width: (application.stage === ApplicationInfo.MainStage && __sideStageEnabled) ?
69− units.gu(22) : units.gu(11)
57+ height: units.gu(17)
70− height: (__sideStageEnabled) ? units.gu(22) : units.gu(19)
58+ width: applicationImage.width
71 radius: "medium"
59 radius: "medium"
72− image: applicationImage
73− }
74
60
75− ApplicationImage {
61+ image: Image {
76− id: applicationImage
62+ id: applicationImage
77− source: ApplicationManager.findApplication((application) ? application.appId : "")
63+ source: application.screenshot
78− width: shapedApplicationImage.width
64+ // height : width = ss.height : ss.width
79− height: shapedApplicationImage.height
65+ height: shapedApplicationImage.height
66+ fillMode: Image.PreserveAspectCrop
67+ width: Math.min(height, height * sourceSize.width / sourceSize.height)
68+ }
69+
80 }
70 }
81
71
82 UbuntuShape {
72 UbuntuShape {
qml/Dash/Apps/RunningApplicationsGrid.qml+5−20
@@ -18,6 +18,7 @@ import QtQuick 2.0
18import "../../Components"
18import "../../Components"
19
19
20import Ubuntu.Gestures 0.1
20import Ubuntu.Gestures 0.1
21+import Unity.Application 0.1
21
22
22ResponsiveFlowView {
23ResponsiveFlowView {
23 id: root
24 id: root
@@ -25,13 +26,7 @@ ResponsiveFlowView {
25
26
26 signal updateScreenshots
27 signal updateScreenshots
27 property alias enableHeightBehavior: heightBehaviour.enabled
28 property alias enableHeightBehavior: heightBehaviour.enabled
28− property bool enableHeightBehaviorOnNextCreation: firstModel.count + secondModel.count == 0
29+ property bool enableHeightBehaviorOnNextCreation: model.count === 0
29−
30− Connections {
31− target: shell
32− onDashShownChanged: if (shell.dashShown && shell.stageScreenshotsReady) updateScreenshots();
33− onStageScreenshotsReadyChanged: if (shell.dashShown && shell.stageScreenshotsReady) updateScreenshots();
34− }
35
30
36 Behavior on height {
31 Behavior on height {
37 id: heightBehaviour
32 id: heightBehaviour
@@ -40,13 +35,7 @@ ResponsiveFlowView {
40 }
35 }
41
36
42 Connections {
37 Connections {
43− target: root.firstModel
38+ target: root.model
44− onCountChanged: {
45− heightBehaviour.enabled = true;
46− }
47− }
48− Connections {
49− target: root.secondModel
50 onCountChanged: {
39 onCountChanged: {
51 heightBehaviour.enabled = true;
40 heightBehaviour.enabled = true;
52 }
41 }
@@ -85,17 +74,13 @@ ResponsiveFlowView {
85 root.terminationModeEnabled = true
74 root.terminationModeEnabled = true
86 }
75 }
87 onRequestedApplicationTermination: {
76 onRequestedApplicationTermination: {
88− shell.applicationManager.stopApplication(model.appId)
77+ ApplicationManager.stopApplication(model.appId)
89 }
78 }
90 onRequestedApplicationActivation: {
79 onRequestedApplicationActivation: {
91− shell.activateApplication(model.appId)
80+ ApplicationManager.requestFocusApplication(model.appId)
92 }
81 }
93
82
94 terminationModeEnabled: root.terminationModeEnabled
83 terminationModeEnabled: root.terminationModeEnabled
95−
96− Component.onCompleted: {
97− root.updateScreenshots.connect(updateScreenshotFromCache);
98− }
99 }
84 }
100 }
85 }
101
86
qml/Dash/Card.qml+130−90
@@ -16,6 +16,8 @@
16
16
17import QtQuick 2.0
17import QtQuick 2.0
18import Ubuntu.Components 0.1
18import Ubuntu.Components 0.1
19+// For image://albumart and image://thumbnailer image providers
20+import Ubuntu.Thumbnailer 0.1
19
21
20AbstractButton {
22AbstractButton {
21 id: root
23 id: root
@@ -23,47 +25,53 @@ AbstractButton {
23 property var components
25 property var components
24 property var cardData
26 property var cardData
25
27
26− property alias fontScale: header.fontScale
28+ property real fontScale: 1.0
27− property alias headerAlignment: header.headerAlignment
29+ property int headerAlignment: Text.AlignLeft
28− property alias headerHeight: header.height
30+ readonly property int headerHeight: headerLoader.item ? headerLoader.item.height : 0
31+ property int fixedHeaderHeight: -1
29− readonly property alias title: header.title
32+ readonly property string title: cardData && cardData["title"] || ""
30
33
31 property bool showHeader: true
34 property bool showHeader: true
32
35
33 implicitWidth: childrenRect.width
36 implicitWidth: childrenRect.width
34− implicitHeight: summary.y + summary.height + (summary.text && background.visible ? units.gu(1) : 0)
37+ implicitHeight: summary.y + summary.height + (summary.text && backgroundLoader.active ? units.gu(1) : 0)
35
38
36− UbuntuShape {
39+ Loader {
37− id: background
40+ id: backgroundLoader
38− objectName: "background"
41+ objectName: "backgroundLoader"
39− radius: "medium"
40− visible: template["card-layout"] !== "horizontal" && (template["card-background"] || components["background"]
41− || artAndSummary)
42− property bool artAndSummary: components["art"]["field"] && components["summary"] || false
43− color: getColor(0) || "white"
44− gradientColor: getColor(1) || color
45− anchors.fill: parent
46− image: backgroundImage.source ? backgroundImage : null
47
42
48− property real luminance: 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b
43+ readonly property bool artAndSummary: components["art"]["field"] && components["summary"] || false
44+ active: template["card-layout"] !== "horizontal" && (template["card-background"] || components["background"] || artAndSummary)
45+ anchors.fill: parent
49
46
47+ sourceComponent: UbuntuShape {
48+ objectName: "background"
49+ radius: "medium"
50+ color: getColor(0) || "white"
51+ gradientColor: getColor(1) || color
52+ anchors.fill: parent
53+ image: backgroundImage.source ? backgroundImage : null
54+
55+ property real luminance: 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b
56+
50− property Image backgroundImage: Image {
57+ property Image backgroundImage: Image {
51− objectName: "backgroundImage"
58+ objectName: "backgroundImage"
52− source: {
59+ source: {
53− if (cardData && typeof cardData["background"] === "string") return cardData["background"]
60+ if (cardData && typeof cardData["background"] === "string") return cardData["background"]
54− else if (template && typeof template["card-background"] === "string") return template["card-background"]
61+ else if (template && typeof template["card-background"] === "string") return template["card-background"]
55− else return ""
62+ else return ""
63+ }
56 }
64 }
57− }
58
65
59− function getColor(index) {
66+ function getColor(index) {
60− if (cardData && typeof cardData["background"] === "object"
67+ if (cardData && typeof cardData["background"] === "object"
61− && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
68+ && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
62− return cardData["background"]["elements"][index];
69+ return cardData["background"]["elements"][index];
63− } else if (template && typeof template["card-background"] === "object"
70+ } else if (template && typeof template["card-background"] === "object"
64− && (template["card-background"]["type"] === "color" || template["card-background"]["type"] === "gradient")) {
71+ && (template["card-background"]["type"] === "color" || template["card-background"]["type"] === "gradient")) {
65− return template["card-background"]["elements"][index];
72+ return template["card-background"]["elements"][index];
66− } else return undefined;
73+ } else return undefined;
74+ }
67 }
75 }
68 }
76 }
69
77
@@ -71,23 +79,27 @@ AbstractButton {
71 id: artShape
79 id: artShape
72 radius: "medium"
80 radius: "medium"
73 objectName: "artShape"
81 objectName: "artShape"
74− width: {
75− if (!visible) return 0
76− return image.fillMode === Image.PreserveAspectCrop || aspect < image.aspect ? image.width : height * image.aspect
77− }
78− height: {
79− if (!visible) return 0
80− return image.fillMode === Image.PreserveAspectCrop || aspect > image.aspect ? image.height : width / image.aspect
81− }
82 anchors.horizontalCenter: template && template["card-layout"] === "horizontal" ? undefined : parent.horizontalCenter
82 anchors.horizontalCenter: template && template["card-layout"] === "horizontal" ? undefined : parent.horizontalCenter
83 anchors.left: template && template["card-layout"] === "horizontal" ? parent.left : undefined
83 anchors.left: template && template["card-layout"] === "horizontal" ? parent.left : undefined
84 visible: cardData && cardData["art"] || false
84 visible: cardData && cardData["art"] || false
85
85
86− property real aspect: components !== undefined ? components["art"]["aspect-ratio"] : 1
86+ readonly property real aspect: components !== undefined ? components["art"]["aspect-ratio"] : 1
87+ readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect
88+
89+ Component.onCompleted: updateWidthHeightBindings();
90+ onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings();
91+
92+ function updateWidthHeightBindings() {
93+ if (aspectSmallerThanImageAspect) {
94+ width = Qt.binding(function() { return !visible ? 0 : image.width });
95+ height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect });
96+ } else {
97+ width = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.width : height * image.aspect });
98+ height = Qt.binding(function() { return !visible ? 0 : image.height });
99+ }
100+ }
87
101
88 image: Image {
102 image: Image {
89− width: template && template["card-layout"] === "horizontal" ? height * artShape.aspect : root.width
90− height: template && template["card-layout"] === "horizontal" ? header.height : width / artShape.aspect
91 objectName: "artImage"
103 objectName: "artImage"
92 source: cardData && cardData["art"] || ""
104 source: cardData && cardData["art"] || ""
93 cache: true
105 cache: true
@@ -96,57 +108,76 @@ AbstractButton {
96 //sourceSize.height: height > width ? height : 0
108 //sourceSize.height: height > width ? height : 0
97 fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop
109 fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop
98
110
99− property real aspect: implicitWidth / implicitHeight
111+ readonly property real aspect: implicitWidth / implicitHeight
112+ readonly property bool isHorizontal: template && template["card-layout"] === "horizontal"
113+
114+ Component.onCompleted: updateWidthHeightBindings();
115+ onIsHorizontalChanged: updateWidthHeightBindings();
116+
117+ function updateWidthHeightBindings() {
118+ if (isHorizontal) {
119+ width = Qt.binding(function() { return height * artShape.aspect });
120+ height = Qt.binding(function() { return root.headerHeight });
121+ } else {
122+ width = Qt.binding(function() { return root.width });
123+ height = Qt.binding(function() { return width / artShape.aspect });
124+ }
125+ }
100 }
126 }
101 }
127 }
102
128
103− ShaderEffect {
129+ Loader {
104− id: overlay
130+ id: overlayLoader
105 anchors {
131 anchors {
106 left: artShape.left
132 left: artShape.left
107 right: artShape.right
133 right: artShape.right
108 bottom: artShape.bottom
134 bottom: artShape.bottom
109 }
135 }
136+ active: template && template["overlay"] && artShape.visible && artShape.image.status === Image.Ready || false
110
137
111− height: header.height
138+ sourceComponent: ShaderEffect {
112− opacity: header.opacity * 0.6
139+ id: overlay
113− visible: template && template["overlay"] && artShape.visible && artShape.image.status === Image.Ready || false
114
140
115− property var source: ShaderEffectSource {
141+ height: headerLoader.item ? headerLoader.item.height : 0
116− id: shaderSource
142+ opacity: headerLoader.item ? headerLoader.item.opacity * 0.6 : 0
117− sourceItem: artShape
118− onVisibleChanged: if (visible) scheduleUpdate()
119− live: false
120− sourceRect: Qt.rect(0, artShape.height - overlay.height, artShape.width, overlay.height)
121− }
122
143
144+ property var source: ShaderEffectSource {
145+ id: shaderSource
146+ sourceItem: artShape
147+ onVisibleChanged: if (visible) scheduleUpdate()
148+ live: false
149+ sourceRect: Qt.rect(0, artShape.height - overlay.height, artShape.width, overlay.height)
150+ }
151+
123− vertexShader: "
152+ vertexShader: "
124− uniform highp mat4 qt_Matrix;
153+ uniform highp mat4 qt_Matrix;
125− attribute highp vec4 qt_Vertex;
154+ attribute highp vec4 qt_Vertex;
126− attribute highp vec2 qt_MultiTexCoord0;
155+ attribute highp vec2 qt_MultiTexCoord0;
127− varying highp vec2 coord;
156+ varying highp vec2 coord;
128− void main() {
157+ void main() {
129− coord = qt_MultiTexCoord0;
158+ coord = qt_MultiTexCoord0;
130− gl_Position = qt_Matrix * qt_Vertex;
159+ gl_Position = qt_Matrix * qt_Vertex;
131− }"
160+ }"
132−
161+
133− fragmentShader: "
162+ fragmentShader: "
134− varying highp vec2 coord;
163+ varying highp vec2 coord;
135− uniform sampler2D source;
164+ uniform sampler2D source;
136− uniform lowp float qt_Opacity;
165+ uniform lowp float qt_Opacity;
137− void main() {
166+ void main() {
138− lowp vec4 tex = texture2D(source, coord);
167+ lowp vec4 tex = texture2D(source, coord);
139− gl_FragColor = vec4(0, 0, 0, tex.a) * qt_Opacity;
168+ gl_FragColor = vec4(0, 0, 0, tex.a) * qt_Opacity;
140− }"
169+ }"
170+ }
141 }
171 }
142
172
143− CardHeader {
173+ Loader {
144− id: header
174+ id: headerLoader
145− objectName: "cardHeader"
175+ objectName: "cardHeaderLoader"
176+
146 anchors {
177 anchors {
147 top: {
178 top: {
148 if (template) {
179 if (template) {
149− if (template["overlay"]) return overlay.top;
180+ if (template["overlay"]) return overlayLoader.top;
150 if (template["card-layout"] === "horizontal") return artShape.top;
181 if (template["card-layout"] === "horizontal") return artShape.top;
151 }
182 }
152 return artShape.bottom;
183 return artShape.bottom;
@@ -159,29 +190,38 @@ AbstractButton {
159 }
190 }
160 right: parent.right
191 right: parent.right
161 }
192 }
193+ active: cardData && cardData["title"] || cardData && cardData["mascot"] || false
194+
195+ sourceComponent: CardHeader {
196+ id: header
197+ objectName: "cardHeader"
162
198
163− mascot: cardData && cardData["mascot"] || ""
199+ mascot: cardData && cardData["mascot"] || ""
164− title: cardData && cardData["title"] || ""
200+ title: root.title
165− subtitle: cardData && cardData["subtitle"] || ""
201+ subtitle: cardData && cardData["subtitle"] || ""
166
202
167− titleWeight: components && components["subtitle"] ? Font.DemiBold : Font.Normal
203+ titleWeight: components && components["subtitle"] ? Font.DemiBold : Font.Normal
168
204
169− opacity: showHeader ? 1 : 0
205+ opacity: showHeader ? 1 : 0
170− inOverlay: root.template && root.template["overlay"] === true
206+ inOverlay: root.template && root.template["overlay"] === true
171− fontColor: inOverlay ? "white" : summary.color
207+ fontColor: inOverlay ? "white" : summary.color
172− useMascotShape: !background.visible && !inOverlay
208+ useMascotShape: !backgroundLoader.active && !inOverlay
209+ headerAlignment: root.headerAlignment
210+ height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight
211+ fontScale: root.fontScale
173
212
174− Behavior on opacity { NumberAnimation { duration: UbuntuAnimation.SnapDuration } }
213+ Behavior on opacity { NumberAnimation { duration: UbuntuAnimation.SnapDuration } }
214+ }
175 }
215 }
176
216
177 Label {
217 Label {
178 id: summary
218 id: summary
179 objectName: "summaryLabel"
219 objectName: "summaryLabel"
180 anchors {
220 anchors {
181− top: header.visible ? header.bottom : artShape.bottom
221+ top: headerLoader.active ? headerLoader.bottom : artShape.bottom
182 left: parent.left
222 left: parent.left
183 right: parent.right
223 right: parent.right
184− margins: background.visible ? units.gu(1) : 0
224+ margins: backgroundLoader.active ? units.gu(1) : 0
185 topMargin: 0
225 topMargin: 0
186 }
226 }
187 wrapMode: Text.Wrap
227 wrapMode: Text.Wrap
@@ -191,6 +231,6 @@ AbstractButton {
191 height: text ? implicitHeight : 0
231 height: text ? implicitHeight : 0
192 fontSize: "small"
232 fontSize: "small"
193 // TODO karni: Change "grey" to Ubuntu.Components.Palette color once updated.
233 // TODO karni: Change "grey" to Ubuntu.Components.Palette color once updated.
194− color: background.visible && background.luminance < 0.7 ? "white" : "grey"
234+ color: backgroundLoader.active && backgroundLoader.item.luminance < 0.7 ? "white" : "grey"
195 }
235 }
196}
236}
qml/Dash/CardCarousel.qml+1−1
@@ -52,7 +52,7 @@ DashRenderer {
52 itemComponent: Card {
52 itemComponent: Card {
53 id: card
53 id: card
54 objectName: "carouselDelegate" + index
54 objectName: "carouselDelegate" + index
55− headerHeight: carousel.headerHeight
55+ fixedHeaderHeight: carousel.headerHeight
56 cardData: model
56 cardData: model
57 template: cardTool.template
57 template: cardTool.template
58 components: cardTool.components
58 components: cardTool.components
qml/Dash/CardFilterGrid.qml+5−6
@@ -21,12 +21,10 @@ DashRenderer {
21 id: genericFilterGrid
21 id: genericFilterGrid
22
22
23 expandable: filterGrid.expandable
23 expandable: filterGrid.expandable
24− collapsedRowCount: Math.min(2, cardTool && cardTool.template && cardTool.template["collapsed-rows"] || 2)
25 collapsedHeight: filterGrid.collapsedHeight
24 collapsedHeight: filterGrid.collapsedHeight
26− columns: filterGrid.columns
27− rows: filter ? collapsedRowCount : uncollapsedRowCount
28 margins: filterGrid.margins
25 margins: filterGrid.margins
29 uncollapsedHeight: filterGrid.uncollapsedHeight
26 uncollapsedHeight: filterGrid.uncollapsedHeight
27+ originY: filterGrid.originY
30 verticalSpacing: units.gu(1)
28 verticalSpacing: units.gu(1)
31 currentItem: filterGrid.currentItem
29 currentItem: filterGrid.currentItem
32 height: filterGrid.height
30 height: filterGrid.height
@@ -44,17 +42,18 @@ DashRenderer {
44 verticalSpacing: genericFilterGrid.verticalSpacing
42 verticalSpacing: genericFilterGrid.verticalSpacing
45 model: genericFilterGrid.model
43 model: genericFilterGrid.model
46 filter: genericFilterGrid.filter
44 filter: genericFilterGrid.filter
47− collapsedRowCount: genericFilterGrid.collapsedRowCount
45+ collapsedRowCount: Math.min(2, cardTool && cardTool.template && cardTool.template["collapsed-rows"] || 2)
48 delegateCreationBegin: genericFilterGrid.delegateCreationBegin
46 delegateCreationBegin: genericFilterGrid.delegateCreationBegin
49 delegateCreationEnd: genericFilterGrid.delegateCreationEnd
47 delegateCreationEnd: genericFilterGrid.delegateCreationEnd
50− delegate: Item {
48+ delegate: Loader {
49+ asynchronous: true
51 width: filterGrid.cellWidth
50 width: filterGrid.cellWidth
52 height: filterGrid.cellHeight
51 height: filterGrid.cellHeight
53 Card {
52 Card {
54 id: card
53 id: card
55 width: cardTool.cardWidth
54 width: cardTool.cardWidth
56 height: cardTool.cardHeight
55 height: cardTool.cardHeight
57− headerHeight: cardTool.headerHeight
56+ fixedHeaderHeight: cardTool.headerHeight
58 anchors.horizontalCenter: parent.horizontalCenter
57 anchors.horizontalCenter: parent.horizontalCenter
59 objectName: "delegate" + index
58 objectName: "delegate" + index
60 cardData: model
59 cardData: model
qml/Dash/CardHeader.qml+41−31
@@ -19,9 +19,9 @@ import Ubuntu.Components 0.1
19
19
20Item {
20Item {
21 id: root
21 id: root
22− property alias mascot: mascotImage.source
22+ property url mascot: ""
23 property alias title: titleLabel.text
23 property alias title: titleLabel.text
24− property alias subtitle: subtitleLabel.text
24+ property var subtitle
25
25
26 property alias titleWeight: titleLabel.font.weight
26 property alias titleWeight: titleLabel.font.weight
27 property alias titleSize: titleLabel.fontSize
27 property alias titleSize: titleLabel.fontSize
@@ -35,8 +35,8 @@ Item {
35 property bool useMascotShape: true
35 property bool useMascotShape: true
36 property color fontColor: Theme.palette.selected.backgroundText
36 property color fontColor: Theme.palette.selected.backgroundText
37
37
38− visible: mascotImage.status === Image.Ready || title
38+ visible: mascot != "" || title
39− height: row.height > 0 ? row.height + row.margins * 2 : 0
39+ implicitHeight: row.height > 0 ? row.height + row.margins * 2 : 0
40
40
41 Row {
41 Row {
42 id: row
42 id: row
@@ -44,7 +44,7 @@ Item {
44
44
45 property real margins: units.gu(1)
45 property real margins: units.gu(1)
46
46
47− spacing: mascotShape.visible || mascotImage.visible || inOverlay ? margins : 0
47+ spacing: mascotShapeLoader.active || mascotImageLoader.active || inOverlay ? margins : 0
48 anchors {
48 anchors {
49 top: parent.top; left: parent.left; right: parent.right
49 top: parent.top; left: parent.left; right: parent.right
50 margins: margins
50 margins: margins
@@ -52,33 +52,40 @@ Item {
52 rightMargin: spacing
52 rightMargin: spacing
53 }
53 }
54
54
55− UbuntuShape {
55+ Loader {
56− id: mascotShape
56+ id: mascotShapeLoader
57− objectName: "mascotShape"
57+ objectName: "mascotShapeLoader"
58
58
59+ active: useMascotShape && mascotImageLoader.item && mascotImageLoader.item.status === Image.Ready
60+ visible: active
61+ anchors.verticalCenter: parent.verticalCenter
59 // TODO karni: Icon aspect-ratio is 8:7.5. Revisit these values to avoid fraction of pixels.
62 // TODO karni: Icon aspect-ratio is 8:7.5. Revisit these values to avoid fraction of pixels.
60 width: units.gu(6)
63 width: units.gu(6)
61 height: units.gu(5.625)
64 height: units.gu(5.625)
62− anchors.verticalCenter: parent.verticalCenter
63− visible: useMascotShape && image && image.status === Image.Ready
64 readonly property int maxSize: Math.max(width, height) * 4
65 readonly property int maxSize: Math.max(width, height) * 4
65
66
66− image: useMascotShape ? mascotImage : null
67+ sourceComponent: UbuntuShape {
68+ image: mascotImageLoader.item
69+ }
67 }
70 }
68
71
69− Image {
72+ Loader {
70− id: mascotImage
73+ id: mascotImageLoader
71− objectName: "mascotImage"
74+ active: root.mascot != ""
72−
75+ visible: active && !useMascotShape && item.status === Image.Ready
73− width: source ? mascotShape.width : 0
74− height: mascotShape.height
75 anchors.verticalCenter: parent.verticalCenter
76 anchors.verticalCenter: parent.verticalCenter
76− visible: !useMascotShape && status === Image.Ready
77+ sourceComponent: Image {
78+ objectName: "mascotImage"
79+
80+ source: root.mascot
81+ width: source ? mascotShapeLoader.width : 0
82+ height: mascotShapeLoader.height
77
83
78− sourceSize { width: mascotShape.maxSize; height: mascotShape.maxSize }
84+ sourceSize { width: mascotShapeLoader.maxSize; height: mascotShapeLoader.maxSize }
79− fillMode: Image.PreserveAspectCrop
85+ fillMode: Image.PreserveAspectCrop
80− horizontalAlignment: Image.AlignHCenter
86+ horizontalAlignment: Image.AlignHCenter
81− verticalAlignment: Image.AlignVCenter
87+ verticalAlignment: Image.AlignVCenter
88+ }
82 }
89 }
83
90
84 Column {
91 Column {
@@ -100,16 +107,19 @@ Item {
100 color: fontColor
107 color: fontColor
101 }
108 }
102
109
103− Label {
110+ Loader {
104− id: subtitleLabel
111+ active: titleLabel.text && root.subtitle
105− objectName: "subtitleLabel"
106 anchors { left: parent.left; right: parent.right }
112 anchors { left: parent.left; right: parent.right }
113+ sourceComponent: Label {
114+ id: subtitleLabel
115+ objectName: "subtitleLabel"
107− elide: Text.ElideRight
116+ elide: Text.ElideRight
108− fontSize: "small"
117+ fontSize: "small"
109− font.weight: Font.Light
118+ font.weight: Font.Light
110− visible: titleLabel.text && text
111− font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale)
119+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale)
112− color: fontColor
120+ color: fontColor
121+ text: root.subtitle
122+ }
113 }
123 }
114 }
124 }
115 }
125 }
qml/Dash/DashApps.qml+4−5
@@ -17,6 +17,7 @@
17import QtQuick 2.0
17import QtQuick 2.0
18import Ubuntu.Components 0.1
18import Ubuntu.Components 0.1
19import Utils 0.1
19import Utils 0.1
20+import Unity.Application 0.1
20import "../Components"
21import "../Components"
21import "../Components/ListItems"
22import "../Components/ListItems"
22import "Apps"
23import "Apps"
@@ -25,17 +26,15 @@ GenericScopeView {
25 id: scopeView
26 id: scopeView
26 objectName: "DashApps"
27 objectName: "DashApps"
27
28
28− // FIXME: a way to aggregate these models would be ideal
29+ property var runningApps: ApplicationManager
29− property var mainStageApplicationsModel: shell.applicationManager.mainStageApplications
30− property var sideStageApplicationModel: shell.applicationManager.sideStageApplications
31
30
32 QtObject {
31 QtObject {
33 id: countObject
32 id: countObject
34− property int count: scopeView.scope.searchQuery.length == 0 ? (mainStageApplicationsModel.count + sideStageApplicationModel.count) : 0
33+ property int count: scopeView.scope.searchQuery.length == 0 ? scopeView.runningApps.count : 0
35 }
34 }
36
35
37 onScopeChanged: {
36 onScopeChanged: {
38 scopeView.scope.categories.addSpecialCategory("running.apps.category", i18n.tr("Recent"), "", "{ \"template\": { \"category-layout\": \"running-apps\" } }", countObject);
37 scopeView.scope.categories.addSpecialCategory("running.apps.category", i18n.tr("Recent"), "", "{ \"template\": { \"category-layout\": \"running-apps\" } }", countObject);
39− enableHeightBehaviorOnNextCreation = (mainStageApplicationsModel.count + sideStageApplicationModel.count == 0)
38+ enableHeightBehaviorOnNextCreation = scopeView.runningApps.count === 0
40 }
39 }
41}
40}
qml/Dash/DashContent.qml+5−10
@@ -27,13 +27,9 @@ Item {
27 readonly property alias currentIndex: dashContentList.currentIndex
27 readonly property alias currentIndex: dashContentList.currentIndex
28 property alias previewOpen: previewListView.open
28 property alias previewOpen: previewListView.open
29
29
30− property ScopeDelegateMapper scopeMapper : ScopeDelegateMapper {}
31 property ListModel searchHistory
30 property ListModel searchHistory
32
31
33− signal movementStarted()
34− signal movementEnded()
35 signal scopeLoaded(string scopeId)
32 signal scopeLoaded(string scopeId)
36− signal positionedAtBeginning()
37 signal gotoScope(string scopeId)
33 signal gotoScope(string scopeId)
38 signal openScope(var scope)
34 signal openScope(var scope)
39
35
@@ -71,7 +67,7 @@ Item {
71 dashContentList.currentIndex = index
67 dashContentList.currentIndex = index
72
68
73 if (reset) {
69 if (reset) {
74− dashContent.positionedAtBeginning()
70+ dashContentList.currentItem.item.positionAtBeginning()
75 }
71 }
76 }
72 }
77
73
@@ -114,8 +110,7 @@ Item {
114 highlightRangeMode: ListView.StrictlyEnforceRange
110 highlightRangeMode: ListView.StrictlyEnforceRange
115 // TODO Investigate if we can switch to a smaller cache buffer when/if UbuntuShape gets more performant
111 // TODO Investigate if we can switch to a smaller cache buffer when/if UbuntuShape gets more performant
116 cacheBuffer: 1073741823
112 cacheBuffer: 1073741823
117− onMovementStarted: dashContent.movementStarted()
113+ onMovementStarted: currentItem.item.showHeader();
118− onMovementEnded: dashContent.movementEnded()
119 clip: parent.x != 0
114 clip: parent.x != 0
120
115
121 // If the number of items is less than the current index, then need to reset to another item.
116 // If the number of items is less than the current index, then need to reset to another item.
@@ -135,7 +130,9 @@ Item {
135 width: ListView.view.width
130 width: ListView.view.width
136 height: ListView.view.height
131 height: ListView.view.height
137 asynchronous: true
132 asynchronous: true
138− source: scopeMapper.map(scope.id)
133+ // TODO This if will eventually go away since we're killing DashApps.qml
134+ // once we move app closing to the spread
135+ source: (scope.id == "clickscope") ? "DashApps.qml" : "GenericScopeView.qml"
139 objectName: scope.id + " loader"
136 objectName: scope.id + " loader"
140
137
141 readonly property bool moving: item ? item.moving : false
138 readonly property bool moving: item ? item.moving : false
@@ -154,8 +151,6 @@ Item {
154 item.scope = Qt.binding(function() { return scope })
151 item.scope = Qt.binding(function() { return scope })
155 item.isCurrent = Qt.binding(function() { return visible && ListView.isCurrentItem })
152 item.isCurrent = Qt.binding(function() { return visible && ListView.isCurrentItem })
156 item.tabBarHeight = dashPageHeader.implicitHeight;
153 item.tabBarHeight = dashPageHeader.implicitHeight;
157− dashContentList.movementStarted.connect(item.movementStarted)
158− dashContent.positionedAtBeginning.connect(item.positionedAtBeginning)
159 dashContent.scopeLoaded(item.scope.id)
154 dashContent.scopeLoaded(item.scope.id)
160 }
155 }
161 Connections {
156 Connections {
qml/Dash/DashRenderer.qml+2−6
@@ -23,14 +23,8 @@ Item {
23 // In case it can be expanded, should we filter it
23 // In case it can be expanded, should we filter it
24 property bool filter: true
24 property bool filter: true
25
25
26− property int collapsedRowCount: 1
27−
28 property int collapsedHeight: height
26 property int collapsedHeight: height
29
27
30− property int columns: 1
31−
32− property int rows: 1
33−
34 property int margins: 0
28 property int margins: 0
35
29
36 property int uncollapsedHeight: height
30 property int uncollapsedHeight: height
@@ -39,6 +33,8 @@ Item {
39
33
40 property var delegateCreationEnd: undefined
34 property var delegateCreationEnd: undefined
41
35
36+ property real originY: 0
37+
42 property real verticalSpacing: 0
38 property real verticalSpacing: 0
43
39
44 // The current item of the renderer
40 // The current item of the renderer
qml/Dash/GenericScopeView.qml+19−27
@@ -18,6 +18,7 @@ import QtQuick 2.0
18import Ubuntu.Components 0.1
18import Ubuntu.Components 0.1
19import Utils 0.1
19import Utils 0.1
20import Unity 0.2
20import Unity 0.2
21+import Unity.Application 0.1
21import "../Components"
22import "../Components"
22import "../Components/ListItems" as ListItems
23import "../Components/ListItems" as ListItems
23
24
@@ -32,17 +33,11 @@ FocusScope {
32 property PageHeader pageHeader: null
33 property PageHeader pageHeader: null
33 property Item previewListView: null
34 property Item previewListView: null
34
35
35− signal movementStarted
36− signal positionedAtBeginning
37−
38 property bool enableHeightBehaviorOnNextCreation: false
36 property bool enableHeightBehaviorOnNextCreation: false
39 property var categoryView: categoryView
37 property var categoryView: categoryView
40
38
41− // FIXME delay the search so that daemons have time to settle, note that
42− // removing this will break ScopeView::test_changeScope
43 onScopeChanged: {
39 onScopeChanged: {
44 if (scope) {
40 if (scope) {
45− timer.restart();
46 scope.activateApplication.connect(activateApp);
41 scope.activateApplication.connect(activateApp);
47 }
42 }
48 }
43 }
@@ -51,18 +46,20 @@ FocusScope {
51 shell.activateApplication(appId);
46 shell.activateApplication(appId);
52 }
47 }
53
48
49+ function positionAtBeginning() {
50+ categoryView.positionAtBeginning()
51+ }
52+
53+ function showHeader() {
54+ categoryView.showHeader()
55+ }
56+
54 Binding {
57 Binding {
55 target: scope
58 target: scope
56 property: "isActive"
59 property: "isActive"
57 value: isCurrent && !previewListView.open
60 value: isCurrent && !previewListView.open
58 }
61 }
59
62
60− Timer {
61− id: timer
62− interval: 2000
63− onTriggered: scope.searchQuery = ""
64− }
65−
66 SortFilterProxyModel {
63 SortFilterProxyModel {
67 id: categoryFilter
64 id: categoryFilter
68 model: scope ? scope.categories : null
65 model: scope ? scope.categories : null
@@ -77,10 +74,6 @@ FocusScope {
77 previewListView.open = false;
74 previewListView.open = false;
78 }
75 }
79
76
80− onMovementStarted: categoryView.showHeader()
81−
82− onPositionedAtBeginning: categoryView.positionAtBeginning()
83−
84 Binding {
77 Binding {
85 target: scopeView.scope
78 target: scopeView.scope
86 property: "searchQuery"
79 property: "searchQuery"
@@ -167,8 +160,7 @@ FocusScope {
167 if (source.toString().indexOf("Apps/RunningApplicationsGrid.qml") != -1) {
160 if (source.toString().indexOf("Apps/RunningApplicationsGrid.qml") != -1) {
168 // TODO: this is still a kludge :D Ideally add some kind of hook so that we
161 // TODO: this is still a kludge :D Ideally add some kind of hook so that we
169 // can do this from DashApps.qml or think a better way that needs no special casing
162 // can do this from DashApps.qml or think a better way that needs no special casing
170− item.firstModel = Qt.binding(function() { return mainStageApplicationsModel })
171− item.secondModel = Qt.binding(function() { return sideStageApplicationModel })
163+ item.model = Qt.binding(function() { return runningApps; })
172 item.canEnableTerminationMode = Qt.binding(function() { return scopeView.isCurrent })
164 item.canEnableTerminationMode = Qt.binding(function() { return scopeView.isCurrent })
173 } else {
165 } else {
174 item.model = Qt.binding(function() { return results })
166 item.model = Qt.binding(function() { return results })
@@ -250,22 +242,22 @@ FocusScope {
250 // to the stable position and delete/create items without any reason
242 // to the stable position and delete/create items without any reason
251 if (categoryView.contentY < categoryView.originY) {
243 if (categoryView.contentY < categoryView.originY) {
252 return;
244 return;
253− } else if (categoryView.contentY + categoryView.height > categoryView.contentHeight) {
245+ } else if (categoryView.contentHeight > categoryView.height && categoryView.contentY + categoryView.height > categoryView.contentHeight) {
254 return;
246 return;
255 }
247 }
256
248
257 if (item && item.hasOwnProperty("delegateCreationBegin")) {
249 if (item && item.hasOwnProperty("delegateCreationBegin")) {
258 if (baseItem.y + baseItem.height <= 0) {
250 if (baseItem.y + baseItem.height <= 0) {
259− // Not visible (item at top of the list)
251+ // Not visible (item at top of the list viewport)
260− item.delegateCreationBegin = baseItem.height
252+ item.delegateCreationBegin = item.originY + baseItem.height
261− item.delegateCreationEnd = baseItem.height
253+ item.delegateCreationEnd = item.originY + baseItem.height
262 } else if (baseItem.y >= categoryView.height) {
254 } else if (baseItem.y >= categoryView.height) {
263− // Not visible (item at bottom of the list)
255+ // Not visible (item at bottom of the list viewport)
264− item.delegateCreationBegin = 0
256+ item.delegateCreationBegin = item.originY
265− item.delegateCreationEnd = 0
257+ item.delegateCreationEnd = item.originY
266 } else {
258 } else {
267− item.delegateCreationBegin = Math.max(-baseItem.y, 0)
259+ item.delegateCreationBegin = item.originY + Math.max(-baseItem.y, 0)
268− item.delegateCreationEnd = Math.min(categoryView.height + item.delegateCreationBegin, baseItem.height)
260+ item.delegateCreationEnd = item.originY + Math.min(categoryView.height + item.delegateCreationBegin, baseItem.height)
269 }
261 }
270 }
262 }
271 }
263 }
qml/Dash/Previews/PreviewRatingDisplay.qml+4−2
@@ -71,7 +71,8 @@ PreviewWidget {
71 id: authorLabel
71 id: authorLabel
72 objectName: "authorLabel"
72 objectName: "authorLabel"
73 anchors { left: parent.left; right: parent.right }
73 anchors { left: parent.left; right: parent.right }
74+ // TODO cimi: Yet another fix requiring Palette update.
74− color: Theme.palette.selected.backgroundText
75+ color: "grey" //Theme.palette.selected.backgroundText
75 opacity: .8
76 opacity: .8
76 text: modelData["author"] || ""
77 text: modelData["author"] || ""
77 visible: text !== ""
78 visible: text !== ""
@@ -82,7 +83,8 @@ PreviewWidget {
82 id: reviewLabel
83 id: reviewLabel
83 objectName: "reviewLabel"
84 objectName: "reviewLabel"
84 anchors { left: parent.left; right: parent.right }
85 anchors { left: parent.left; right: parent.right }
86+ // TODO cimi: Yet another fix requiring Palette update.
85− color: Theme.palette.selected.backgroundText
87+ color: "grey" //Theme.palette.selected.backgroundText
86 opacity: .8
88 opacity: .8
87 text: modelData["review"] || ""
89 text: modelData["review"] || ""
88 visible: text !== ""
90 visible: text !== ""
qml/Dash/Previews/PreviewRatingInput.qml+4−2
@@ -76,7 +76,8 @@ PreviewWidget {
76 verticalCenter: parent.verticalCenter
76 verticalCenter: parent.verticalCenter
77 left: parent.left
77 left: parent.left
78 }
78 }
79+ // TODO cimi: Yet another fix requiring Palette update.
79− color: Theme.palette.selected.backgroundText
80+ color: "grey" //Theme.palette.selected.backgroundText
80 opacity: .8
81 opacity: .8
81 text: widgetData["rating-label"] || i18n.tr("Rate this")
82 text: widgetData["rating-label"] || i18n.tr("Rate this")
82 }
83 }
@@ -121,7 +122,8 @@ PreviewWidget {
121 left: parent.left
122 left: parent.left
122 right: parent.right
123 right: parent.right
123 }
124 }
125+ // TODO cimi: Yet another fix requiring Palette update.
124− color: Theme.palette.selected.backgroundText
126+ color: "grey" //Theme.palette.selected.backgroundText
125 opacity: .8
127 opacity: .8
126 text: widgetData["review-label"] || i18n.tr("Add a review")
128 text: widgetData["review-label"] || i18n.tr("Add a review")
127 }
129 }
qml/Dash/Previews/PreviewVideoPlayback.qml+1
@@ -16,6 +16,7 @@
16
16
17import QtQuick 2.0
17import QtQuick 2.0
18import Ubuntu.Components 0.1
18import Ubuntu.Components 0.1
19+import Ubuntu.Thumbnailer 0.1
19import "../../Components"
20import "../../Components"
20
21
21/*! \brief Preview widget for video.
22/*! \brief Preview widget for video.
qml/Dash/ScopeDelegateMapper.qmldeleted−30
@@ -1,30 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−
19−QtObject {
20− property var scopeDelegateMapping: {"clickscope": "DashApps.qml"}
21− property string genericScope: "GenericScopeView.qml"
22−
23− function map(scopeId) {
24− var customScope = scopeDelegateMapping[scopeId]
25− if (customScope != undefined) {
26− return customScope
27− }
28− return genericScope
29− }
30−}
qml/Launcher/Launcher.qml+2−1
@@ -29,7 +29,8 @@ Item {
29 property int dragAreaWidth: units.gu(1)
29 property int dragAreaWidth: units.gu(1)
30 property int minimizeDistance: units.gu(26)
30 property int minimizeDistance: units.gu(26)
31 property real progress: dragArea.dragging && dragArea.touchX > panelWidth ?
31 property real progress: dragArea.dragging && dragArea.touchX > panelWidth ?
32− (width * (dragArea.touchX-panelWidth) / (width - panelWidth)) : 0
32+ (width * (dragArea.touchX-panelWidth) / (width - panelWidth)) :
33+ (dragArea.dragging ? 0.001 : 0)
33
34
34 readonly property bool shown: panel.x > -panel.width
35 readonly property bool shown: panel.x > -panel.width
35
36
qml/Notifications/NotificationMenuItemFactory.qml+2−1
@@ -66,7 +66,8 @@ Loader {
66 TextField {
66 TextField {
67 id: textfield
67 id: textfield
68
68
69+ // TODO using Qt.ImhNoPredictiveText here until lp #1291575 is fixed for ubuntu-ui-toolkit
69− inputMethodHints: Qt.ImhNoAutoUppercase
70+ inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText
70 anchors.left: parent.left; anchors.right: parent.right
71 anchors.left: parent.left; anchors.right: parent.right
71 echoMode: checkBox.checked ? TextInput.Normal : TextInput.Password
72 echoMode: checkBox.checked ? TextInput.Normal : TextInput.Password
72 height: units.gu(5)
73 height: units.gu(5)
qml/Panel/Panel.qml+1−1
@@ -62,7 +62,7 @@ Item {
62 height: __panelMinusSeparatorLineHeight
62 height: __panelMinusSeparatorLineHeight
63 y: 0
63 y: 0
64
64
65− Behavior on y { StandardAnimation {} }
65+ Behavior on y { StandardAnimation { duration: UbuntuAnimation.FastDuration } }
66 }
66 }
67
67
68 PanelSeparatorLine {
68 PanelSeparatorLine {
qml/Shell.qml+200−309
@@ -31,7 +31,6 @@ import "Panel"
31import "Hud"
31import "Hud"
32import "Components"
32import "Components"
33import "Bottombar"
33import "Bottombar"
34−import "SideStage"
35import "Notifications"
34import "Notifications"
36import Unity.Notifications 1.0 as NotificationBackend
35import Unity.Notifications 1.0 as NotificationBackend
37
36
@@ -49,20 +48,24 @@ FocusScope {
49 readonly property real panelHeight: panel.panelHeight
48 readonly property real panelHeight: panel.panelHeight
50
49
51 property bool dashShown: dash.shown
50 property bool dashShown: dash.shown
52− property bool stageScreenshotsReady: {
51+
53− if (sideStage.shown) {
52+ property bool sideStageEnabled: shell.width >= units.gu(100)
54− if (mainStage.applications.count > 0) {
53+ readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
55− return mainStage.usingScreenshots && sideStage.usingScreenshots;
54+
56− } else {
55+ function activateApplication(appId) {
57− return sideStage.usingScreenshots;
56+ if (ApplicationManager.findApplication(appId)) {
57+ ApplicationManager.requestFocusApplication(appId);
58+ stages.show(true);
59+ if (stages.locked && ApplicationManager.focusedApplicationId == appId) {
60+ applicationsDisplayLoader.item.select(appId);
58 }
61 }
59 } else {
62 } else {
60− return mainStage.usingScreenshots;
63+ var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
64+ ApplicationManager.startApplication(appId, execFlags);
65+ stages.show(false);
61 }
66 }
62 }
67 }
63
68
64− property var applicationManager: ApplicationManagerWrapper {}
65−
66 Binding {
69 Binding {
67 target: LauncherModel
70 target: LauncherModel
68 property: "applicationManager"
71 property: "applicationManager"
@@ -71,49 +74,6 @@ FocusScope {
71
74
72 Component.onCompleted: {
75 Component.onCompleted: {
73 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
76 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
74−
75− applicationManager.sideStageEnabled = Qt.binding(function() { return sideStage.enabled })
76−
77− // FIXME: if application focused before shell starts, shell draws on top of it only.
78− // We should detect already running applications on shell start and bring them to the front.
79− applicationManager.unfocusCurrentApplication();
80− }
81−
82− readonly property bool applicationFocused: !!applicationManager.mainStageFocusedApplication
83− || !!applicationManager.sideStageFocusedApplication
84− // Used for autopilot testing.
85− readonly property string currentFocusedAppId: ApplicationManager.focusedApplicationId
86−
87− readonly property bool fullscreenMode: {
88− if (greeter.shown || lockscreen.shown) {
89− return false;
90− } else if (mainStage.usingScreenshots) { // Window Manager animating so want to re-evaluate fullscreen mode
91− return mainStage.switchingFromFullscreenToFullscreen;
92− } else if (applicationManager.mainStageFocusedApplication) {
93− return applicationManager.mainStageFocusedApplication.fullscreen;
94− } else {
95− return false;
96− }
97− }
98−
99− function activateApplication(appId, argument) {
100− if (applicationManager) {
101− // For newly started applications, as it takes them time to draw their first frame
102− // we add a delay before we hide the animation screenshots to compensate.
103− var addDelay = !applicationManager.getApplicationFromDesktopFile(appId);
104−
105− var application;
106− application = applicationManager.activateApplication(appId, argument);
107− if (application == null) {
108− return;
109− }
110− if (application.stage == ApplicationInfo.MainStage || !sideStage.enabled) {
111− mainStage.activateApplication(appId, addDelay);
112− } else {
113− sideStage.activateApplication(appId, addDelay);
114− }
115− stages.show();
116− }
117 }
77 }
118
78
119 GSettings {
79 GSettings {
@@ -133,244 +93,215 @@ FocusScope {
133 Keys.onVolumeDownPressed: volumeControl.volumeDown()
93 Keys.onVolumeDownPressed: volumeControl.volumeDown()
134
94
135 Item {
95 Item {
136− id: underlay
96+ id: underlayClipper
137− objectName: "underlay"
138 anchors.fill: parent
97 anchors.fill: parent
98+ anchors.rightMargin: stages.overlayWidth
99+ clip: stages.overlayMode && !stages.painting
139
100
140− // Whether the underlay is fully covered by opaque UI elements.
101+ InputFilterArea {
141− property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
102+ anchors.fill: parent
103+ blockInput: parent.clip
104+ }
142
105
143− readonly property bool applicationRunning: ((mainStage.applications && mainStage.applications.count > 0)
106+ Item {
144− || (sideStage.applications && sideStage.applications.count > 0))
107+ id: underlay
108+ objectName: "underlay"
109+ anchors.fill: parent
110+ anchors.rightMargin: -parent.anchors.rightMargin
145
111
146− // Whether the user should see the topmost application surface (if there's one at all).
112+ // Whether the underlay is fully covered by opaque UI elements.
147− readonly property bool applicationSurfaceShouldBeSeen: applicationRunning && !stages.fullyHidden
113+ property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
148− && !mainStage.usingScreenshots // but want sideStage animating over app surface
149
114
115+ // Whether the user should see the topmost application surface (if there's one at all).
116+ readonly property bool applicationSurfaceShouldBeSeen: stages.shown && !stages.painting && !stages.overlayMode
150
117
118+ // NB! Application surfaces are stacked behind the shell one. So they can only be seen by the user
119+ // through the translucent parts of the shell surface.
120+ visible: !fullyCovered && !applicationSurfaceShouldBeSeen
151
121
152− // NB! Application surfaces are stacked behing the shell one. So they can only be seen by the user
122+ CrossFadeImage {
153− // through the translucent parts of the shell surface.
123+ id: backgroundImage
154− visible: !fullyCovered && !applicationSurfaceShouldBeSeen
124+ objectName: "backgroundImage"
155
125
156− Rectangle {
157− anchors.fill: parent
126+ anchors.fill: parent
158− color: "black"
127+ source: shell.background
159− opacity: dash.disappearingAnimationProgress
128+ fillMode: Image.PreserveAspectCrop
160− }
129+ }
161
130
162− Image {
131+ Rectangle {
163− anchors.fill: dash
132+ anchors.fill: parent
164− source: shell.width > shell.height ? "Dash/graphics/paper_landscape.png" : "Dash/graphics/paper_portrait.png"
133+ color: "black"
165− fillMode: Image.PreserveAspectCrop
134+ opacity: dash.disappearingAnimationProgress
166− horizontalAlignment: Image.AlignRight
167− verticalAlignment: Image.AlignTop
168− }
135+ }
169
136
170− Dash {
137+ Image {
171− id: dash
138+ anchors.fill: dash
172− objectName: "dash"
139+ source: shell.width > shell.height ? "Dash/graphics/paper_landscape.png" : "Dash/graphics/paper_portrait.png"
173−
140+ fillMode: Image.PreserveAspectCrop
174− available: !greeter.shown && !lockscreen.shown
141+ horizontalAlignment: Image.AlignRight
175− hides: [stages, launcher, panel.indicators]
142+ verticalAlignment: Image.AlignTop
176− shown: disappearingAnimationProgress !== 1.0
177− enabled: disappearingAnimationProgress === 0.0 && edgeDemo.dashEnabled
178− // FIXME: unfocus all applications when going back to the dash
179− onEnabledChanged: {
180− if (enabled) {
181− shell.applicationManager.unfocusCurrentApplication()
182− }
183 }
143 }
184
144
145+ Dash {
146+ id: dash
147+ objectName: "dash"
148+
149+ available: !greeter.shown && !lockscreen.shown
150+ hides: [stages, launcher, panel.indicators]
151+ shown: disappearingAnimationProgress !== 1.0
152+ enabled: disappearingAnimationProgress === 0.0 && edgeDemo.dashEnabled
153+
185− anchors {
154+ anchors {
186− fill: parent
155+ fill: parent
187− topMargin: panel.panelHeight
156+ topMargin: panel.panelHeight
157+ }
158+
159+ contentScale: 1.0 - 0.2 * disappearingAnimationProgress
160+ opacity: 1.0 - disappearingAnimationProgress
161+ property real disappearingAnimationProgress: {
162+ if (greeter.shown) {
163+ return greeter.showProgress;
164+ } else {
165+ if (stages.overlayMode) {
166+ return 0;
167+ }
168+ return stages.showProgress
169+ }
170+ }
171+
172+ // FIXME: only necessary because stages.showProgress and
173+ // greeterRevealer.animatedProgress are not animated
174+ Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
188 }
175 }
176+ }
177+ }
189
178
190− contentScale: 1.0 - 0.2 * disappearingAnimationProgress
179+ EdgeDragArea {
191− opacity: 1.0 - disappearingAnimationProgress
180+ id: stagesDragHandle
192− property real disappearingAnimationProgress: {
181+ direction: Direction.Leftwards
193− if (greeter.shown) {
182+
194− return greeter.showProgress;
183+ anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
184+ width: shell.edgeSize
185+
186+ property real progress: stages.width
187+
188+ onTouchXChanged: {
189+ if (status == DirectionalDragArea.Recognized) {
190+ if (ApplicationManager.count == 0) {
191+ progress = Math.max(stages.width - stagesDragHandle.width + touchX, stages.width * .3)
195 } else {
192 } else {
196− return stagesOuterContainer.showProgress;
193+ progress = stages.width - stagesDragHandle.width + touchX
197 }
194 }
198 }
195 }
196+ }
199
197
200− // FIXME: only necessary because stagesOuterContainer.showProgress and
198+ onDraggingChanged: {
201− // greeterRevealer.animatedProgress are not animated
199+ if (!dragging) {
202− Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
200+ if (ApplicationManager.count > 0 && progress < stages.width - units.gu(10)) {
201+ stages.show(true)
202+ }
203+ stagesDragHandle.progress = stages.width;
204+ }
203 }
205 }
204 }
206 }
205
207
206 Item {
208 Item {
207− id: stagesOuterContainer
209+ id: stages
208−
210+ objectName: "stages"
209 width: parent.width
211 width: parent.width
210 height: parent.height
212 height: parent.height
211− x: launcher.progress
212− Behavior on x {SmoothedAnimation{velocity: 600}}
213
213
214− property real showProgress:
214+ x: {
215− MathUtils.clamp(1 - (x + stages.x) / shell.width, 0, 1)
215+ if (shown) {
216+ if (overlayMode || locked) {
217+ return 0;
218+ }
219+ return launcher.progress
220+ } else {
221+ return stagesDragHandle.progress
222+ }
223+ }
216
224
217− Showable {
225+ Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.FastDuration } }
218− id: stages
219− objectName: "stages"
220
226
221− x: width
227+ property bool shown: false
222
228
223− property bool fullyShown: shown && x == 0 && parent.x == 0
229+ property real showProgress: overlayMode ? 0 : MathUtils.clamp(1 - x / shell.width, 0, 1)
224− property bool fullyHidden: !shown && x == width
225− available: !greeter.shown
226− hides: [panel.indicators]
227− shown: false
228− opacity: 1.0
229− showAnimation: StandardAnimation { property: "x"; duration: 350; to: 0; easing.type: Easing.OutCubic }
230− hideAnimation: StandardAnimation { property: "x"; duration: 350; to: width; easing.type: Easing.OutCubic }
231
230
232− width: parent.width
231+ property bool fullyShown: x == 0
233− height: parent.height
232+ property bool fullyHidden: x == width
234
233
235− // close the stages when no focused application remains
234+ property bool painting: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.painting : false
236− Connections {
235+ property bool fullscreen: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.fullscreen : false
237− target: shell.applicationManager
236+ property bool overlayMode: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayMode : false
238− onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
237+ property int overlayWidth: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayWidth : false
239− onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
238+ property bool locked: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.locked : false
240− ignoreUnknownSignals: true
241− }
242
239
243− function closeIfNoApplications() {
240+ function show(focusApp) {
244− if (!shell.applicationManager.mainStageFocusedApplication
241+ shown = true;
245− && !shell.applicationManager.sideStageFocusedApplication
242+ panel.indicators.hide();
246− && shell.applicationManager.mainStageApplications.count == 0
243+ edgeDemo.stopDemo();
247− && shell.applicationManager.sideStageApplications.count == 0) {
244+ greeter.hide();
248− stages.hide();
245+ if (!ApplicationManager.focusedApplicationId && ApplicationManager.count > 0 && focusApp) {
249− }
246+ ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
250 }
247 }
248+ }
251
249
252− // show the stages when an application gets the focus
250+ function hide() {
253− Connections {
251+ shown = false;
254− target: shell.applicationManager
252+ if (ApplicationManager.focusedApplicationId) {
255− onMainStageFocusedApplicationChanged: {
253+ ApplicationManager.unfocusCurrentApplication();
256− if (shell.applicationManager.mainStageFocusedApplication) {
257− mainStage.show();
258− stages.show();
259− }
260− }
261− onSideStageFocusedApplicationChanged: {
262− if (shell.applicationManager.sideStageFocusedApplication) {
263− sideStage.show();
264− stages.show();
265− }
266− }
267− ignoreUnknownSignals: true
268 }
254 }
255+ }
269
256
270− Stage {
257+ Connections {
271− id: mainStage
258+ target: ApplicationManager
272
259
273− anchors.fill: parent
260+ onFocusRequested: {
274− fullyShown: stages.fullyShown
261+ stages.show(true);
275− fullyHidden: stages.fullyHidden
276− shouldUseScreenshots: !fullyShown
277− rightEdgeEnabled: !sideStage.enabled
278−
279− applicationManager: shell.applicationManager
280− rightEdgeDraggingAreaWidth: shell.edgeSize
281− normalApplicationY: shell.panelHeight
282−
283− shown: true
284− function show() {
285− stages.show();
286− }
287− function hide() {
288− }
262+ }
289
263
290− // FIXME: workaround the fact that focusing a main stage application
264+ onFocusedApplicationIdChanged: {
291− // raises its surface on top of all other surfaces including the ones
265+ if (ApplicationManager.focusedApplicationId.length > 0) {
292− // that belong to side stage applications.
266+ stages.show(false);
293− onFocusedApplicationChanged: {
267+ } else {
294− if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
268+ if (!stages.overlayMode) {
295− shell.applicationManager.focusApplication(sideStage.focusedApplication);
269+ stages.hide();
296 }
270 }
297 }
271 }
298 }
272 }
299
273
300− SideStage {
274+ onApplicationAdded: {
301− id: sideStage
275+ stages.show(false);
302−
276+ }
303− applicationManager: shell.applicationManager
304− rightEdgeDraggingAreaWidth: shell.edgeSize
305− normalApplicationY: shell.panelHeight
306
277
307− onShownChanged: {
278+ onApplicationRemoved: {
308− if (!shown && mainStage.applications.count == 0) {
279+ if (ApplicationManager.focusedApplicationId.length == 0) {
309− stages.hide();
280+ stages.hide();
310− }
311− }
312− // FIXME: when hiding the side stage, refocus the main stage
313− // application so that it goes in front of the side stage
314− // application and hides it
315− onFullyShownChanged: {
316− if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
317− shell.applicationManager.focusApplication(mainStage.focusedApplication);
318− }
319 }
281 }
282+ }
283+ }
320
284
321− enabled: shell.width >= units.gu(100)
285+ Loader {
322− visible: enabled
286+ id: applicationsDisplayLoader
323− fullyShown: stages.fullyShown && shown
287+ anchors.fill: parent
324− && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
288+
325− shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
289+ source: shell.sideStageEnabled ? "Stages/StageWithSideStage.qml" : "Stages/PhoneStage.qml"
326−
290+
327− available: !greeter.shown && !lockscreen.shown && enabled
291+ Binding {
328− hides: [launcher, panel.indicators]
292+ target: applicationsDisplayLoader.item
329− shown: false
293+ property: "moving"
330− showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
294+ value: !stages.fullyShown
331− hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
332−
333− width: units.gu(40)
334− height: stages.height
335− handleExpanded: sideStageRevealer.pressed
336− }
337−
338− Revealer {
339− id: sideStageRevealer
340−
341− enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
342− && sideStage.available
343− direction: Qt.RightToLeft
344− openedValue: parent.width - sideStage.width
345− hintDisplacement: units.gu(3)
346− /* The size of the sidestage handle needs to be bigger than the
347− typical size used for edge detection otherwise it is really
348− hard to grab.
349− */
350− handleSize: sideStage.shown ? units.gu(4) : shell.edgeSize
351− closedValue: parent.width + sideStage.handleSizeCollapsed
352− target: sideStage
353− x: parent.width - width
354− width: sideStage.width + handleSize * 0.7
355− height: sideStage.height
356− orientation: Qt.Horizontal
357− }
295+ }
358−
296+ Binding {
359− DragHandle {
297+ target: applicationsDisplayLoader.item
360− id: stagesDragHandle
298+ property: "shown"
361−
299+ value: stages.shown
362− anchors.top: parent.top
300+ }
363− anchors.bottom: parent.bottom
301+ Binding {
364− anchors.right: parent.left
302+ target: applicationsDisplayLoader.item
365−
303+ property: "dragAreaWidth"
366− width: shell.edgeSize
304+ value: shell.edgeSize
367− direction: Direction.Leftwards
368− enabled: greeter.showProgress == 0 && edgeDemo.dashEnabled
369− property bool haveApps: mainStage.applications.count > 0 || sideStage.applications.count > 0
370−
371− maxTotalDragDistance: haveApps ? parent.width : parent.width * 0.7
372− // Make autocompletion impossible when !haveApps
373− edgeDragEvaluator.minDragDistance: haveApps ? maxTotalDragDistance * 0.1 : Number.MAX_VALUE
374 }
305 }
375 }
306 }
376 }
307 }
@@ -447,26 +378,6 @@ FocusScope {
447
378
448 dragHandleWidth: shell.edgeSize
379 dragHandleWidth: shell.edgeSize
449
380
450− property var previousMainApp: null
451− property var previousSideApp: null
452−
453− function removeApplicationFocus() {
454− greeter.previousMainApp = applicationManager.mainStageFocusedApplication;
455− greeter.previousSideApp = applicationManager.sideStageFocusedApplication;
456− applicationManager.unfocusCurrentApplication();
457− }
458−
459− function restoreApplicationFocus() {
460− if (greeter.previousMainApp) {
461− applicationManager.focusApplication(greeter.previousMainApp);
462− greeter.previousMainApp = null;
463− }
464− if (greeter.previousSideApp) {
465− applicationManager.focusApplication(greeter.previousSideApp);
466− greeter.previousSideApp = null;
467− }
468− }
469−
470 onShownChanged: {
381 onShownChanged: {
471 if (shown) {
382 if (shown) {
472 lockscreen.reset();
383 lockscreen.reset();
@@ -476,9 +387,6 @@ FocusScope {
476 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
387 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
477 }
388 }
478 greeter.forceActiveFocus();
389 greeter.forceActiveFocus();
479− removeApplicationFocus();
480− } else {
481− restoreApplicationFocus();
482 }
390 }
483 }
391 }
484
392
@@ -496,33 +404,16 @@ FocusScope {
496 }
404 }
497 }
405 }
498
406
499− Connections {
407+ Binding {
500− target: applicationManager
408+ target: ApplicationManager
501− ignoreUnknownSignals: true
409+ property: "suspended"
502− // If any app is focused when greeter is open, it's due to a user action
410+ value: greeter.shown && greeter.showProgress == 1
503− // like a snap decision (say, an incoming call).
504− // TODO: these should be protected to only unlock for certain applications / certain usecases
505− // potentially only in connection with a notification.
506− onMainStageFocusedApplicationChanged: {
507− if (greeter.shown && applicationManager.mainStageFocusedApplication) {
508− greeter.previousMainApp = null // make way for new focused app
509− greeter.previousSideApp = null
510− greeter.hide()
511− }
512− }
513− onSideStageFocusedApplicationChanged: {
514− if (greeter.shown && applicationManager.sideStageFocusedApplication) {
515− greeter.previousMainApp = null // make way for new focused app
516− greeter.previousSideApp = null
517− greeter.hide()
518− }
519− }
520 }
411 }
521 }
412 }
522
413
523 InputFilterArea {
414 InputFilterArea {
524 anchors.fill: parent
415 anchors.fill: parent
525− blockInput: !applicationFocused || greeter.shown || lockscreen.shown || launcher.shown
416+ blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
526 || panel.indicators.shown || hud.shown
417 || panel.indicators.shown || hud.shown
527 }
418 }
528
419
@@ -571,7 +462,9 @@ FocusScope {
571 available: edgeDemo.panelEnabled
462 available: edgeDemo.panelEnabled
572 contentEnabled: edgeDemo.panelContentEnabled
463 contentEnabled: edgeDemo.panelContentEnabled
573 }
464 }
574− fullscreenMode: shell.fullscreenMode
465+ property string focusedAppId: ApplicationManager.focusedApplicationId
466+ property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
467+ fullscreenMode: focusedApplication && stages.fullscreen && !greeter.shown && !lockscreen.shown
575 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown && dash.searchable
468 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown && dash.searchable
576
469
577 InputFilterArea {
470 InputFilterArea {
@@ -597,9 +490,8 @@ FocusScope {
597 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
490 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
598
491
599 Connections {
492 Connections {
600− target: shell.applicationManager
493+ target: ApplicationManager
601− onMainStageFocusedApplicationChanged: hud.hide()
494+ onFocusedApplicationIdChanged: hud.hide()
602− onSideStageFocusedApplicationChanged: hud.hide()
603 }
495 }
604 }
496 }
605
497
@@ -624,7 +516,7 @@ FocusScope {
624 theHud: hud
516 theHud: hud
625 anchors.fill: parent
517 anchors.fill: parent
626 enabled: hud.available
518 enabled: hud.available
627− applicationIsOnForeground: applicationFocused
519+ applicationIsOnForeground: ApplicationManager.focusedApplicationId
628 }
520 }
629
521
630 InputFilterArea {
522 InputFilterArea {
@@ -655,18 +547,17 @@ FocusScope {
655 showHome()
547 showHome()
656 }
548 }
657 onDash: {
549 onDash: {
658− if (stages.shown) {
550+ if (stages.shown && !stages.overlayMode) {
659− stages.hide();
551+ if (!stages.locked) {
552+ stages.hide();
660− launcher.hide();
553+ launcher.hide();
554+ }
661 }
555 }
662 }
556 }
663 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
557 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
664− onLauncherApplicationSelected:{
558+ onLauncherApplicationSelected: {
665− if (edgeDemo.running)
559+ if (!edgeDemo.running)
666− return;
667−
668− greeter.hide()
669− shell.activateApplication(appId)
560+ shell.activateApplication(appId)
670 }
561 }
671 onShownChanged: {
562 onShownChanged: {
672 if (shown) {
563 if (shown) {
@@ -750,14 +641,14 @@ FocusScope {
750 anchors.bottom: parent.bottom
641 anchors.bottom: parent.bottom
751 anchors.left: parent.left
642 anchors.left: parent.left
752 anchors.right: parent.right
643 anchors.right: parent.right
753− height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
644+ height: ApplicationManager.keyboardVisible ? ApplicationManager.keyboardHeight : 0
754
645
755− enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
646+ enabled: ApplicationManager.keyboardVisible
756 }
647 }
757
648
758 Label {
649 Label {
759 anchors.centerIn: parent
650 anchors.centerIn: parent
760− visible: applicationManager.fake
651+ visible: ApplicationManager.fake ? ApplicationManager.fake : false
761 text: "EARLY ALPHA\nNOT READY FOR USE"
652 text: "EARLY ALPHA\nNOT READY FOR USE"
762 color: "lightgrey"
653 color: "lightgrey"
763 opacity: 0.2
654 opacity: 0.2
qml/SideStage/SideStage.qmldeleted−73
@@ -1,73 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import Unity.Application 0.1
19−import Ubuntu.Components 0.1
20−import "../Components"
21−
22−Stage {
23− id: stage
24−
25− type: ApplicationInfo.SideStage
26− property real handleSizeCollapsed: units.gu(2.5)
27− property real handleSizeExpanded: units.gu(4)
28− property bool handleExpanded
29−
30− /* FIXME: workaround so that when a main stage app goes fullscreen
31− the sidestage's top is not transparent. Proper fix would be to
32− resize the side stage app.
33− */
34− Rectangle {
35− id: backgroundBeneathPanel
36− anchors {
37− top: parent.top
38− left: parent.left
39− right: parent.right
40− }
41− height: shell.panelHeight
42− color: background.color
43− z: -1
44− }
45−
46− Rectangle {
47− id: background
48− anchors.fill: parent
49− color: "#2c2924"
50− z: -1
51− visible: stage.usingScreenshots
52− }
53−
54− SidestageHandle {
55− id: handle
56− objectName: "sideStageHandle"
57−
58− anchors {
59− top: parent.top
60− bottom: parent.bottom
61− left: parent.left
62− leftMargin: -width
63− }
64− width: handleExpanded ? handleSizeExpanded : handleSizeCollapsed
65− Behavior on width { NumberAnimation { easing.type: Easing.OutQuart} }
66− z: -1
67− }
68−
69− InputFilterArea {
70− anchors.fill: handle
71− blockInput: visible
72− }
73−}
qml/Stages/PhoneStage.qmlnew file+499
@@ -0,0 +1,499 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import Ubuntu.Components 0.1
19+import Ubuntu.Gestures 0.1
20+import Unity.Application 0.1
21+import Utils 0.1
22+import "../Components"
23+
24+Item {
25+ id: root
26+
27+ // Controls to be set from outside
28+ property bool shown: false
29+ property bool moving: false
30+ property int dragAreaWidth
31+
32+ // State information propagated to the outside
33+ readonly property bool painting: mainScreenshotImage.visible || fadeInScreenshotImage.visible || appSplash.visible || spreadView.visible
34+ property bool fullscreen: priv.focusedApplication ? priv.focusedApplication.fullscreen : false
35+ property bool locked: spreadView.visible
36+
37+ // Not used for PhoneStage, only useful for SideStage and similar
38+ property bool overlayMode: false
39+ property int overlayWidth: 0
40+
41+ function select(appId) {
42+ spreadView.snapTo(priv.indexOf(appId))
43+ }
44+
45+ onMovingChanged: {
46+ if (moving) {
47+ if (ApplicationManager.focusedApplicationId) {
48+ priv.requestNewScreenshot();
49+ } else {
50+ mainScreenshotImage.anchors.leftMargin = 0;
51+ mainScreenshotImage.source = ApplicationManager.get(0).screenshot;
52+ mainScreenshotImage.visible = true;
53+ }
54+ } else {
55+ mainScreenshotImage.visible = false;
56+ }
57+ }
58+
59+ Connections {
60+ target: ApplicationManager
61+
62+ onFocusRequested: {
63+ if (spreadView.visible) {
64+ spreadView.snapTo(priv.indexOf(appId));
65+ } else {
66+ priv.switchToApp(appId);
67+ }
68+ }
69+
70+ onFocusedApplicationIdChanged: {
71+ if (ApplicationManager.focusedApplicationId.length > 0) {
72+ if (priv.secondApplicationStarting || priv.applicationStarting) {
73+ appSplashTimer.restart();
74+ } else {
75+ var application = priv.focusedApplication;
76+ root.fullscreen = application.fullscreen;
77+ mainScreenshotImage.source = application.screenshot;
78+ }
79+ } else {
80+ spreadView.selectedIndex = -1;
81+ spreadView.phase = 0;
82+ spreadView.contentX = -spreadView.shift;
83+ }
84+ }
85+
86+ onApplicationAdded: {
87+ if (!priv.focusedApplication) {
88+ mainScreenshotImage.source = "";
89+ mainScreenshotImage.visible = false;
90+ priv.applicationStarting = true;
91+ } else {
92+ mainScreenshotImage.source = "";
93+ priv.newFocusedAppId = appId;
94+ priv.secondApplicationStarting = true;
95+ priv.requestNewScreenshot();
96+ }
97+
98+ if (spreadView.visible) {
99+ spreadView.snapTo(0);
100+ }
101+ }
102+
103+ onApplicationRemoved: {
104+ if (ApplicationManager.count == 0) {
105+ mainScreenshotImage.source = ""
106+ mainScreenshotImage.visible = false;
107+ } else {
108+ mainScreenshotImage.source = ApplicationManager.get(0).screenshot;
109+ }
110+ }
111+ }
112+
113+ QtObject {
114+ id: priv
115+
116+ property string focusedAppId: ApplicationManager.focusedApplicationId
117+ property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
118+ property url focusedScreenshot: focusedApplication ? focusedApplication.screenshot : ""
119+
120+ property bool waitingForScreenshot: false
121+
122+ property bool applicationStarting: false
123+ property bool secondApplicationStarting: false
124+
125+ property string newFocusedAppId
126+
127+ onFocusedScreenshotChanged: {
128+ if (root.moving && priv.waitingForScreenshot) {
129+ mainScreenshotImage.anchors.leftMargin = 0;
130+ mainScreenshotImage.source = priv.focusedScreenshot
131+ mainScreenshotImage.visible = true;
132+ } else if (priv.secondApplicationStarting && priv.waitingForScreenshot) {
133+ applicationSwitchingAnimation.start();
134+ }
135+ waitingForScreenshot = false;
136+ }
137+
138+ function requestNewScreenshot() {
139+ waitingForScreenshot = true;
140+ ApplicationManager.updateScreenshot(focusedAppId);
141+ }
142+
143+ function switchToApp(appId) {
144+ if (priv.focusedAppId) {
145+ priv.newFocusedAppId = appId;
146+ root.fullscreen = ApplicationManager.findApplication(appId).fullscreen;
147+ applicationSwitchingAnimation.start();
148+ } else {
149+ ApplicationManager.focusApplication(appId);
150+ }
151+ }
152+
153+ function indexOf(appId) {
154+ for (var i = 0; i < ApplicationManager.count; i++) {
155+ if (ApplicationManager.get(i).appId == appId) {
156+ return i;
157+ }
158+ }
159+ return -1;
160+ }
161+
162+ }
163+
164+ // FIXME: the signal connections seems to get lost.
165+ Connections {
166+ target: priv.focusedApplication
167+ onScreenshotChanged: priv.focusedScreenshot = priv.focusedApplication.screenshot
168+ }
169+ Binding {
170+ target: root
171+ property: "fullscreen"
172+ value: priv.focusedApplication ? priv.focusedApplication.fullscreen : false
173+ }
174+
175+ Timer {
176+ id: appSplashTimer
177+ // FIXME: We really need to show something meaningful in the app surface instead of guessing
178+ // when it might be ready
179+ interval: 500
180+ repeat: false
181+ onTriggered: {
182+ priv.applicationStarting = false;
183+ priv.secondApplicationStarting = false;
184+ }
185+ }
186+
187+ SequentialAnimation {
188+ id: applicationSwitchingAnimation
189+ // setup
190+ PropertyAction { target: mainScreenshotImage; property: "anchors.leftMargin"; value: 0 }
191+ PropertyAction { target: mainScreenshotImage; property: "source"; value: priv.focusedScreenshot }
192+ PropertyAction { targets: [mainScreenshotImage, fadeInScreenshotImage]; property: "visible"; value: true }
193+ PropertyAction { target: fadeInScreenshotImage; property: "source"; value: {
194+ var newFocusedApp = ApplicationManager.findApplication(priv.newFocusedAppId);
195+ return newFocusedApp ? newFocusedApp.screenshot : "" }
196+ }
197+ PropertyAction { target: fadeInScreenshotImage; property: "opacity"; value: 0 }
198+ PropertyAction { target: fadeInScreenshotImage; property: "scale"; value: .8 }
199+
200+
201+ // The actual animation
202+ ParallelAnimation {
203+ UbuntuNumberAnimation { target: mainScreenshotImage; property: "anchors.leftMargin"; to: root.width; duration: UbuntuAnimation.SlowDuration }
204+ UbuntuNumberAnimation { target: fadeInScreenshotImage; properties: "opacity,scale"; to: 1; duration: UbuntuAnimation.SlowDuration }
205+ }
206+
207+ // restore stuff
208+ ScriptAction { script: ApplicationManager.focusApplication(priv.newFocusedAppId); }
209+ PropertyAction { target: fadeInScreenshotImage; property: "visible"; value: false }
210+ PropertyAction { target: mainScreenshotImage; property: "visible"; value: false }
211+ }
212+
213+ // FIXME: Drop this and make the imageprovider show a splashscreen instead
214+ Rectangle {
215+ id: appSplash2
216+ anchors.fill: parent
217+ color: "white"
218+ visible: priv.secondApplicationStarting
219+ }
220+ Image {
221+ id: fadeInScreenshotImage
222+ anchors { left: parent.left; bottom: parent.bottom }
223+ width: parent.width
224+ scale: .7
225+ visible: false
226+ }
227+
228+ Rectangle {
229+ id: appSplash
230+ anchors.fill: parent
231+ color: "white"
232+ visible: priv.applicationStarting
233+ }
234+ Image {
235+ id: mainScreenshotImage
236+ anchors { left: parent.left; bottom: parent.bottom }
237+ width: parent.width
238+ visible: false
239+ }
240+
241+ EdgeDragArea {
242+ id: spreadDragArea
243+ direction: Direction.Leftwards
244+ enabled: ApplicationManager.count > 1 && spreadView.phase != 2
245+
246+ anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
247+ width: root.dragAreaWidth
248+
249+ // Sitting at the right edge of the screen, this EdgeDragArea directly controls the spreadView when
250+ // attachedToView is true. When the finger movement passes positionMarker3 we detach it from the
251+ // spreadView and make the spreadView snap to positionMarker4.
252+ property bool attachedToView: true
253+
254+ property var gesturePoints: new Array()
255+
256+ onTouchXChanged: {
257+ if (!dragging && !priv.waitingForScreenshot) {
258+ // Initial touch. Let's update the screenshot and reset the spreadView to the starting position.
259+ priv.requestNewScreenshot();
260+ spreadView.phase = 0;
261+ spreadView.contentX = -spreadView.shift;
262+ }
263+ if (dragging && attachedToView) {
264+ // Gesture recognized. Let's move the spreadView with the finger
265+ spreadView.contentX = -touchX - spreadView.shift;
266+ }
267+ if (attachedToView && spreadView.shiftedContentX >= spreadView.width * spreadView.positionMarker3) {
268+ // We passed positionMarker3. Detach from spreadView and snap it.
269+ attachedToView = false;
270+ spreadView.snap();
271+ }
272+ gesturePoints.push(touchX);
273+ }
274+
275+ onStatusChanged: {
276+ if (status == DirectionalDragArea.Recognized) {
277+ attachedToView = true;
278+ }
279+ }
280+
281+ onDraggingChanged: {
282+ if (dragging) {
283+ // Gesture recognized. Start recording this gesture
284+ gesturePoints = [];
285+ return;
286+ }
287+
288+ // Ok. The user released. Find out if it was a one-way movement.
289+ var oneWayFlick = true;
290+ var smallestX = spreadDragArea.width;
291+ for (var i = 0; i < gesturePoints.length; i++) {
292+ if (gesturePoints[i] >= smallestX) {
293+ oneWayFlick = false;
294+ break;
295+ }
296+ smallestX = gesturePoints[i];
297+ }
298+ gesturePoints = [];
299+
300+ if (oneWayFlick && spreadView.shiftedContentX > units.gu(2) &&
301+ spreadView.shiftedContentX < spreadView.positionMarker1 * spreadView.width) {
302+ // If it was a short one-way movement, do the Alt+Tab switch
303+ // no matter if we didn't cross positionMarker1 yet.
304+ spreadView.snapTo(1);
305+ } else if (!dragging && attachedToView) {
306+ // otherwise snap to the closest snap position we can find
307+ // (might be back to start, to app 1 or to spread)
308+ spreadView.snap();
309+ }
310+ }
311+ }
312+
313+ Rectangle {
314+ id: coverFlipBackground
315+ anchors.fill: parent
316+ color: "black"
317+ visible: spreadView.visible
318+ }
319+
320+ InputFilterArea {
321+ anchors.fill: root
322+ blockInput: spreadView.visible
323+ }
324+
325+ Flickable {
326+ id: spreadView
327+ objectName: "spreadView"
328+ anchors.fill: parent
329+ visible: spreadDragArea.status == DirectionalDragArea.Recognized || phase > 1 || snapAnimation.running
330+ contentWidth: spreadRow.width - shift
331+ contentX: -shift
332+
333+ // The flickable needs to fill the screen in order to get touch events all over.
334+ // However, we don't want to the user to be able to scroll back all the way. For
335+ // that, the beginning of the gesture starts with a negative value for contentX
336+ // so the flickable wants to pull it into the view already. "shift" tunes the
337+ // distance where to "lock" the content.
338+ property real shift: width / 2
339+ property real shiftedContentX: contentX + shift
340+
341+ property int tileDistance: width / 4
342+
343+ // Those markers mark the various positions in the spread (ratio to screen width from right to left):
344+ // 0 - 1: following finger, snap back to the beginning on release
345+ property real positionMarker1: 0.3
346+ // 1 - 2: curved snapping movement, snap to app 1 on release
347+ property real positionMarker2: 0.45
348+ // 2 - 3: movement follows finger, snaps back to app 1 on release
349+ property real positionMarker3: 0.6
350+ // passing 3, we detach movement from the finger and snap to 4
351+ property real positionMarker4: 0.9
352+
353+ // This is where the first app snaps to when bringing it in from the right edge.
354+ property real snapPosition: 0.75
355+
356+ // Phase of the animation:
357+ // 0: Starting from right edge, a new app (index 1) comes in from the right
358+ // 1: The app has reached the first snap position.
359+ // 2: The list is dragged further and snaps into the spread view when entering phase 2
360+ property int phase: 0
361+
362+ property int selectedIndex: -1
363+
364+ onShiftedContentXChanged: {
365+ switch (phase) {
366+ case 0:
367+ if (shiftedContentX > width * positionMarker2) {
368+ phase = 1;
369+ }
370+ break;
371+ case 1:
372+ if (shiftedContentX < width * positionMarker2) {
373+ phase = 0;
374+ } else if (shiftedContentX >= width * positionMarker4) {
375+ phase = 2;
376+ }
377+ break;
378+ }
379+ }
380+
381+ function snap() {
382+ if (shiftedContentX < positionMarker1 * width) {
383+ snapAnimation.targetContentX = -shift;
384+ snapAnimation.start();
385+ } else if (shiftedContentX < positionMarker2 * width) {
386+ snapTo(1)
387+ } else if (shiftedContentX < positionMarker3 * width) {
388+ snapTo(1)
389+ } else if (phase < 2){
390+ // Add 1 pixel to make sure we definitely hit positionMarker4 even with rounding errors of the animation.
391+ snapAnimation.targetContentX = width * positionMarker4 + 1 - shift;
392+ snapAnimation.start();
393+ }
394+ }
395+ function snapTo(index) {
396+ spreadView.selectedIndex = index;
397+ root.fullscreen = ApplicationManager.get(index).fullscreen;
398+ snapAnimation.targetContentX = -shift;
399+ snapAnimation.start();
400+ }
401+
402+ SequentialAnimation {
403+ id: snapAnimation
404+ property int targetContentX: -spreadView.shift
405+
406+ UbuntuNumberAnimation {
407+ target: spreadView
408+ property: "contentX"
409+ to: snapAnimation.targetContentX
410+ duration: UbuntuAnimation.FastDuration
411+ }
412+
413+ ScriptAction {
414+ script: {
415+ if (spreadView.selectedIndex >= 0) {
416+ ApplicationManager.focusApplication(ApplicationManager.get(spreadView.selectedIndex).appId);
417+ spreadView.selectedIndex = -1
418+ spreadView.phase = 0;
419+ spreadView.contentX = -spreadView.shift;
420+ }
421+ }
422+ }
423+ }
424+
425+ Item {
426+ id: spreadRow
427+ // This width controls how much the spread can be flicked left/right. It's composed of:
428+ // tileDistance * app count (with a minimum of 3 apps, in order to also allow moving 1 and 2 apps a bit)
429+ // + some constant value (still scales with the screen width) which looks good and somewhat fills the screen
430+ width: Math.max(3, ApplicationManager.count) * spreadView.tileDistance + (spreadView.width - spreadView.tileDistance) * 1.5
431+
432+ x: spreadView.contentX
433+
434+ Repeater {
435+ id: spreadRepeater
436+ model: ApplicationManager
437+ delegate: TransformedSpreadDelegate {
438+ id: appDelegate
439+ objectName: "appDelegate" + index
440+ startAngle: 45
441+ endAngle: 5
442+ startScale: 1.1
443+ endScale: 0.7
444+ startDistance: spreadView.tileDistance
445+ endDistance: units.gu(.5)
446+ width: spreadView.width
447+ height: spreadView.height
448+ selected: spreadView.selectedIndex == index
449+ otherSelected: spreadView.selectedIndex >= 0 && !selected
450+
451+ z: index
452+ x: index == 0 ? 0 : spreadView.width + (index - 1) * spreadView.tileDistance
453+
454+ // Each tile has a different progress value running from 0 to 1.
455+ // A progress value of 0 means the tile is at the right edge. 1 means the tile has reched the left edge.
456+ progress: {
457+ var tileProgress = (spreadView.shiftedContentX - index * spreadView.tileDistance) / spreadView.width;
458+ // Tile 1 needs to move directly from the beginning...
459+ if (index == 1 && spreadView.phase < 2) {
460+ tileProgress += spreadView.tileDistance / spreadView.width;
461+ }
462+ return tileProgress;
463+ }
464+
465+ // This mostly is the same as progress, just adds the snapping to phase 1 for tiles 0 and 1
466+ animatedProgress: {
467+ if (spreadView.phase == 0 && index < 2) {
468+ if (progress < spreadView.positionMarker1) {
469+ return progress;
470+ } else if (progress < spreadView.positionMarker1 + snappingCurve.period){
471+ return spreadView.positionMarker1 + snappingCurve.value * 3;
472+ } else {
473+ return spreadView.positionMarker2;
474+ }
475+ }
476+ return progress;
477+ }
478+
479+ EasingCurve {
480+ id: snappingCurve
481+ type: EasingCurve.OutQuad
482+ period: 0.05
483+ progress: appDelegate.progress - spreadView.positionMarker1
484+ }
485+
486+ onClicked: {
487+ if (spreadView.phase == 2) {
488+ if (ApplicationManager.focusedApplicationId == ApplicationManager.get(index).appId) {
489+ spreadView.snapTo(index);
490+ } else {
491+ ApplicationManager.requestFocusApplication(ApplicationManager.get(index).appId);
492+ }
493+ }
494+ }
495+ }
496+ }
497+ }
498+ }
499+}
qml/SideStage/SidestageHandle.qml → qml/Stages/SidestageHandle.qmlrenamed

The content is the same.

qml/Stages/SpreadDelegate.qmlnew file+55
@@ -0,0 +1,55 @@
1+/*
2+ * Copyright 2014 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Authors: Michael Zanetti <michael.zanetti@canonical.com>
17+*/
18+
19+import QtQuick 2.0
20+
21+Item {
22+ id: root
23+
24+ signal clicked()
25+
26+ property real topMarginProgress
27+
28+ QtObject {
29+ id: priv
30+ property real heightDifference: root.height - appImage.implicitHeight
31+ }
32+
33+ Image {
34+ id: dropShadow
35+ anchors.fill: appImage
36+ anchors.margins: -units.gu(2)
37+ source: "graphics/dropshadow.png"
38+ opacity: .4
39+ }
40+ Image {
41+ id: appImage
42+ anchors {
43+ left: parent.left;
44+ bottom: parent.bottom;
45+ top: parent.top;
46+ topMargin: priv.heightDifference * Math.max(0, 1 - root.topMarginProgress)
47+ }
48+ source: model.screenshot
49+ antialiasing: true
50+ }
51+ MouseArea {
52+ anchors.fill: appImage
53+ onClicked: root.clicked()
54+ }
55+}
qml/Stages/StageWithSideStage.qmlnew file+407
@@ -0,0 +1,407 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import Ubuntu.Components 0.1
19+import "../Components"
20+import Unity.Application 0.1
21+import Ubuntu.Gestures 0.1
22+
23+Item {
24+ id: root
25+ objectName: "stages"
26+ anchors.fill: parent
27+
28+ // Controls to be set from outside
29+ property bool shown: false
30+ property bool moving: false
31+ property int dragAreaWidth
32+
33+ // State information propagated to the outside
34+ readonly property bool painting: mainStageImage.visible || sideStageImage.visible || sideStageSnapAnimation.running
35+ property bool fullscreen: priv.focusedApplication ? priv.focusedApplication.fullscreen : false
36+ property bool overlayMode: (sideStageImage.shown && priv.mainStageAppId.length == 0) || priv.overlayOverride
37+ || (priv.mainStageAppId.length == 0 && sideStageSnapAnimation.running)
38+
39+ readonly property int overlayWidth: priv.overlayOverride ? 0 : priv.sideStageWidth
40+
41+ onShownChanged: {
42+ if (!shown) {
43+ priv.mainStageAppId = "";
44+ }
45+ }
46+
47+ onMovingChanged: {
48+ if (moving) {
49+ if (!priv.mainStageAppId && !priv.sideStageAppId) {
50+ // Pulling in from the right, make the last used (topmost) app visible
51+ var application = ApplicationManager.get(0);
52+ if (application.stage == ApplicationInfoInterface.SideStage) {
53+ sideStageImage.application = application;
54+ sideStageImage.x = root.width - sideStageImage.width
55+ sideStageImage.visible = true;
56+ } else {
57+ mainStageImage.application = application;
58+ mainStageImage.visible = true;
59+ }
60+ } else {
61+ priv.requestNewScreenshot(ApplicationInfoInterface.MainStage)
62+ if (priv.focusedApplicationId == priv.sideStageAppId) {
63+ priv.requestNewScreenshot(ApplicationInfoInterface.SideStage)
64+ }
65+ }
66+ } else {
67+ mainStageImage.visible = false;
68+ sideStageImage.visible = false;
69+ }
70+ }
71+
72+ QtObject {
73+ id: priv
74+
75+ property int sideStageWidth: units.gu(40)
76+
77+
78+ property string sideStageAppId
79+ property string mainStageAppId
80+
81+
82+ property var sideStageApp: ApplicationManager.findApplication(sideStageAppId)
83+ property var mainStageApp: ApplicationManager.findApplication(mainStageAppId)
84+
85+ property string sideStageScreenshot: sideStageApp ? sideStageApp.screenshot : ""
86+ property string mainStageScreenshot: mainStageApp ? mainStageApp.screenshot : ""
87+
88+ property string focusedApplicationId: ApplicationManager.focusedApplicationId
89+ property var focusedApplication: ApplicationManager.findApplication(focusedApplicationId)
90+ property url focusedScreenshot: focusedApplication ? focusedApplication.screenshot : ""
91+
92+ property bool waitingForMainScreenshot: false
93+ property bool waitingForSideScreenshot: false
94+ property bool waitingForScreenshots: waitingForMainScreenshot || waitingForSideScreenshot
95+
96+ property string startingAppId: ""
97+
98+ // Keep overlayMode even if there is no focused app (to allow pulling in the sidestage from the right)
99+ property bool overlayOverride: false
100+
101+ onFocusedApplicationChanged: {
102+ if (focusedApplication) {
103+ if (focusedApplication.stage == ApplicationInfoInterface.MainStage) {
104+ mainStageAppId = focusedApplicationId;
105+ priv.overlayOverride = false;
106+ if (priv.startingAppId == focusedApplicationId && sideStageImage.shown) {
107+ // There was already a sidestage app on top. bring it back!
108+ ApplicationManager.focusApplication(priv.sideStageAppId)
109+ priv.startingAppId = "";
110+ }
111+ } else if (focusedApplication.stage == ApplicationInfoInterface.SideStage) {
112+ sideStageAppId = focusedApplicationId;
113+ if (priv.startingAppId == focusedApplicationId && !sideStageImage.shown) {
114+ sideStageImage.snapToApp(focusedApplication);
115+ priv.startingAppId = "";
116+ }
117+ }
118+ } else if (root.overlayMode){
119+ sideStageImage.snapTo(root.width)
120+ }
121+ }
122+
123+ onMainStageScreenshotChanged: {
124+ waitingForMainScreenshot = false;
125+ }
126+
127+ onSideStageScreenshotChanged: {
128+ waitingForSideScreenshot = false;
129+ }
130+
131+ onFocusedScreenshotChanged: {
132+ waitingForSideScreenshot = false;
133+ }
134+
135+ onWaitingForScreenshotsChanged: {
136+ if (waitingForScreenshots) {
137+ return;
138+ }
139+
140+ if (root.moving) {
141+ if (mainStageAppId) {
142+ mainStageImage.application = mainStageApp
143+ mainStageImage.visible = true;
144+ }
145+ if (sideStageAppId && focusedApplicationId == sideStageAppId) {
146+ sideStageImage.application = sideStageApp;
147+ sideStageImage.x = root.width - sideStageImage.width
148+ sideStageImage.visible = true;
149+ }
150+ }
151+ if (sideStageHandleMouseArea.pressed) {
152+ if (sideStageAppId) {
153+ sideStageImage.application = sideStageApp;
154+ sideStageImage.x = root.width - sideStageImage.width
155+ sideStageImage.visible = true;
156+ }
157+ if (mainStageAppId) {
158+ mainStageImage.application = mainStageApp
159+ mainStageImage.visible = true;
160+ }
161+ }
162+ }
163+
164+ function requestNewScreenshot(stage) {
165+ if (stage == ApplicationInfoInterface.MainStage && mainStageAppId) {
166+ waitingForMainScreenshot = true;
167+ ApplicationManager.updateScreenshot(mainStageAppId);
168+ } else if (stage == ApplicationInfoInterface.SideStage && sideStageAppId) {
169+ waitingForSideScreenshot = true;
170+ ApplicationManager.updateScreenshot(sideStageAppId);
171+ }
172+ }
173+
174+ }
175+ // FIXME: the signal connection seems to get lost with the fake application manager.
176+ Connections {
177+ target: priv.sideStageApp
178+ onScreenshotChanged: priv.sideStageScreenshot = priv.sideStageApp.screenshot
179+ }
180+ Connections {
181+ target: priv.mainStageApp
182+ onScreenshotChanged: priv.mainStageScreenshot = priv.mainStageApp.screenshot
183+ }
184+
185+ Connections {
186+ target: ApplicationManager
187+
188+ onApplicationAdded: {
189+ priv.startingAppId = appId;
190+ splashScreenTimer.start();
191+ var application = ApplicationManager.findApplication(appId)
192+ if (application.stage == ApplicationInfoInterface.SideStage) {
193+ sideStageSplash.visible = true;
194+ } else if (application.stage == ApplicationInfoInterface.MainStage) {
195+ mainStageSplash.visible = true;
196+ }
197+ }
198+
199+ onFocusRequested: {
200+ var application = ApplicationManager.findApplication(appId)
201+ if (application.stage == ApplicationInfoInterface.SideStage) {
202+ if (!root.shown) {
203+ priv.mainStageAppId = "";
204+ mainStageImage.application = null
205+ }
206+ if (sideStageImage.shown) {
207+ sideStageImage.switchTo(application);
208+ if (priv.mainStageAppId) {
209+ mainStageImage.application = priv.mainStageApp;
210+ mainStageImage.visible = true;
211+ }
212+ } else {
213+ sideStageImage.application = application;
214+ sideStageImage.snapToApp(application);
215+ }
216+ } else if (application.stage == ApplicationInfoInterface.MainStage) {
217+ if (root.shown) {
218+ if (sideStageImage.shown) {
219+ sideStageImage.application = priv.sideStageApp;
220+ sideStageImage.visible = true;
221+ }
222+ priv.mainStageAppId = application.appId;
223+ mainStageImage.switchTo(application)
224+ ApplicationManager.focusApplication(appId)
225+ if (sideStageImage.shown) {
226+ // There was already a focused SS app. Bring it back
227+ ApplicationManager.focusApplication(priv.sideStageAppId)
228+ }
229+ } else {
230+ if (sideStageImage.shown) {
231+ sideStageImage.visible = false;
232+ sideStageImage.x = root.width;
233+ }
234+
235+ mainStageImage.application = application;
236+ ApplicationManager.focusApplication(appId)
237+ }
238+ }
239+ }
240+
241+ onApplicationRemoved: {
242+ if (priv.mainStageAppId == appId) {
243+ priv.mainStageAppId = "";
244+ }
245+ if (priv.sideStageAppId == appId) {
246+ priv.sideStageAppId = "";
247+ }
248+ if (priv.sideStageAppId.length == 0) {
249+ sideStageImage.shown = false;
250+ priv.overlayOverride = false;
251+ }
252+ }
253+
254+ }
255+
256+ Timer {
257+ id: splashScreenTimer
258+ // FIXME: apart from removing this completely in the future and make the app surface paint
259+ // meaningful stuff, also check for colin's stuff to land so we can shape 1.4 secs away from here
260+ // https://code.launchpad.net/~cjwatson/upstart-app-launch/libclick-manifest/+merge/210520
261+ // https://code.launchpad.net/~cjwatson/upstart-app-launch/libclick-pkgdir/+merge/209909
262+ interval: 1700
263+ repeat: false
264+ onTriggered: {
265+ mainStageSplash.visible = false;
266+ sideStageSplash.visible = false;
267+ }
268+ }
269+
270+ SwitchingApplicationImage {
271+ id: mainStageImage
272+ anchors.bottom: parent.bottom
273+ width: parent.width
274+ visible: false
275+
276+ onSwitched: {
277+ sideStageImage.visible = false;
278+ }
279+ }
280+
281+ Rectangle {
282+ id: mainStageSplash
283+ anchors.fill: root
284+ anchors.rightMargin: root.width - sideStageImage.x
285+ color: "white"
286+ }
287+
288+ SidestageHandle {
289+ id: sideStageHandle
290+ anchors { top: parent.top; right: sideStageImage.left; bottom: parent.bottom }
291+ width: root.dragAreaWidth
292+ visible: root.shown && priv.sideStageAppId && sideStageImage.x < root.width
293+
294+ }
295+ MouseArea {
296+ id: sideStageHandleMouseArea
297+ anchors { top: parent.top; right: parent.right; bottom: parent.bottom; rightMargin: sideStageImage.shown ? sideStageImage.width : 0}
298+ width: root.dragAreaWidth
299+ visible: priv.sideStageAppId
300+
301+ property var dragPoints: new Array()
302+
303+ onPressed: {
304+ priv.requestNewScreenshot(ApplicationInfoInterface.SideStage)
305+ if (priv.mainStageAppId) {
306+ priv.requestNewScreenshot(ApplicationInfoInterface.MainStage)
307+ }
308+ }
309+
310+ onMouseXChanged: {
311+ dragPoints.push(mouseX)
312+
313+ var dragPoint = root.width + mouseX;
314+ if (sideStageImage.shown) {
315+ dragPoint -= sideStageImage.width
316+ }
317+ sideStageImage.x = Math.max(root.width - sideStageImage.width, dragPoint)
318+ }
319+
320+ onReleased: {
321+ var distance = 0;
322+ var lastX = dragPoints[0];
323+ var oneWayFlick = true;
324+ for (var i = 0; i < dragPoints.length; ++i) {
325+ if (dragPoints[i] < lastX) {
326+ oneWayFlick = false;
327+ }
328+ distance += dragPoints[i] - lastX;
329+ lastX = dragPoints[i];
330+ }
331+ dragPoints = [];
332+
333+ if (oneWayFlick || distance > sideStageImage.width / 2) {
334+ sideStageImage.snapTo(root.width)
335+ } else {
336+ sideStageImage.snapToApp(priv.sideStageApp)
337+ }
338+ }
339+ }
340+
341+ SwitchingApplicationImage {
342+ id: sideStageImage
343+ width: priv.sideStageWidth
344+ height: root.height
345+ x: root.width
346+ anchors.bottom: parent.bottom
347+ visible: true
348+ property bool shown: false
349+
350+ onSwitched: {
351+ mainStageImage.visible = false;
352+ ApplicationManager.focusApplication(application.appId)
353+ }
354+
355+ function snapTo(targetX) {
356+ sideStageSnapAnimation.targetX = targetX
357+ sideStageImage.visible = true;
358+ if (priv.mainStageAppId) {
359+ mainStageImage.application = priv.mainStageApp
360+ mainStageImage.visible = true;
361+ }
362+ sideStageSnapAnimation.start();
363+ }
364+
365+ function snapToApp(application) {
366+ sideStageImage.application = application
367+ sideStageSnapAnimation.snapToId = application.appId;
368+ snapTo(root.width - sideStageImage.width);
369+ }
370+
371+ SequentialAnimation {
372+ id: sideStageSnapAnimation
373+ property int targetX: root.width
374+ property string snapToId
375+
376+ UbuntuNumberAnimation { target: sideStageImage; property: "x"; to: sideStageSnapAnimation.targetX; duration: UbuntuAnimation.SlowDuration }
377+ ScriptAction {
378+ script: {
379+ if (sideStageSnapAnimation.targetX == root.width) {
380+ if (priv.mainStageAppId) {
381+ ApplicationManager.focusApplication(priv.mainStageAppId)
382+ } else {
383+ priv.overlayOverride = true;
384+ ApplicationManager.unfocusCurrentApplication();
385+ }
386+ sideStageImage.shown = false;
387+ }
388+ if (sideStageSnapAnimation.snapToId) {
389+ ApplicationManager.focusApplication(sideStageSnapAnimation.snapToId)
390+ sideStageSnapAnimation.snapToId = "";
391+ sideStageImage.shown = true;
392+ priv.overlayOverride = false;
393+ }
394+ sideStageImage.visible = false;
395+ mainStageImage.visible = false;
396+ }
397+ }
398+ }
399+ }
400+
401+ Rectangle {
402+ id: sideStageSplash
403+ anchors.fill: parent
404+ anchors.leftMargin: sideStageImage.x
405+ color: "white"
406+ }
407+}
qml/Stages/SwitchingApplicationImage.qmlnew file+81
@@ -0,0 +1,81 @@
1+/*
2+ * Copyright 2014 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Authors: Michael Zanetti <michael.zanetti@canonical.com>
17+*/
18+
19+import QtQuick 2.0
20+import Ubuntu.Components 0.1
21+
22+Rectangle {
23+ id: root
24+ implicitHeight: image.implicitHeight
25+ implicitWidth: image.implicitWidth
26+ color: "black"
27+
28+ property var application
29+
30+ signal switched()
31+
32+ function switchTo(application) {
33+ if (root.application == application) {
34+ root.switched();
35+ return;
36+ }
37+
38+ priv.newApplication = application
39+ root.visible = true;
40+ switchToAnimation.start()
41+ }
42+
43+ QtObject {
44+ id: priv
45+ property var newApplication
46+ }
47+
48+ Image {
49+ id: newImage
50+ anchors.bottom: parent.bottom
51+ width: root.width
52+ source: priv.newApplication ? priv.newApplication.screenshot : ""
53+ }
54+
55+ Image {
56+ id: image
57+ visible: true
58+ source: root.application ? root.application.screenshot : ""
59+ width: root.width
60+ height: sourceSize.height
61+ anchors.bottom: parent.bottom
62+
63+ }
64+
65+ SequentialAnimation {
66+ id: switchToAnimation
67+ ParallelAnimation {
68+ UbuntuNumberAnimation { target: image; property: "x"; from: 0; to: root.width; duration: UbuntuAnimation.SlowDuration }
69+ UbuntuNumberAnimation { target: newImage; property: "scale"; from: 0.7; to: 1; duration: UbuntuAnimation.SlowDuration }
70+ }
71+ ScriptAction {
72+ script: {
73+ image.x = 0
74+ root.application = priv.newApplication
75+ root.visible = false;
76+ priv.newApplication = null
77+ root.switched();
78+ }
79+ }
80+ }
81+}
qml/Stages/TransformedSpreadDelegate.qmlnew file+327
@@ -0,0 +1,327 @@
1+/*
2+ * Copyright 2014 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Authors: Michael Zanetti <michael.zanetti@canonical.com>
17+*/
18+
19+import QtQuick 2.0
20+import Utils 0.1
21+import Ubuntu.Components 0.1
22+
23+SpreadDelegate {
24+ id: root
25+
26+ property bool selected: false
27+ property bool otherSelected: false
28+
29+ // The progress animates the tiles. A value > 0 makes it appear from the right edge. At 1 it reaches the end position.
30+ property real progress: 0
31+ // This is required to snap tile 1 during phase 1 and 2.
32+ property real animatedProgress: 0
33+
34+ property real startAngle: 0
35+ property real endAngle: 0
36+
37+ property real startScale: 1
38+ property real endScale: 1
39+
40+ // Specific to just one tile
41+ property real tile1StartScale: startScale + .4
42+ property real tile0SnapAngle: 10
43+
44+ property real startDistance: units.gu(5)
45+ property real endDistance: units.gu(.5)
46+
47+ onSelectedChanged: {
48+ if (selected) {
49+ priv.snapshot();
50+ }
51+ priv.isSelected = selected;
52+ }
53+
54+ onOtherSelectedChanged: {
55+ if (otherSelected) {
56+ priv.snapshot();
57+ }
58+ priv.otherSelected = otherSelected;
59+ }
60+
61+ Connections {
62+ target: spreadView
63+ onPhaseChanged: {
64+ if (spreadView.phase == 1) {
65+ if (index == 0) {
66+ priv.phase2startTranslate = priv.easingAnimation(0, spreadView.positionMarker4, 0, -spreadView.width, spreadView.positionMarker4) + spreadView.width;
67+ priv.phase2startAngle = priv.easingAnimation(0, spreadView.positionMarker4, root.startAngle, root.endAngle, spreadView.positionMarker4);
68+ priv.phase2startScale = priv.easingAnimation(0, spreadView.positionMarker4, root.startScale, root.endScale, spreadView.positionMarker4);
69+ priv.phase2startTopMarginProgress = priv.easingAnimation(0, 1, 0, 1, spreadView.positionMarker4);
70+ } else if (index == 1) {
71+ // find where the main easing for Tile 1 would be when reaching phase 2
72+ var phase2Progress = spreadView.positionMarker4 - spreadView.tileDistance / spreadView.width;
73+ priv.phase2startTranslate = priv.easingAnimation(0, phase2Progress, 0, -spreadView.width + root.endDistance, phase2Progress);
74+ priv.phase2startAngle = priv.easingAnimation(0, phase2Progress, root.startAngle, root.endAngle, phase2Progress);
75+ priv.phase2startScale = priv.easingAnimation(0, phase2Progress, root.startScale, root.endScale, phase2Progress);
76+ priv.phase2startTopMarginProgress = priv.easingAnimation(0, 1, 0, spreadView.positionMarker4, phase2Progress);
77+ }
78+ }
79+ }
80+ }
81+
82+ QtObject {
83+ id: priv
84+ property bool isSelected: false
85+ property bool otherSelected: false
86+ property real selectedProgress
87+ property real selectedXTranslate
88+ property real selectedAngle
89+ property real selectedScale
90+ property real selectedOpacity
91+ property real selectedTopMarginProgress
92+
93+ // Those values are needed as target values for the end of phase 1.
94+ // As they are static values, lets calculate them once when entering phase 1 instead of calculating them in each animation pass.
95+ property real phase2startTranslate
96+ property real phase2startAngle
97+ property real phase2startScale
98+ property real phase2startTopMarginProgress
99+
100+ function snapshot() {
101+ selectedProgress = root.progress;
102+ selectedXTranslate = xTranslate;
103+ selectedAngle = angle;
104+ selectedScale = scale;
105+ selectedOpacity = opacity;
106+ selectedTopMarginProgress = topMarginProgress;
107+ }
108+
109+ // This calculates how much negative progress there can be if unwinding the spread completely
110+ // the progress for each tile starts at 0 when it crosses the right edge, so the later a tile comes in,
111+ // the bigger its negativeProgress can be.
112+ property real negativeProgress: {
113+ if (index == 1 && spreadView.phase < 2) {
114+ return 0;
115+ }
116+ return -index * spreadView.tileDistance / spreadView.width;
117+ }
118+
119+ function linearAnimation(startProgress, endProgress, startValue, endValue, progress) {
120+ // progress : progressDiff = value : valueDiff => value = progress * valueDiff / progressDiff
121+ return (progress - startProgress) * (endValue - startValue) / (endProgress - startProgress) + startValue;
122+ }
123+
124+ function easingAnimation(startProgress, endProgress, startValue, endValue, progress) {
125+ helperEasingCurve.progress = progress - startProgress;
126+ helperEasingCurve.period = endProgress - startProgress;
127+ return helperEasingCurve.value * (endValue - startValue) + startValue;
128+ }
129+
130+ property real animatedEndDistance: linearAnimation(0, 2, root.endDistance, 0, root.progress)
131+
132+ // The following blocks handle the animation of the tile in the spread.
133+ // At the beginning, each tile is attached at the right edge, outside the screen.
134+ // The progress for each tile starts at 0 and it reaches its end position at a progress of 1.
135+ // The first phases are handled special for the first 2 tiles. as we do the alt-tab and snapping in there
136+ // Once we reached phase 3, the animation is the same for all tiles.
137+ // When a tile is selected, the animation state is snapshotted, and the spreadView is unwound (progress animates
138+ // back to negativeProgress). All tiles are kept in place and faded out to 0 opacity except
139+ // the selected tile, which is animated from the snapshotted position to be fullscreen.
140+
141+ property real xTranslate: {
142+ if (otherSelected) {
143+ if (spreadView.phase < 2 && index == 0) {
144+ return linearAnimation(selectedProgress, negativeProgress,
145+ selectedXTranslate, selectedXTranslate - spreadView.tileDistance, root.progress);
146+ }
147+
148+ return selectedXTranslate;
149+ }
150+
151+ switch (index) {
152+ case 0:
153+ if (spreadView.phase == 0) {
154+ return Math.min(0, linearAnimation(0, spreadView.positionMarker2,
155+ 0, -spreadView.width * .25, root.animatedProgress));
156+ } else if (spreadView.phase == 1){
157+ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
158+ -spreadView.width * .25, priv.phase2startTranslate, root.progress);
159+ } else if (!priv.isSelected){ // phase 2
160+ // Apply the same animation as with the rest but add spreadView.width to align it with the others.
161+ return -easingCurve.value * spreadView.width + spreadView.width;
162+ } else if (priv.isSelected) {
163+ return linearAnimation(selectedProgress, negativeProgress, selectedXTranslate, 0, root.progress);
164+ }
165+
166+ case 1:
167+ if (spreadView.phase == 0 && !priv.isSelected) {
168+ return linearAnimation(0, spreadView.positionMarker2,
169+ 0, -spreadView.width * spreadView.snapPosition, root.animatedProgress);
170+ } else if (spreadView.phase == 1 && !priv.isSelected) {
171+ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
172+ -spreadView.width * spreadView.snapPosition, priv.phase2startTranslate,
173+ root.progress);
174+ }
175+ }
176+
177+ if (priv.isSelected) {
178+ // Distance to left edge
179+ var targetTranslate = -spreadView.width - ((index - 1) * root.startDistance);
180+ return linearAnimation(selectedProgress, negativeProgress,
181+ selectedXTranslate, targetTranslate, root.progress);
182+ }
183+
184+ // Fix it at the right edge...
185+ var rightEdgeOffset = -((index - 1) * root.startDistance);
186+ // ...and use our easing to move them to the left. Stop a bit earlier for each tile
187+ return -easingCurve.value * spreadView.width + (index * animatedEndDistance) + rightEdgeOffset;
188+
189+ }
190+
191+ property real angle: {
192+ if (priv.otherSelected) {
193+ return priv.selectedAngle;
194+ }
195+ if (priv.isSelected) {
196+ return linearAnimation(selectedProgress, negativeProgress, selectedAngle, 0, root.progress);
197+ }
198+ switch (index) {
199+ case 0:
200+ if (spreadView.phase == 0) {
201+ return Math.max(0, linearAnimation(0, spreadView.positionMarker2,
202+ 0, root.tile0SnapAngle, root.animatedProgress));
203+ } else if (spreadView.phase == 1) {
204+ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
205+ root.tile0SnapAngle, phase2startAngle, root.progress);
206+ }
207+ case 1:
208+ if (spreadView.phase == 0) {
209+ return linearAnimation(0, spreadView.positionMarker2, root.startAngle,
210+ root.startAngle * (1-spreadView.snapPosition), root.animatedProgress);
211+ } else if (spreadView.phase == 1) {
212+ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
213+ root.startAngle * (1-spreadView.snapPosition), priv.phase2startAngle,
214+ root.progress);
215+ }
216+ }
217+ return root.startAngle - easingCurve.value * (root.startAngle - root.endAngle);
218+ }
219+
220+ property real scale: {
221+ if (priv.otherSelected) {
222+ return priv.selectedScale;
223+ }
224+ if (priv.isSelected) {
225+ return linearAnimation(selectedProgress, negativeProgress, selectedScale, 1, root.progress);
226+ }
227+
228+ switch (index) {
229+ case 0:
230+ if (spreadView.phase == 0) {
231+ return 1;
232+ } else if (spreadView.phase == 1) {
233+ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
234+ 1, phase2startScale, root.progress);
235+ }
236+ case 1:
237+ if (spreadView.phase == 0) {
238+ var targetScale = tile1StartScale - ((tile1StartScale - 1) * spreadView.snapPosition);
239+ return linearAnimation(0, spreadView.positionMarker2,
240+ root.tile1StartScale, targetScale, root.animatedProgress);
241+ } else if (spreadView.phase == 1) {
242+ var startScale = tile1StartScale - ((tile1StartScale - 1) * spreadView.snapPosition);
243+ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
244+ startScale, priv.phase2startScale, root.progress);
245+ }
246+ }
247+ return root.startScale - easingCurve.value * (root.startScale - root.endScale);
248+ }
249+
250+ property real opacity: {
251+ if (priv.otherSelected) {
252+ return linearAnimation (selectedProgress, Math.max(0, selectedProgress - .5),
253+ selectedOpacity, 0, root.progress);
254+ }
255+ if (index == 0) {
256+ switch (spreadView.phase) {
257+ case 0:
258+ return linearAnimation(0, spreadView.positionMarker2, 1, .3, root.animatedProgress);
259+ case 1:
260+ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
261+ .3, 1, root.animatedProgress);
262+ }
263+ }
264+
265+ return 1;
266+ }
267+
268+ property real topMarginProgress: {
269+ if (priv.isSelected) {
270+ return linearAnimation(selectedProgress, negativeProgress, selectedTopMarginProgress, 0, root.progress);
271+ }
272+
273+ switch (index) {
274+ case 0:
275+ if (spreadView.phase == 0) {
276+ return 0;
277+ } else if (spreadView.phase == 1) {
278+ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
279+ 0, priv.phase2startTopMarginProgress, root.progress);
280+ }
281+ break;
282+ case 1:
283+ if (spreadView.phase == 0) {
284+ return 0;
285+ } else if (spreadView.phase == 1) {
286+ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
287+ 0, priv.phase2startTopMarginProgress, root.progress);
288+ }
289+ }
290+
291+ return easingCurve.value;
292+ }
293+ }
294+
295+ transform: [
296+ Rotation {
297+ origin { x: 0; y: spreadView.height / 2 }
298+ axis { x: 0; y: 1; z: 0 }
299+ angle: priv.angle
300+ },
301+ Scale {
302+ origin { x: 0; y: spreadView.height / 2 }
303+ xScale: priv.scale
304+ yScale: xScale
305+ },
306+ Translate {
307+ x: priv.xTranslate
308+ }
309+ ]
310+ opacity: priv.opacity
311+ topMarginProgress: priv.topMarginProgress
312+
313+ EasingCurve {
314+ id: easingCurve
315+ type: EasingCurve.OutSine
316+ period: 1 - spreadView.positionMarker2
317+ progress: root.animatedProgress
318+ }
319+
320+ // This is used as a calculation helper to figure values for progress other than the current one
321+ // Do not bind anything to this...
322+ EasingCurve {
323+ id: helperEasingCurve
324+ type: easingCurve.type
325+ period: easingCurve.period
326+ }
327+}
qml/Stages/graphics/dropshadow.pngnew file

Binary file not shown.

qml/SideStage/graphics/sidestage_handle@20.png → qml/Stages/graphics/sidestage_handle@20.pngrenamed

Binary file not shown.

qml/SideStage/graphics/sidestage_handle@20.sci → qml/Stages/graphics/sidestage_handle@20.scirenamed

The content is the same.

qml/graphics/phone_background.jpg

Binary file not shown.

qml/graphics/tablet_background.jpg

Binary file not shown.

run+15−1
@@ -68,8 +68,22 @@ if $MOUSE_TOUCH; then
68 QML_PHONE_SHELL_ARGS="$QML_PHONE_SHELL_ARGS -mousetouch"
68 QML_PHONE_SHELL_ARGS="$QML_PHONE_SHELL_ARGS -mousetouch"
69fi
69fi
70
70
71+control_c()
72+{
73+ /sbin/initctl stop unity8
74+ exit $?
75+}
76+
77+trap control_c INT
78+
71if $GDB; then
79if $GDB; then
72 gdb -ex run --args $QML_PHONE_SHELL_PATH $QML_PHONE_SHELL_ARGS $@
80 gdb -ex run --args $QML_PHONE_SHELL_PATH $QML_PHONE_SHELL_ARGS $@
73else
81else
82+ status=`/sbin/initctl status unity8`
83+ if [ "$status" != "unity8 stop/waiting" ]; then
84+ echo "Unity8 is already running, please stop it first"
85+ exit 1
86+ fi
74− $QML_PHONE_SHELL_PATH $QML_PHONE_SHELL_ARGS $@
87+ /sbin/initctl start unity8 BINARY="`readlink -f $QML_PHONE_SHELL_PATH` $QML_PHONE_SHELL_ARGS $@"
88+ tailf -n 0 ~/.cache/upstart/unity8.log
75fi
89fi
src/main.cpp+3−6
@@ -117,13 +117,10 @@ int startShell(int argc, const char** argv, void* server)
117
117
118 QUrl source(::qmlDirectory()+"Shell.qml");
118 QUrl source(::qmlDirectory()+"Shell.qml");
119 prependImportPaths(view->engine(), ::overrideImportPaths());
119 prependImportPaths(view->engine(), ::overrideImportPaths());
120− appendImportPaths(view->engine(), ::fallbackImportPaths());
121−
122− if (isUbuntuMirServer) {
120+ if (!isUbuntuMirServer) {
123− QStringList importPaths = view->engine()->importPathList();
121+ prependImportPaths(view->engine(), ::nonMirImportPaths());
124− importPaths.replaceInStrings(QRegExp("qt5/imports$"), "qt5/imports/Unity-Mir");
125− view->engine()->setImportPathList(importPaths);
126 }
122 }
123+ appendImportPaths(view->engine(), ::fallbackImportPaths());
127
124
128 view->setSource(source);
125 view->setSource(source);
129 view->setColor("transparent");
126 view->setColor("transparent");
tests/CMakeLists.txt+2−2
@@ -1,7 +1,7 @@
1−# QML tests that do not require graphical capabitlies.
1+# QML tests that do not require graphical capabilities.
2add_custom_target(qmlunittests)
2add_custom_target(qmlunittests)
3
3
4−# QML tests that require graphical capabitlies.
4+# QML tests that require graphical capabilities.
5add_custom_target(qmluitests)
5add_custom_target(qmluitests)
6
6
7add_custom_target(qmltests)
7add_custom_target(qmltests)
tests/autopilot/unity8/application_lifecycle/tests/__init__.py+49
@@ -0,0 +1,49 @@
1+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2+#
3+# Unity Autopilot Test Suite
4+# Copyright (C) 2014 Canonical
5+#
6+# This program is free software: you can redistribute it and/or modify
7+# it under the terms of the GNU General Public License as published by
8+# the Free Software Foundation, either version 3 of the License, or
9+# (at your option) any later version.
10+#
11+# This program is distributed in the hope that it will be useful,
12+# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+# GNU General Public License for more details.
15+#
16+# You should have received a copy of the GNU General Public License
17+# along with this program. If not, see <http://www.gnu.org/licenses/>.
18+#
19+
20+from autopilot.matchers import Eventually
21+from testtools.matchers import Equals
22+from ubuntuuitoolkit import fixture_setup
23+
24+from unity8 import process_helpers
25+from unity8.shell import tests
26+
27+
28+class ApplicationLifeCycleTestCase(tests.UnityTestCase):
29+
30+ def setUp(self):
31+ super(ApplicationLifeCycleTestCase, self).setUp()
32+ self._qml_mock_enabled = False
33+ self._data_dirs_mock_enabled = False
34+ unity_proxy = self.launch_unity()
35+ process_helpers.unlock_unity(unity_proxy)
36+
37+ def create_test_application(self):
38+ desktop_file_dict = fixture_setup.DEFAULT_DESKTOP_FILE_DICT
39+ desktop_file_dict.update({'X-Ubuntu-Single-Instance': 'true'})
40+ fake_application = fixture_setup.FakeApplication(
41+ desktop_file_dict=desktop_file_dict)
42+ self.useFixture(fake_application)
43+ return (
44+ fake_application.qml_file_path, fake_application.desktop_file_path)
45+
46+ def assert_current_focused_application(self, application_name):
47+ self.assertThat(
48+ self.main_window.get_current_focused_app_id,
49+ Eventually(Equals(application_name)))
tests/autopilot/unity8/application_lifecycle/tests/test_application_lifecycle.py+56−72
@@ -21,27 +21,26 @@
21
21
22from __future__ import absolute_import
22from __future__ import absolute_import
23
23
24−from unity8.process_helpers import unlock_unity
24+import logging
25−from unity8.shell import disable_qml_mocking
25+import os
26−from unity8.shell.tests import UnityTestCase, _get_device_emulation_scenarios
27
26
28from autopilot.matchers import Eventually
27from autopilot.matchers import Eventually
29from autopilot.platform import model
28from autopilot.platform import model
30−from testtools.matchers import Equals, NotEquals
29+from testtools.matchers import Equals
31
30
32−import logging
31+from unity8 import process_helpers
32+from unity8.application_lifecycle import tests
33
33
34−logger = logging.getLogger(__name__)
35
34
35+logger = logging.getLogger(__name__)
36
36
37−class ApplicationLifecycleTests(UnityTestCase):
38
37
39− scenarios = _get_device_emulation_scenarios()
38+class ApplicationLifecycleTests(tests.ApplicationLifeCycleTestCase):
40
39
41 def setUp(self):
40 def setUp(self):
41+ if model() == 'Desktop':
42+ self.skipTest('Test cannot be run on the desktop.')
42 super(ApplicationLifecycleTests, self).setUp()
43 super(ApplicationLifecycleTests, self).setUp()
43− if model() == "Desktop":
44− self.skipTest("Test cannot be run on the desktop.")
45
44
46 def swipe_screen_from_right(self):
45 def swipe_screen_from_right(self):
47 width = self.main_window.width
46 width = self.main_window.width
@@ -54,80 +53,65 @@ class ApplicationLifecycleTests(UnityTestCase):
54 logger.info("Swiping screen from the right edge")
53 logger.info("Swiping screen from the right edge")
55 self.touch.drag(start_x, start_y, end_x, end_y)
54 self.touch.drag(start_x, start_y, end_x, end_y)
56
55
57− def _launch_app(self, app_name):
56+ def launch_fake_app(self):
58− """Launches the application *app_name*
57+ _, desktop_file_path = self.create_test_application()
59−
58+ desktop_file_name = os.path.basename(desktop_file_path)
60− Assumes that the desktop file resides at:
59+ application_name, _ = os.path.splitext(desktop_file_name)
61− /usr/share/applications/{app_name}.desktop
60+ self.launch_upstart_application(application_name)
61+ return application_name
62
62
63− """
64− desktop_file = "--desktop_file_hint="\
65− "/usr/share/applications/{app_name}.desktop".format(
66− app_name=app_name
67− )
68− return self.launch_test_application(
69− app_name,
70− desktop_file,
71− "--stage_hint=main_stage",
72− app_type='qt'
73− )
74−
75− @disable_qml_mocking
76 def test_can_launch_application(self):
63 def test_can_launch_application(self):
77 """Must be able to launch an application."""
64 """Must be able to launch an application."""
78− unity_proxy = self.launch_unity()
65+ application_name = self.launch_fake_app()
79− unlock_unity(unity_proxy)
66+ self.assert_current_focused_application(application_name)
80−
81− app = self._launch_app("messaging-app")
82
67
83− self.assertThat(app, NotEquals(None))
84− self.assertThat(
85− self.main_window.get_current_focused_app_id(),
86− Eventually(Equals("messaging-app"))
87− )
88−
89− @disable_qml_mocking
90 def test_can_launch_multiple_applications(self):
68 def test_can_launch_multiple_applications(self):
91 """A second application launched must be focused."""
69 """A second application launched must be focused."""
92− unity_proxy = self.launch_unity()
70+ application1_name = self.launch_fake_app()
93− unlock_unity(unity_proxy)
71+ self.assert_current_focused_application(application1_name)
94−
72+
95− self._launch_app("messaging-app")
73+ application2_name = self.launch_fake_app()
96− self.assertThat(
74+ self.assertFalse(application1_name == application2_name)
97− self.main_window.get_current_focused_app_id(),
75+ self.assert_current_focused_application(application2_name)
98− Eventually(Equals("messaging-app"))
76+
99− )
100−
101− self._launch_app("address-book-app")
102− self.assertThat(
103− self.main_window.get_current_focused_app_id(),
104− Eventually(Equals("address-book-app"))
105− )
106−
107− @disable_qml_mocking
108 def test_app_moves_from_unfocused_to_focused(self):
77 def test_app_moves_from_unfocused_to_focused(self):
109 """An application that is in the unfocused state must be able to be
78 """An application that is in the unfocused state must be able to be
110 brought back to the focused state.
79 brought back to the focused state.
111
80
112 """
81 """
113− unity_proxy = self.launch_unity()
82+ application1_name = self.launch_fake_app()
114− unlock_unity(unity_proxy)
83+ self.assert_current_focused_application(application1_name)
115−
116− self._launch_app("messaging-app")
117− self.assertThat(
118− self.main_window.get_current_focused_app_id(),
119− Eventually(Equals("messaging-app"))
120− )
121
84
122− self._launch_app("address-book-app")
85+ application2_name = self.launch_fake_app()
123− self.assertThat(
86+ self.assertFalse(application1_name == application2_name)
124− self.main_window.get_current_focused_app_id(),
87+ self.assert_current_focused_application(application2_name)
125− Eventually(Equals("address-book-app"))
126− )
127
88
128 self.swipe_screen_from_right()
89 self.swipe_screen_from_right()
129
90
130− self.assertThat(
91+ self.assert_current_focused_application(application1_name)
92+
93+ def test_greeter_hides_on_app_open(self):
94+ """Greeter should hide when an app is opened"""
95+ process_helpers.lock_unity()
131− self.main_window.get_current_focused_app_id(),
96+ greeter = self.main_window.get_greeter()
97+ self.assertThat(greeter.created, Eventually(Equals(True)))
98+
99+ application_name = self.launch_fake_app()
132− Eventually(Equals("messaging-app"))
100+ self.assertThat(greeter.created, Eventually(Equals(False)))
133− )
101+ self.assert_current_focused_application(application_name)
102+
103+ def test_greeter_hides_on_app_focus(self):
104+ """Greeter should hide when an app is re-focused"""
105+ application_name = self.launch_fake_app()
106+ self.assert_current_focused_application(application_name)
107+
108+ self.main_window.show_dash_swiping()
109+ self.assert_current_focused_application('')
110+
111+ process_helpers.lock_unity()
112+ greeter = self.main_window.get_greeter()
113+ self.assertThat(greeter.created, Eventually(Equals(True)))
114+
115+ self.launch_upstart_application(application_name)
116+ self.assertThat(greeter.created, Eventually(Equals(False)))
117+ self.assert_current_focused_application(application_name)
tests/autopilot/unity8/application_lifecycle/tests/test_url_dispatcher.py+8−39
@@ -20,48 +20,19 @@
20"""Test the integration with the URL dispatcher service."""
20"""Test the integration with the URL dispatcher service."""
21
21
22import os
22import os
23+import subprocess
23
24
24from autopilot import platform
25from autopilot import platform
25−from autopilot.matchers import Eventually
26−from testtools.matchers import Equals
27−from ubuntuuitoolkit import fixture_setup
28
26
29−from unity8 import process_helpers
27+from unity8.application_lifecycle import tests
30−from unity8.shell import tests
31
28
32
29
33−class URLDispatcherTestCase(tests.UnityTestCase):
30+class URLDispatcherTestCase(tests.ApplicationLifeCycleTestCase):
34−
35− scenarios = tests._get_device_emulation_scenarios()
36−
37− test_qml = ("""
38−import QtQuick 2.0
39−import Ubuntu.Components 0.1
40−
41−MainView {
42− width: units.gu(48)
43− height: units.gu(60)
44−
45− Label {
46− text: 'Test application.'
47− }
48−}
49−""")
50
31
51 def setUp(self):
32 def setUp(self):
52 if platform.model() == 'Desktop':
33 if platform.model() == 'Desktop':
53 self.skipTest("URL dispatcher doesn't work on the desktop.")
34 self.skipTest("URL dispatcher doesn't work on the desktop.")
54 super(URLDispatcherTestCase, self).setUp()
35 super(URLDispatcherTestCase, self).setUp()
55− self._qml_mock_enabled = False
56− self._data_dirs_mock_enabled = False
57− unity_proxy = self.launch_unity()
58− process_helpers.unlock_unity(unity_proxy)
59−
60− def create_test_application(self):
61− fake_application = fixture_setup.FakeApplication(self.test_qml)
62− self.useFixture(fake_application)
63− return (
64− fake_application.qml_file_path, fake_application.desktop_file_path)
65
36
66 def test_swipe_out_application_started_by_url_dispatcher(self):
37 def test_swipe_out_application_started_by_url_dispatcher(self):
67 _, desktop_file_path = self.create_test_application()
38 _, desktop_file_path = self.create_test_application()
@@ -70,12 +41,10 @@ MainView {
70
41
71 self.assertEqual('', self.main_window.get_current_focused_app_id())
42 self.assertEqual('', self.main_window.get_current_focused_app_id())
72 self.addCleanup(os.system, 'pkill qmlscene')
43 self.addCleanup(os.system, 'pkill qmlscene')
44+
45+ subprocess.check_call(
73− os.system('url-dispatcher application:///{}'.format(desktop_file_name))
46+ ['url-dispatcher', 'application:///{}'.format(desktop_file_name)])
74− self.assertThat(
47+ self.assert_current_focused_application(application_name)
75− self.main_window.get_current_focused_app_id,
76− Eventually(Equals(application_name)))
77
48
78 self.main_window.show_dash_swiping()
49 self.main_window.show_dash_swiping()
79− self.assertThat(
50+ self.assert_current_focused_application('')
80− self.main_window.get_current_focused_app_id,
81− Eventually(Equals('')))
tests/autopilot/unity8/indicators/tests/__init__.py+17
@@ -0,0 +1,17 @@
1+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2+#
3+# Unity - Indicators Autopilot Test Suite
4+# Copyright (C) 2013, 2014 Canonical
5+#
6+# This program is free software: you can redistribute it and/or modify
7+# it under the terms of the GNU General Public License as published by
8+# the Free Software Foundation, either version 3 of the License, or
9+# (at your option) any later version.
10+#
11+# This program is distributed in the hope that it will be useful,
12+# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+# GNU General Public License for more details.
15+#
16+# You should have received a copy of the GNU General Public License
17+# along with this program. If not, see <http://www.gnu.org/licenses/>.
tests/autopilot/unity8/indicators/tests/test_indicators.py+69−13
@@ -1,7 +1,7 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
2#
3−# Unity - Indicators Autopilot Test Suite
3+# Unity Indicators Autopilot Test Suite
4−# Copyright (C) 2013 Canonical
4+# Copyright (C) 2013, 2014 Canonical
5#
5#
6# This program is free software: you can redistribute it and/or modify
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
7# it under the terms of the GNU General Public License as published by
@@ -15,20 +15,32 @@
15#
15#
16# You should have received a copy of the GNU General Public License
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18−#
19
18
20from __future__ import absolute_import
19from __future__ import absolute_import
21
20
21+from testscenarios import multiply_scenarios
22+
22from autopilot import platform
23from autopilot import platform
23−from testtools import skipIf
24
24
25from unity8.process_helpers import unlock_unity
25from unity8.process_helpers import unlock_unity
26−from unity8.shell.tests import UnityTestCase
26+from unity8.shell.tests import UnityTestCase, _get_device_emulation_scenarios
27
27
28
28
29class IndicatorTestCase(UnityTestCase):
29class IndicatorTestCase(UnityTestCase):
30
30
31− scenarios = [
31+ device_emulation_scenarios = _get_device_emulation_scenarios()
32+
33+ def setUp(self):
34+ if platform.model() == 'Desktop':
35+ self.skipTest('Test cannot be run on the desktop.')
36+ super(IndicatorTestCase, self).setUp()
37+ self.unity_proxy = self.launch_unity()
38+ unlock_unity(self.unity_proxy)
39+
40+
41+class IndicatorExistsTestCase(IndicatorTestCase):
42+
43+ indicator_scenarios = [
32 ('Bluetooth', dict(indicator_name='indicator-bluetooth')),
44 ('Bluetooth', dict(indicator_name='indicator-bluetooth')),
33 ('Datetime', dict(indicator_name='indicator-datetime')),
45 ('Datetime', dict(indicator_name='indicator-datetime')),
34 ('Location', dict(indicator_name='indicator-location')),
46 ('Location', dict(indicator_name='indicator-location')),
@@ -37,14 +49,58 @@ class IndicatorTestCase(UnityTestCase):
37 ('Power', dict(indicator_name='indicator-power')),
49 ('Power', dict(indicator_name='indicator-power')),
38 ('Sound', dict(indicator_name='indicator-sound')),
50 ('Sound', dict(indicator_name='indicator-sound')),
39 ]
51 ]
52+ scenarios = multiply_scenarios(
53+ indicator_scenarios,
54+ IndicatorTestCase.device_emulation_scenarios
55+ )
40
56
41− @skipIf(platform.image_codename() == 'Desktop',
42− "Unity8/phablet-only test!")
43 def setUp(self):
57 def setUp(self):
44− super(IndicatorTestCase, self).setUp()
58+ super(IndicatorExistsTestCase, self).setUp()
59+ if (platform.model() == 'Nexus 10' and
60+ self.indicator_name == 'indicator-bluetooth'):
61+ self.skipTest('Nexus 10 does not have bluetooth at the moment.')
45
62
46 def test_indicator_exists(self):
63 def test_indicator_exists(self):
47− unity_proxy = self.launch_unity()
64+ self.main_window._get_indicator_widget(
48− unlock_unity(unity_proxy)
65+ self.indicator_name
66+ )
67+
68+
69+class IndicatorPageTitleMatchesWidgetTestCase(IndicatorTestCase):
70+
71+ indicator_scenarios = [
72+ ('Bluetooth', dict(indicator_name='indicator-bluetooth',
73+ title='Bluetooth')),
74+ ('Datetime', dict(indicator_name='indicator-datetime',
75+ title='Upcoming')),
76+ ('Location', dict(indicator_name='indicator-location',
77+ title='Location')),
78+ ('Messaging', dict(indicator_name='indicator-messages',
79+ title='Incoming')),
80+ ('Network', dict(indicator_name='indicator-network',
81+ title='Network')),
82+ ('Power', dict(indicator_name='indicator-power',
83+ title='Battery')),
84+ ('Sound', dict(indicator_name='indicator-sound',
85+ title='Sound')),
86+ ]
87+ scenarios = multiply_scenarios(
88+ indicator_scenarios,
89+ IndicatorTestCase.device_emulation_scenarios
90+ )
91+
92+ def setUp(self):
93+ super(IndicatorPageTitleMatchesWidgetTestCase, self).setUp()
94+ if (platform.model() == 'Nexus 10' and
95+ self.indicator_name == 'indicator-bluetooth'):
96+ self.skipTest('Nexus 10 does not have bluetooth at the moment.')
97+
98+ def test_indicator_page_title_matches_widget(self):
99+ """Swiping open an indicator must show its correct title.
100+
101+ See https://bugs.launchpad.net/ubuntu-ux/+bug/1253804 .
102+ """
103+ indicator_page = self.main_window.open_indicator_page(
49− indicator = self.main_window.get_indicator(self.indicator_name)
104+ self.indicator_name)
50− self.assertIsNotNone(indicator)
105+ self.assertTrue(indicator_page.visible)
106+ self.assertEqual(indicator_page.title, self.title)
tests/autopilot/unity8/process_helpers.py+16
@@ -89,6 +89,22 @@ def unlock_unity(unity_proxy_obj=None):
89 break
89 break
90
90
91
91
92+def lock_unity(unity_proxy_obj=None):
93+ """Helper function that attempts to lock the unity greeter."""
94+ import evdev, time
95+ uinput = evdev.UInput(name='unity8-autopilot-power-button',
96+ devnode='/dev/autopilot-uinput')
97+ # One press and release to turn screen off (locking unity)
98+ uinput.write(evdev.ecodes.EV_KEY, evdev.ecodes.KEY_POWER, 1)
99+ uinput.write(evdev.ecodes.EV_KEY, evdev.ecodes.KEY_POWER, 0)
100+ uinput.syn()
101+ time.sleep(1)
102+ # And another press and release to turn screen back on
103+ uinput.write(evdev.ecodes.EV_KEY, evdev.ecodes.KEY_POWER, 1)
104+ uinput.write(evdev.ecodes.EV_KEY, evdev.ecodes.KEY_POWER, 0)
105+ uinput.syn()
106+
107+
92def restart_unity_with_testability(*args):
108def restart_unity_with_testability(*args):
93 """Restarts (or starts) unity with testability enabled.
109 """Restarts (or starts) unity with testability enabled.
94
110
tests/autopilot/unity8/shell/emulators/__init__.py+2−6
@@ -17,16 +17,12 @@
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#
18#
19
19
20−from autopilot import input, introspection
20+from ubuntuuitoolkit import emulators as toolkit_emulators
21
21
22
22
23−class UnityEmulatorBase(introspection.CustomEmulatorBase):
23+class UnityEmulatorBase(toolkit_emulators.UbuntuUIToolkitEmulatorBase):
24 """A base class for all unity emulators."""
24 """A base class for all unity emulators."""
25
25
26− def __init__(self, *args):
27− super(UnityEmulatorBase, self).__init__(*args)
28− self.pointing_device = input.Pointer(device=input.Touch.create())
29−
30
26
31class UnityEmulatorException(Exception):
27class UnityEmulatorException(Exception):
32 """Exception raised when there is an error with the Unity emulators."""
28 """Exception raised when there is an error with the Unity emulators."""
tests/autopilot/unity8/shell/emulators/dash.py+35−10
@@ -23,6 +23,8 @@ from unity8.shell import emulators
23
23
24from autopilot import logging as autopilot_logging
24from autopilot import logging as autopilot_logging
25from autopilot.introspection import dbus
25from autopilot.introspection import dbus
26+from testtools.matchers import MatchesAny, Equals
27+from ubuntuuitoolkit import emulators as toolkit_emulators
26
28
27
29
28logger = logging.getLogger(__name__)
30logger = logging.getLogger(__name__)
@@ -80,10 +82,7 @@ class Dash(emulators.UnityEmulatorBase):
80 'No scope found with id {0}'.format(scope_id))
82 'No scope found with id {0}'.format(scope_id))
81
83
82 def _get_scope_from_loader(self, loader):
84 def _get_scope_from_loader(self, loader):
83− if loader.scopeId == 'clickscope':
84− return loader.select_single(DashApps)
85+ return loader.get_children()[0]
85− else:
86− return loader.select_single(GenericScopeView)
87
86
88 def _open_scope_scrolling(self, scope_loader):
87 def _open_scope_scrolling(self, scope_loader):
89 scroll = self._get_scroll_direction(scope_loader)
88 scroll = self._get_scroll_direction(scope_loader)
@@ -135,6 +134,19 @@ class Dash(emulators.UnityEmulatorBase):
135 self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
134 self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
136 self.dash_content_list.currentIndex.wait_for(original_index + 1)
135 self.dash_content_list.currentIndex.wait_for(original_index + 1)
137
136
137+ def enter_search_query(self, query):
138+ search_text_field = self._get_search_text_field()
139+ search_text_field.write(query)
140+ search_text_field.state.wait_for('idle')
141+
142+ def _get_search_text_field(self):
143+ page_header = self._get_page_header()
144+ search_container = page_header.select_single(
145+ 'QQuickItem', objectName='searchContainer')
146+ search_container.state.wait_for(
147+ MatchesAny(Equals('narrowActive'), Equals('active')))
148+ return search_container.select_single(toolkit_emulators.TextField)
149+
138
150
139class GenericScopeView(emulators.UnityEmulatorBase):
151class GenericScopeView(emulators.UnityEmulatorBase):
140 """Autopilot emulator for generic scopes."""
152 """Autopilot emulator for generic scopes."""
@@ -145,6 +157,7 @@ class GenericScopeView(emulators.UnityEmulatorBase):
145
157
146 :parameter category: The name of the category where the application is.
158 :parameter category: The name of the category where the application is.
147 :app_name: The name of the application.
159 :app_name: The name of the application.
160+ :return: The opened preview.
148
161
149 """
162 """
150 category_element = self._get_category_element(category)
163 category_element = self._get_category_element(category)
@@ -153,8 +166,11 @@ class GenericScopeView(emulators.UnityEmulatorBase):
153 # Some categories do not show previews, like recent apps.
166 # Some categories do not show previews, like recent apps.
154 # --elopio - 2014-1-14
167 # --elopio - 2014-1-14
155 self.pointing_device.click_object(icon)
168 self.pointing_device.click_object(icon)
156− return self.get_root_instance().wait_select_single(
169+ preview_list = self.get_root_instance().wait_select_single(
157 'PreviewListView', objectName='dashContentPreviewList')
170 'PreviewListView', objectName='dashContentPreviewList')
171+ preview_list.x.wait_for(0)
172+ return preview_list.select_single(
173+ Preview, objectName='preview{}'.format(preview_list.currentIndex))
158
174
159 def _get_category_element(self, category):
175 def _get_category_element(self, category):
160 try:
176 try:
@@ -175,11 +191,20 @@ class DashApps(GenericScopeView):
175
191
176 """
192 """
177 category_element = self._get_category_element(category)
193 category_element = self._get_category_element(category)
178− application_tiles = category_element.select_many('Card')
194+ application_cards = category_element.select_many('Card')
179− # TODO return them on the same order they are displayed.
195+
180− # --elopio - 2014-1-15
196+ # sort by y, x
197+ application_cards = sorted(
198+ application_cards,
199+ key=lambda card: (card.globalRect.y, card.globalRect.x))
200+
181 result = []
201 result = []
182− for card in application_tiles:
202+ for card in application_cards:
183 if card.objectName != 'cardToolCard':
203 if card.objectName != 'cardToolCard':
204+ card_header = card.select_single('CardHeader')
184− result.append(card)
205+ result.append(card_header.title)
185 return result
206 return result
207+
208+
209+class Preview(emulators.UnityEmulatorBase):
210+ """Autopilot custom proxy object for generic previews."""
tests/autopilot/unity8/shell/emulators/main_window.py+33−3
@@ -20,6 +20,7 @@
20import logging
20import logging
21
21
22from autopilot import logging as autopilot_logging
22from autopilot import logging as autopilot_logging
23+from autopilot import input
23
24
24from unity8.shell import emulators
25from unity8.shell import emulators
25from unity8.shell.emulators.greeter import Greeter
26from unity8.shell.emulators.greeter import Greeter
@@ -87,12 +88,32 @@ class QQuickView(emulators.UnityEmulatorBase):
87 def get_pinentryField(self):
88 def get_pinentryField(self):
88 return self.select_single(objectName="pinentryField")
89 return self.select_single(objectName="pinentryField")
89
90
90− def get_indicator(self, indicator_name):
91+ def _get_indicator_widget(self, indicator_name):
91− return self.wait_select_single(
92+ return self.select_single(
92 'DefaultIndicatorWidget',
93 'DefaultIndicatorWidget',
93 objectName=indicator_name+'-widget'
94 objectName=indicator_name+'-widget'
94 )
95 )
95
96
97+ def _get_indicator_page(self, indicator_name):
98+ return self.select_single(
99+ 'DefaultIndicatorPage',
100+ objectName=indicator_name+'-page'
101+ )
102+
103+ @autopilot_logging.log_action(logger.info)
104+ def open_indicator_page(self, indicator_name):
105+ """Swipe to open the indicator, wait until it's open.
106+
107+ :returns: The indicator page.
108+ """
109+ widget = self._get_indicator_widget(indicator_name)
110+ start_x, start_y = input.get_center_point(widget)
111+ end_x = start_x
112+ end_y = self.height
113+ self.pointing_device.drag(start_x, start_y, end_x, end_y)
114+ self.wait_select_single('Indicators', fullyOpened=True)
115+ return self._get_indicator_page(indicator_name)
116+
96 def get_shell_background(self):
117 def get_shell_background(self):
97 return self.select_single(
118 return self.select_single(
98 "CrossFadeImage", objectName="backgroundImage")
119 "CrossFadeImage", objectName="backgroundImage")
@@ -112,4 +133,13 @@ class QQuickView(emulators.UnityEmulatorBase):
112
133
113 def get_current_focused_app_id(self):
134 def get_current_focused_app_id(self):
114 """Return the id of the focused application."""
135 """Return the id of the focused application."""
115− return self.select_single('Shell').currentFocusedAppId
136+ return self.select_single('Shell').focusedApplicationId
137+
138+ @autopilot_logging.log_action(logger.info)
139+ def search(self, query):
140+ search_indicator = self._get_search_indicator()
141+ self.pointing_device.click_object(search_indicator)
142+ self.get_dash().enter_search_query(query)
143+
144+ def _get_search_indicator(self):
145+ return self.select_single('SearchIndicator', objectName='search')
tests/autopilot/unity8/shell/tests/test_emulators.py+27−17
@@ -39,6 +39,22 @@ from unity8.shell import emulators, fixture_setup, tests
39from unity8.shell.emulators import dash as dash_emulators
39from unity8.shell.emulators import dash as dash_emulators
40
40
41
41
42+class MainWindowTestCase(tests.UnityTestCase):
43+
44+ scenarios = tests._get_device_emulation_scenarios()
45+
46+ def setUp(self):
47+ super(MainWindowTestCase, self).setUp()
48+ unity_proxy = self.launch_unity()
49+ process_helpers.unlock_unity(unity_proxy)
50+
51+ def test_search(self):
52+ self.main_window.search('Test')
53+ text_field = self.main_window.get_dash()._get_search_text_field()
54+ self.assertEqual(text_field.text, 'Test')
55+ self.assertEqual(text_field.state, 'idle')
56+
57+
42class DashBaseTestCase(tests.UnityTestCase):
58class DashBaseTestCase(tests.UnityTestCase):
43
59
44 scenarios = tests._get_device_emulation_scenarios()
60 scenarios = tests._get_device_emulation_scenarios()
@@ -141,14 +157,16 @@ class GenericScopeViewEmulatorTestCase(DashBaseTestCase):
141
157
142 def test_open_preview(self):
158 def test_open_preview(self):
143 preview = self.generic_scope.open_preview('0', 'Title.0.0')
159 preview = self.generic_scope.open_preview('0', 'Title.0.0')
144− preview.x.wait_for(0)
160+ self.assertIsInstance(preview, dash_emulators.Preview)
161+ self.assertTrue(preview.isCurrent)
162+
145
163
146class DashAppsEmulatorTestCase(DashBaseTestCase):
164class DashAppsEmulatorTestCase(DashBaseTestCase):
147
165
148 available_applications = [
166 available_applications = [
149 'Title.2.0', 'Title.2.1', 'Title.2.2', 'Title.2.3', 'Title.2.4',
167 'Title.2.0', 'Title.2.1', 'Title.2.2', 'Title.2.3', 'Title.2.4',
150 'Title.2.5', 'Title.2.6', 'Title.2.7', 'Title.2.8', 'Title.2.9',
168 'Title.2.5', 'Title.2.6', 'Title.2.7', 'Title.2.8', 'Title.2.9',
151− 'Title.2.9', 'Title.2.10', 'Title.2.11']
169+ 'Title.2.10', 'Title.2.11', 'Title.2.12']
152
170
153 def setUp(self):
171 def setUp(self):
154 # Set up the fake scopes before launching unity.
172 # Set up the fake scopes before launching unity.
@@ -165,28 +183,20 @@ class DashAppsEmulatorTestCase(DashBaseTestCase):
165 self.assertEqual(
183 self.assertEqual(
166 'No category found with name unexisting category', str(exception))
184 'No category found with name unexisting category', str(exception))
167
185
168− def test_get_applications_should_return_list_with_names(self):
186+ def test_get_applications_should_return_correct_applications(self):
169 category = '2'
187 category = '2'
170 expected_apps_count = self._get_number_of_application_slots(category)
188 expected_apps_count = self._get_number_of_application_slots(category)
171 expected_applications = self.available_applications[
189 expected_applications = self.available_applications[
172 :expected_apps_count]
190 :expected_apps_count]
173−
174 applications = self.applications_scope.get_applications(category)
191 applications = self.applications_scope.get_applications(category)
175− applications_titles = []
176− for application in applications:
177− cardHeader = application.select_single('CardHeader')
178− applications_titles.append(cardHeader.title)
179−
180− self.assertThat(applications, HasLength(expected_apps_count))
181− for expected in expected_applications:
192+ self.assertEqual(expected_applications, applications)
182− self.assertThat(applications_titles, Contains(expected))
183
193
184 def _get_number_of_application_slots(self, category):
194 def _get_number_of_application_slots(self, category):
185 category_element = self.applications_scope._get_category_element(
195 category_element = self.applications_scope._get_category_element(
186 category)
196 category)
187 grid = category_element.select_single('CardFilterGrid')
197 grid = category_element.select_single('CardFilterGrid')
188− return grid.columns * grid.rows
198+ filtergrid = grid.select_single('FilterGrid')
189−
199+ if (grid.filter):
190− def test_open_preview(self):
200+ return filtergrid.collapsedRowCount * filtergrid.columns
191− preview = self.applications_scope.open_preview('2', 'Title.2.1')
201+ else:
192− preview.x.wait_for(0)
202+ return filtergrid.uncollapsedRowCount * filtergrid.columns
tests/autopilot/unity8/shell/tests/test_notifications.py+3−4
@@ -196,7 +196,7 @@ class InteractiveNotificationBase(NotificationsBase):
196 get_notification = lambda: notify_list.wait_select_single(
196 get_notification = lambda: notify_list.wait_select_single(
197 'Notification', objectName='notification1')
197 'Notification', objectName='notification1')
198 notification = get_notification()
198 notification = get_notification()
199− self._assert_notification(notification, None, None, True, True, 1.0)
199+ self._assert_notification(notification, summary, body, True, True, 1.0)
200 initial_height = notification.height
200 initial_height = notification.height
201 self.touch.tap_object(notification.select_single(objectName="button1"))
201 self.touch.tap_object(notification.select_single(objectName="button1"))
202 self.assertThat(
202 self.assertThat(
@@ -403,7 +403,6 @@ class EphemeralNotificationsTests(NotificationsBase):
403
403
404 notification = lambda: notify_list.wait_select_single(
404 notification = lambda: notify_list.wait_select_single(
405 'Notification', objectName='notification1')
405 'Notification', objectName='notification1')
406−
407 self._assert_notification(
406 self._assert_notification(
408 notification(),
407 notification(),
409 summary,
408 summary,
@@ -579,7 +578,7 @@ class EphemeralNotificationsTests(NotificationsBase):
579 icon_path = self._get_icon_path('avatars/amanda.png')
578 icon_path = self._get_icon_path('avatars/amanda.png')
580 notification.update(summary, body, icon_path)
579 notification.update(summary, body, icon_path)
581 notification.show()
580 notification.show()
582− self._assert_notification(get_notification(), summary, body)
581+ self._assert_notification(get_notification(), summary, body, True, False, 1.0)
583
582
584 def test_update_notification_layout_change(self):
583 def test_update_notification_layout_change(self):
585 """Notification must allow updating its contents and layout while
584 """Notification must allow updating its contents and layout while
@@ -626,7 +625,7 @@ class EphemeralNotificationsTests(NotificationsBase):
626 notification.show()
625 notification.show()
627
626
628 self.assertThat(get_notification, Eventually(NotEquals(None)))
627 self.assertThat(get_notification, Eventually(NotEquals(None)))
629− self._assert_notification(get_notification(), summary, body, False)
628+ self._assert_notification(get_notification(), summary, body, False, False, 1.0)
630
629
631 def test_append_hint(self):
630 def test_append_hint(self):
632 """Notification has to accumulate body-text using append-hint."""
631 """Notification has to accumulate body-text using append-hint."""
tests/autopilot/unity8/shell/tests/test_system_integration.py+3−2
@@ -22,6 +22,7 @@
22import subprocess
22import subprocess
23import unittest
23import unittest
24
24
25+from autopilot import platform
25from unity8.shell.emulators import UnityEmulatorBase
26from unity8.shell.emulators import UnityEmulatorBase
26from unity8.shell.tests import UnityTestCase, _get_device_emulation_scenarios
27from unity8.shell.tests import UnityTestCase, _get_device_emulation_scenarios
27
28
@@ -31,10 +32,10 @@ class SystemIntegrationTests(UnityTestCase):
31
32
32 scenarios = _get_device_emulation_scenarios()
33 scenarios = _get_device_emulation_scenarios()
33
34
34− @unittest.skip("Disabled until we can investigate jenkins failure")
35+ @unittest.skipIf(platform.model() == "Desktop", "Test is broken on otto, see bug 1281634.")
35 def test_networkmanager_integration(self):
36 def test_networkmanager_integration(self):
36 self.launch_unity()
37 self.launch_unity()
37
38
38 # invoke policykit to check permissions
39 # invoke policykit to check permissions
39− pid = subprocess.check_output("pidof -s unity8", shell=True)
40+ pid = subprocess.check_output(["pidof", "-s", "unity8"], universal_newlines=True)
40 subprocess.check_call("pkcheck --action-id org.freedesktop.NetworkManager.enable-disable-network --process " + pid, shell=True)
41 subprocess.check_call("pkcheck --action-id org.freedesktop.NetworkManager.enable-disable-network --process " + pid, shell=True)
tests/mocks/Ubuntu/CMakeLists.txt+1
@@ -1 +1,2 @@
1add_subdirectory(DownloadDaemonListener)
1add_subdirectory(DownloadDaemonListener)
2+add_subdirectory(Thumbnailer)
tests/mocks/Ubuntu/Thumbnailer/CMakeLists.txtnew file+7
@@ -0,0 +1,7 @@
1+# copy files into build directory for shadow builds
2+add_custom_target(UbuntuThumbnailerQmlDirFile ALL
3+ COMMAND cp "${CMAKE_CURRENT_SOURCE_DIR}/qmldir" ${CMAKE_CURRENT_BINARY_DIR}
4+ DEPENDS qmldir
5+)
6+
7+install(FILES qmldir DESTINATION ${SHELL_INSTALL_QML}/mocks/Ubuntu/Thumbnailer)
tests/mocks/Ubuntu/Thumbnailer/qmldirnew file+2
@@ -0,0 +1,2 @@
1+module Ubuntu.Thumbnailer
2+Dummy 0.1 Dummy.qml
tests/mocks/Unity/Application/ApplicationInfo.cpp+9
@@ -20,6 +20,7 @@
20#include <QQuickItem>
20#include <QQuickItem>
21#include <QQuickView>
21#include <QQuickView>
22#include <QQmlComponent>
22#include <QQmlComponent>
23+#include <QTimer>
23
24
24ApplicationInfo::ApplicationInfo(const QString &appId, QObject *parent)
25ApplicationInfo::ApplicationInfo(const QString &appId, QObject *parent)
25 : ApplicationInfoInterface(appId, parent)
26 : ApplicationInfoInterface(appId, parent)
@@ -32,6 +33,7 @@ ApplicationInfo::ApplicationInfo(const QString &appId, QObject *parent)
32 ,m_windowComponent(0)
33 ,m_windowComponent(0)
33 ,m_parentItem(0)
34 ,m_parentItem(0)
34{
35{
36+ QTimer::singleShot(300, this, SLOT(setRunning()));
35}
37}
36
38
37ApplicationInfo::ApplicationInfo(QObject *parent)
39ApplicationInfo::ApplicationInfo(QObject *parent)
@@ -44,6 +46,7 @@ ApplicationInfo::ApplicationInfo(QObject *parent)
44 ,m_windowComponent(0)
46 ,m_windowComponent(0)
45 ,m_parentItem(0)
47 ,m_parentItem(0)
46{
48{
49+ QTimer::singleShot(300, this, SLOT(setRunning()));
47}
50}
48
51
49void ApplicationInfo::onWindowComponentStatusChanged(QQmlComponent::Status status)
52void ApplicationInfo::onWindowComponentStatusChanged(QQmlComponent::Status status)
@@ -52,6 +55,12 @@ void ApplicationInfo::onWindowComponentStatusChanged(QQmlComponent::Status statu
52 doCreateWindowItem();
55 doCreateWindowItem();
53}
56}
54
57
58+void ApplicationInfo::setRunning()
59+{
60+ m_state = Running;
61+ Q_EMIT stateChanged();
62+}
63+
55void ApplicationInfo::createWindowComponent()
64void ApplicationInfo::createWindowComponent()
56{
65{
57 // The assumptions I make here really should hold.
66 // The assumptions I make here really should hold.
tests/mocks/Unity/Application/ApplicationInfo.h+2
@@ -74,6 +74,7 @@ class ApplicationInfo : public ApplicationInfoInterface {
74 IMPLEMENT_PROPERTY(fullscreen, Fullscreen, bool)
74 IMPLEMENT_PROPERTY(fullscreen, Fullscreen, bool)
75 IMPLEMENT_PROPERTY(imageQml, ImageQml, QString)
75 IMPLEMENT_PROPERTY(imageQml, ImageQml, QString)
76 IMPLEMENT_PROPERTY(windowQml, WindowQml, QString)
76 IMPLEMENT_PROPERTY(windowQml, WindowQml, QString)
77+ IMPLEMENT_PROPERTY(screenshot, Screenshot, QUrl)
77
78
78 #undef IMPLEMENT_PROPERTY
79 #undef IMPLEMENT_PROPERTY
79
80
@@ -83,6 +84,7 @@ class ApplicationInfo : public ApplicationInfoInterface {
83
84
84 private Q_SLOTS:
85 private Q_SLOTS:
85 void onWindowComponentStatusChanged(QQmlComponent::Status status);
86 void onWindowComponentStatusChanged(QQmlComponent::Status status);
87+ void setRunning();
86
88
87 private:
89 private:
88 void createWindowItem();
90 void createWindowItem();
tests/mocks/Unity/Application/ApplicationManager.cpp+109−3
@@ -24,13 +24,17 @@
24#include <QQuickItem>
24#include <QQuickItem>
25#include <QQuickView>
25#include <QQuickView>
26#include <QQmlComponent>
26#include <QQmlComponent>
27+#include <QTimer>
28+#include <QDateTime>
27
29
28ApplicationManager::ApplicationManager(QObject *parent)
30ApplicationManager::ApplicationManager(QObject *parent)
29 : ApplicationManagerInterface(parent)
31 : ApplicationManagerInterface(parent)
32+ , m_suspended(false)
30 , m_mainStageComponent(0)
33 , m_mainStageComponent(0)
31 , m_mainStage(0)
34 , m_mainStage(0)
32 , m_sideStageComponent(0)
35 , m_sideStageComponent(0)
33 , m_sideStage(0)
36 , m_sideStage(0)
37+ , m_rightMargin(0)
34{
38{
35 buildListOfAvailableApplications();
39 buildListOfAvailableApplications();
36}
40}
@@ -63,6 +67,8 @@ QVariant ApplicationManager::data(const QModelIndex& index, int role) const {
63 return app->state();
67 return app->state();
64 case RoleFocused:
68 case RoleFocused:
65 return app->focused();
69 return app->focused();
70+ case RoleScreenshot:
71+ return app->screenshot();
66 default:
72 default:
67 return QVariant();
73 return QVariant();
68 }
74 }
@@ -84,13 +90,16 @@ ApplicationInfo *ApplicationManager::findApplication(const QString &appId) const
84}
90}
85
91
86void ApplicationManager::add(ApplicationInfo *application) {
92void ApplicationManager::add(ApplicationInfo *application) {
87− if (!application)
93+ if (!application) {
88 return;
94 return;
95+ }
89
96
90 beginInsertRows(QModelIndex(), m_runningApplications.size(), m_runningApplications.size());
97 beginInsertRows(QModelIndex(), m_runningApplications.size(), m_runningApplications.size());
91 m_runningApplications.append(application);
98 m_runningApplications.append(application);
92 endInsertRows();
99 endInsertRows();
100+ Q_EMIT applicationAdded(application->appId());
93 Q_EMIT countChanged();
101 Q_EMIT countChanged();
102+ Q_EMIT focusRequested(application->appId());
94}
103}
95
104
96void ApplicationManager::remove(ApplicationInfo *application) {
105void ApplicationManager::remove(ApplicationInfo *application) {
@@ -99,6 +108,7 @@ void ApplicationManager::remove(ApplicationInfo *application) {
99 beginRemoveRows(QModelIndex(), i, i);
108 beginRemoveRows(QModelIndex(), i, i);
100 m_runningApplications.removeAt(i);
109 m_runningApplications.removeAt(i);
101 endRemoveRows();
110 endRemoveRows();
111+ Q_EMIT applicationRemoved(application->appId());
102 Q_EMIT countChanged();
112 Q_EMIT countChanged();
103 }
113 }
104}
114}
@@ -171,6 +181,8 @@ ApplicationInfo* ApplicationManager::startApplication(const QString &appId,
171 }
181 }
172 add(application);
182 add(application);
173
183
184+ QMetaObject::invokeMethod(this, "focusApplication", Qt::QueuedConnection, Q_ARG(QString, appId));
185+
174 return application;
186 return application;
175}
187}
176
188
@@ -180,8 +192,32 @@ bool ApplicationManager::stopApplication(const QString &appId)
180 if (application == nullptr)
192 if (application == nullptr)
181 return false;
193 return false;
182
194
195+ if (application->appId() == focusedApplicationId()) {
196+ unfocusCurrentApplication();
197+ }
183 remove(application);
198 remove(application);
184− Q_EMIT focusedApplicationIdChanged();
199+ return true;
200+}
201+
202+bool ApplicationManager::updateScreenshot(const QString &appId)
203+{
204+ int idx = -1;
205+ ApplicationInfo *application = nullptr;
206+ for (int i = 0; i < m_availableApplications.count(); ++i) {
207+ application = m_availableApplications.at(i);
208+ if (application->appId() == appId) {
209+ idx = i;
210+ break;
211+ }
212+ }
213+
214+ if (idx == -1) {
215+ return false;
216+ }
217+
218+ application->setScreenshot(QString("image://application/%1/%2").arg(appId).arg(QDateTime::currentMSecsSinceEpoch()));
219+ QModelIndex appIndex = index(idx);
220+ Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << RoleScreenshot);
185 return true;
221 return true;
186}
222}
187
223
@@ -194,6 +230,25 @@ QString ApplicationManager::focusedApplicationId() const {
194 return QString();
230 return QString();
195}
231}
196
232
233+bool ApplicationManager::suspended() const
234+{
235+ return m_suspended;
236+}
237+
238+void ApplicationManager::setSuspended(bool suspended)
239+{
240+ ApplicationInfo *focusedApp = findApplication(focusedApplicationId());
241+ if (focusedApp) {
242+ if (suspended) {
243+ focusedApp->setState(ApplicationInfo::Suspended);
244+ } else {
245+ focusedApp->setState(ApplicationInfo::Running);
246+ }
247+ }
248+ m_suspended = suspended;
249+ Q_EMIT suspendedChanged();
250+}
251+
197bool ApplicationManager::focusApplication(const QString &appId)
252bool ApplicationManager::focusApplication(const QString &appId)
198{
253{
199 ApplicationInfo *application = findApplication(appId);
254 ApplicationInfo *application = findApplication(appId);
@@ -242,6 +297,15 @@ bool ApplicationManager::focusApplication(const QString &appId)
242 return true;
297 return true;
243}
298}
244
299
300+bool ApplicationManager::requestFocusApplication(const QString &appId)
301+{
302+ if (appId != focusedApplicationId()) {
303+ QMetaObject::invokeMethod(this, "focusRequested", Qt::QueuedConnection, Q_ARG(QString, appId));
304+ return true;
305+ }
306+ return false;
307+}
308+
245void ApplicationManager::unfocusCurrentApplication()
309void ApplicationManager::unfocusCurrentApplication()
246{
310{
247 for (ApplicationInfo *app : m_runningApplications) {
311 for (ApplicationInfo *app : m_runningApplications) {
@@ -270,10 +334,12 @@ void ApplicationManager::generateQmlStrings(ApplicationInfo *application)
270 "Image {\n"
334 "Image {\n"
271 " anchors.fill: parent\n"
335 " anchors.fill: parent\n"
272 " anchors.topMargin: %1\n"
336 " anchors.topMargin: %1\n"
337+ " anchors.rightMargin: %2\n"
273− " source: \"file://%2/Dash/graphics/phone/screenshots/%3.png\"\n"
338+ " source: \"file://%3/Dash/graphics/phone/screenshots/%4.png\"\n"
274 " smooth: true\n"
339 " smooth: true\n"
275 " fillMode: Image.PreserveAspectCrop\n"
340 " fillMode: Image.PreserveAspectCrop\n"
276 "}").arg(topMargin)
341 "}").arg(topMargin)
342+ .arg(m_rightMargin)
277 .arg(qmlDirectory())
343 .arg(qmlDirectory())
278 .arg(application->icon().toString());
344 .arg(application->icon().toString());
279 application->setWindowQml(windowQml);
345 application->setWindowQml(windowQml);
@@ -299,6 +365,7 @@ void ApplicationManager::buildListOfAvailableApplications()
299 application->setName("Phone");
365 application->setName("Phone");
300 application->setIcon(QUrl("phone"));
366 application->setIcon(QUrl("phone"));
301 application->setStage(ApplicationInfo::SideStage);
367 application->setStage(ApplicationInfo::SideStage);
368+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
302 generateQmlStrings(application);
369 generateQmlStrings(application);
303 m_availableApplications.append(application);
370 m_availableApplications.append(application);
304
371
@@ -307,6 +374,7 @@ void ApplicationManager::buildListOfAvailableApplications()
307 application->setName("Camera");
374 application->setName("Camera");
308 application->setIcon(QUrl("camera"));
375 application->setIcon(QUrl("camera"));
309 application->setFullscreen(true);
376 application->setFullscreen(true);
377+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
310 generateQmlStrings(application);
378 generateQmlStrings(application);
311 m_availableApplications.append(application);
379 m_availableApplications.append(application);
312
380
@@ -314,6 +382,7 @@ void ApplicationManager::buildListOfAvailableApplications()
314 application->setAppId("gallery-app");
382 application->setAppId("gallery-app");
315 application->setName("Gallery");
383 application->setName("Gallery");
316 application->setIcon(QUrl("gallery"));
384 application->setIcon(QUrl("gallery"));
385+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
317 generateQmlStrings(application);
386 generateQmlStrings(application);
318 m_availableApplications.append(application);
387 m_availableApplications.append(application);
319
388
@@ -322,13 +391,16 @@ void ApplicationManager::buildListOfAvailableApplications()
322 application->setName("Facebook");
391 application->setName("Facebook");
323 application->setIcon(QUrl("facebook"));
392 application->setIcon(QUrl("facebook"));
324 application->setStage(ApplicationInfo::SideStage);
393 application->setStage(ApplicationInfo::SideStage);
394+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
325 generateQmlStrings(application);
395 generateQmlStrings(application);
326 m_availableApplications.append(application);
396 m_availableApplications.append(application);
327
397
328 application = new ApplicationInfo(this);
398 application = new ApplicationInfo(this);
329 application->setAppId("webbrowser-app");
399 application->setAppId("webbrowser-app");
400+ application->setFullscreen(true);
330 application->setName("Browser");
401 application->setName("Browser");
331 application->setIcon(QUrl("browser"));
402 application->setIcon(QUrl("browser"));
403+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
332 generateQmlStrings(application);
404 generateQmlStrings(application);
333 m_availableApplications.append(application);
405 m_availableApplications.append(application);
334
406
@@ -337,6 +409,7 @@ void ApplicationManager::buildListOfAvailableApplications()
337 application->setName("Twitter");
409 application->setName("Twitter");
338 application->setIcon(QUrl("twitter"));
410 application->setIcon(QUrl("twitter"));
339 application->setStage(ApplicationInfo::SideStage);
411 application->setStage(ApplicationInfo::SideStage);
412+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
340 generateQmlStrings(application);
413 generateQmlStrings(application);
341 m_availableApplications.append(application);
414 m_availableApplications.append(application);
342
415
@@ -344,6 +417,7 @@ void ApplicationManager::buildListOfAvailableApplications()
344 application->setAppId("gmail-webapp");
417 application->setAppId("gmail-webapp");
345 application->setName("GMail");
418 application->setName("GMail");
346 application->setIcon(QUrl("gmail"));
419 application->setIcon(QUrl("gmail"));
420+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
347 m_availableApplications.append(application);
421 m_availableApplications.append(application);
348
422
349 application = new ApplicationInfo(this);
423 application = new ApplicationInfo(this);
@@ -351,6 +425,7 @@ void ApplicationManager::buildListOfAvailableApplications()
351 application->setName("Weather");
425 application->setName("Weather");
352 application->setIcon(QUrl("weather"));
426 application->setIcon(QUrl("weather"));
353 application->setStage(ApplicationInfo::SideStage);
427 application->setStage(ApplicationInfo::SideStage);
428+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
354 generateQmlStrings(application);
429 generateQmlStrings(application);
355 m_availableApplications.append(application);
430 m_availableApplications.append(application);
356
431
@@ -359,6 +434,7 @@ void ApplicationManager::buildListOfAvailableApplications()
359 application->setName("Notepad");
434 application->setName("Notepad");
360 application->setIcon(QUrl("notepad"));
435 application->setIcon(QUrl("notepad"));
361 application->setStage(ApplicationInfo::SideStage);
436 application->setStage(ApplicationInfo::SideStage);
437+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
362 m_availableApplications.append(application);
438 m_availableApplications.append(application);
363
439
364 application = new ApplicationInfo(this);
440 application = new ApplicationInfo(this);
@@ -366,6 +442,7 @@ void ApplicationManager::buildListOfAvailableApplications()
366 application->setName("Calendar");
442 application->setName("Calendar");
367 application->setIcon(QUrl("calendar"));
443 application->setIcon(QUrl("calendar"));
368 application->setStage(ApplicationInfo::SideStage);
444 application->setStage(ApplicationInfo::SideStage);
445+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
369 m_availableApplications.append(application);
446 m_availableApplications.append(application);
370
447
371 application = new ApplicationInfo(this);
448 application = new ApplicationInfo(this);
@@ -373,18 +450,21 @@ void ApplicationManager::buildListOfAvailableApplications()
373 application->setName("Media Player");
450 application->setName("Media Player");
374 application->setIcon(QUrl("mediaplayer-app"));
451 application->setIcon(QUrl("mediaplayer-app"));
375 application->setFullscreen(true);
452 application->setFullscreen(true);
453+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
376 m_availableApplications.append(application);
454 m_availableApplications.append(application);
377
455
378 application = new ApplicationInfo(this);
456 application = new ApplicationInfo(this);
379 application->setAppId("evernote");
457 application->setAppId("evernote");
380 application->setName("Evernote");
458 application->setName("Evernote");
381 application->setIcon(QUrl("evernote"));
459 application->setIcon(QUrl("evernote"));
460+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
382 m_availableApplications.append(application);
461 m_availableApplications.append(application);
383
462
384 application = new ApplicationInfo(this);
463 application = new ApplicationInfo(this);
385 application->setAppId("map");
464 application->setAppId("map");
386 application->setName("Map");
465 application->setName("Map");
387 application->setIcon(QUrl("map"));
466 application->setIcon(QUrl("map"));
467+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
388 generateQmlStrings(application);
468 generateQmlStrings(application);
389 m_availableApplications.append(application);
469 m_availableApplications.append(application);
390
470
@@ -392,24 +472,28 @@ void ApplicationManager::buildListOfAvailableApplications()
392 application->setAppId("pinterest");
472 application->setAppId("pinterest");
393 application->setName("Pinterest");
473 application->setName("Pinterest");
394 application->setIcon(QUrl("pinterest"));
474 application->setIcon(QUrl("pinterest"));
475+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
395 m_availableApplications.append(application);
476 m_availableApplications.append(application);
396
477
397 application = new ApplicationInfo(this);
478 application = new ApplicationInfo(this);
398 application->setAppId("soundcloud");
479 application->setAppId("soundcloud");
399 application->setName("SoundCloud");
480 application->setName("SoundCloud");
400 application->setIcon(QUrl("soundcloud"));
481 application->setIcon(QUrl("soundcloud"));
482+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
401 m_availableApplications.append(application);
483 m_availableApplications.append(application);
402
484
403 application = new ApplicationInfo(this);
485 application = new ApplicationInfo(this);
404 application->setAppId("wikipedia");
486 application->setAppId("wikipedia");
405 application->setName("Wikipedia");
487 application->setName("Wikipedia");
406 application->setIcon(QUrl("wikipedia"));
488 application->setIcon(QUrl("wikipedia"));
489+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
407 m_availableApplications.append(application);
490 m_availableApplications.append(application);
408
491
409 application = new ApplicationInfo(this);
492 application = new ApplicationInfo(this);
410 application->setAppId("youtube");
493 application->setAppId("youtube");
411 application->setName("YouTube");
494 application->setName("YouTube");
412 application->setIcon(QUrl("youtube"));
495 application->setIcon(QUrl("youtube"));
496+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
413 m_availableApplications.append(application);
497 m_availableApplications.append(application);
414}
498}
415
499
@@ -484,3 +568,25 @@ void ApplicationManager::createSideStage()
484 m_sideStage->setParentItem(shell);
568 m_sideStage->setParentItem(shell);
485 m_sideStage->setFlag(QQuickItem::ItemHasContents, false);
569 m_sideStage->setFlag(QQuickItem::ItemHasContents, false);
486}
570}
571+
572+QStringList ApplicationManager::availableApplications()
573+{
574+ QStringList appIds;
575+ Q_FOREACH(ApplicationInfo *app, m_availableApplications) {
576+ appIds << app->appId();
577+ }
578+ return appIds;
579+}
580+
581+int ApplicationManager::rightMargin() const
582+{
583+ return m_rightMargin;
584+}
585+
586+void ApplicationManager::setRightMargin(int rightMargin)
587+{
588+ m_rightMargin = rightMargin;
589+ Q_FOREACH(ApplicationInfo *app, m_availableApplications) {
590+ generateQmlStrings(app);
591+ }
592+}
tests/mocks/Unity/Application/ApplicationManager.h+16
@@ -45,6 +45,10 @@ class ApplicationManager : public ApplicationManagerInterface {
45
45
46 Q_PROPERTY(bool fake READ fake CONSTANT)
46 Q_PROPERTY(bool fake READ fake CONSTANT)
47
47
48+ // Only for testing
49+ // This can be used to place some controls to right, like make tryPhoneStage for example
50+ Q_PROPERTY(int rightMargin READ rightMargin WRITE setRightMargin)
51+
48 public:
52 public:
49 ApplicationManager(QObject *parent = NULL);
53 ApplicationManager(QObject *parent = NULL);
50 virtual ~ApplicationManager();
54 virtual ~ApplicationManager();
@@ -87,13 +91,22 @@ class ApplicationManager : public ApplicationManagerInterface {
87 Q_INVOKABLE void move(int from, int to);
91 Q_INVOKABLE void move(int from, int to);
88
92
89 // Application control methods
93 // Application control methods
94+ Q_INVOKABLE bool requestFocusApplication(const QString &appId) override;
90 Q_INVOKABLE bool focusApplication(const QString &appId) override;
95 Q_INVOKABLE bool focusApplication(const QString &appId) override;
91 Q_INVOKABLE void unfocusCurrentApplication() override;
96 Q_INVOKABLE void unfocusCurrentApplication() override;
92 Q_INVOKABLE ApplicationInfo *startApplication(const QString &appId, const QStringList &arguments = QStringList()) override;
97 Q_INVOKABLE ApplicationInfo *startApplication(const QString &appId, const QStringList &arguments = QStringList()) override;
93 Q_INVOKABLE ApplicationInfo *startApplication(const QString &appId, ExecFlags flags, const QStringList &arguments = QStringList());
98 Q_INVOKABLE ApplicationInfo *startApplication(const QString &appId, ExecFlags flags, const QStringList &arguments = QStringList());
94 Q_INVOKABLE bool stopApplication(const QString &appId) override;
99 Q_INVOKABLE bool stopApplication(const QString &appId) override;
100+ Q_INVOKABLE bool updateScreenshot(const QString &appId) override;
95
101
96 QString focusedApplicationId() const override;
102 QString focusedApplicationId() const override;
103+ bool suspended() const;
104+ void setSuspended(bool suspended);
105+
106+ // Only for testing
107+ Q_INVOKABLE QStringList availableApplications();
108+ int rightMargin() const;
109+ void setRightMargin(int rightMargin);
97
110
98 Q_SIGNALS:
111 Q_SIGNALS:
99 void keyboardHeightChanged();
112 void keyboardHeightChanged();
@@ -113,12 +126,15 @@ class ApplicationManager : public ApplicationManagerInterface {
113 void createSideStage();
126 void createSideStage();
114 int m_keyboardHeight;
127 int m_keyboardHeight;
115 bool m_keyboardVisible;
128 bool m_keyboardVisible;
129+ bool m_suspended;
116 QList<ApplicationInfo*> m_runningApplications;
130 QList<ApplicationInfo*> m_runningApplications;
117 QList<ApplicationInfo*> m_availableApplications;
131 QList<ApplicationInfo*> m_availableApplications;
118 QQmlComponent *m_mainStageComponent;
132 QQmlComponent *m_mainStageComponent;
119 QQuickItem *m_mainStage;
133 QQuickItem *m_mainStage;
120 QQmlComponent *m_sideStageComponent;
134 QQmlComponent *m_sideStageComponent;
121 QQuickItem *m_sideStage;
135 QQuickItem *m_sideStage;
136+
137+ int m_rightMargin;
122};
138};
123
139
124Q_DECLARE_OPERATORS_FOR_FLAGS(ApplicationManager::ExecFlags)
140Q_DECLARE_OPERATORS_FOR_FLAGS(ApplicationManager::ExecFlags)
tests/mocks/Unity/Application/ApplicationScreenshotProvider.cppnew file+77
@@ -0,0 +1,77 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it under
5+ * the terms of the GNU Lesser General Public License version 3, as published by
6+ * the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but WITHOUT
9+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+ * Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "ApplicationScreenshotProvider.h"
18+#include "ApplicationManager.h"
19+#include "ApplicationInfo.h"
20+
21+#include "paths.h"
22+
23+#include <QDebug>
24+#include <QGuiApplication>
25+#include <QWindow>
26+#include <QQuickWindow>
27+
28+ApplicationScreenshotProvider::ApplicationScreenshotProvider(ApplicationManager *appManager)
29+ : QQuickImageProvider(QQuickImageProvider::Image)
30+ , m_appManager(appManager)
31+{
32+}
33+
34+QImage ApplicationScreenshotProvider::requestImage(const QString &imageId, QSize * size,
35+ const QSize &requestedSize)
36+{
37+ // We ignore requestedSize here intentionally to avoid keeping scaled copies around
38+ Q_UNUSED(requestedSize)
39+
40+ QString appId = imageId.split('/').first();
41+
42+ ApplicationInfo* app = static_cast<ApplicationInfo*>(m_appManager->findApplication(appId));
43+ if (app == nullptr) {
44+ return QImage();
45+ }
46+
47+ QString filePath = QString("%1/Dash/graphics/phone/screenshots/%2@12.png").arg(qmlDirectory()).arg(app->icon().toString());
48+
49+ QImage image;
50+ if (!image.load(filePath)) {
51+ qWarning() << "failed loading app image" << filePath;
52+ }
53+
54+
55+ if (app->stage() == ApplicationInfo::SideStage) {
56+ QByteArray gus = qgetenv("GRID_UNIT_PX");
57+ if (gus.isEmpty() || gus.toInt() == 0) {
58+ gus = "8";
59+ }
60+ image = image.scaledToWidth(gus.toInt() * 48);
61+ } else {
62+ // Lets scale main stage applications to be the size of the screen/window.
63+ QGuiApplication *unity = qobject_cast<QGuiApplication*>(qApp);
64+ Q_FOREACH (QWindow *win, unity->allWindows()) {
65+ QQuickWindow *quickWin = qobject_cast<QQuickWindow*>(win);
66+ if (quickWin) {
67+ image = image.scaledToWidth(quickWin->width() - m_appManager->rightMargin());
68+ break;
69+ }
70+ }
71+ }
72+
73+ size->setWidth(image.width());
74+ size->setHeight(image.height());
75+
76+ return image;
77+}
tests/mocks/Unity/Application/ApplicationScreenshotProvider.hnew file+34
@@ -0,0 +1,34 @@
1+/*
2+ * Copyright (C) 201 Canonical, Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it under
5+ * the terms of the GNU Lesser General Public License version 3, as published by
6+ * the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but WITHOUT
9+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+ * Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef APPLICATIONSCREENSHOTPROVIDER_H
18+#define APPLICATIONSCREENSHOTPROVIDER_H
19+
20+#include <QQuickImageProvider>
21+
22+class ApplicationManager;
23+class ApplicationScreenshotProvider : public QQuickImageProvider
24+{
25+public:
26+ explicit ApplicationScreenshotProvider(ApplicationManager *appManager);
27+
28+ QImage requestImage(const QString &appId, QSize *size, const QSize &requestedSize) override;
29+
30+private:
31+ ApplicationManager* m_appManager;
32+};
33+
34+#endif // APPLICATIONSCREENSHOTPROVIDER_H
tests/mocks/Unity/Application/CMakeLists.txt+15
@@ -5,6 +5,7 @@ set(FakeUnityApplicationQml_SOURCES
5 ApplicationInfo.cpp
5 ApplicationInfo.cpp
6 ApplicationImage.cpp
6 ApplicationImage.cpp
7 ApplicationManager.cpp
7 ApplicationManager.cpp
8+ ApplicationScreenshotProvider.cpp
8 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
9 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
9 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
10 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
10)
11)
@@ -34,3 +35,17 @@ install(TARGETS FakeUnityApplicationQml
34install(FILES qmldir InputFilterArea.qml OSKController.qml
35install(FILES qmldir InputFilterArea.qml OSKController.qml
35 DESTINATION ${SHELL_INSTALL_QML}/mocks/Unity/Application
36 DESTINATION ${SHELL_INSTALL_QML}/mocks/Unity/Application
36 )
37 )
38+
39+# Duplicate the mock to nonmirplugins
40+# In shadow builds we just link it, in real installs we also install it to the other path
41+add_custom_target(OSKControllerQmlFile2 ALL
42+ COMMAND mkdir -p ${CMAKE_BINARY_DIR}/nonmirplugins/Unity && rm -f ${CMAKE_BINARY_DIR}/nonmirplugins/Unity/Application && ln -s ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_BINARY_DIR}/nonmirplugins/Unity
43+ DEPENDS qmldir
44+)
45+install(TARGETS FakeUnityApplicationQml
46+ DESTINATION ${SHELL_INSTALL_QML}/nonmirplugins/Unity/Application
47+ )
48+
49+install(FILES qmldir InputFilterArea.qml OSKController.qml
50+ DESTINATION ${SHELL_INSTALL_QML}/nonmirplugins/Unity/Application
51+ )
tests/mocks/Unity/Application/plugin.cpp+18−3
@@ -18,13 +18,20 @@
18#include "ApplicationInfo.h"
18#include "ApplicationInfo.h"
19#include "ApplicationImage.h"
19#include "ApplicationImage.h"
20#include "ApplicationManager.h"
20#include "ApplicationManager.h"
21+#include "ApplicationScreenshotProvider.h"
21
22
22#include <qqml.h>
23#include <qqml.h>
24+#include <QQmlEngine>
25+
26+ApplicationManager *s_appManager = 0;
23
27
24static QObject* applicationManagerSingleton(QQmlEngine* engine, QJSEngine* scriptEngine) {
28static QObject* applicationManagerSingleton(QQmlEngine* engine, QJSEngine* scriptEngine) {
25− Q_UNUSED(engine);
29+ Q_UNUSED(engine);
26− Q_UNUSED(scriptEngine);
30+ Q_UNUSED(scriptEngine);
31+ if (!s_appManager) {
27− return new ApplicationManager();
32+ s_appManager = new ApplicationManager();
33+ }
34+ return s_appManager;
28}
35}
29
36
30void FakeUnityApplicationQmlPlugin::registerTypes(const char *uri)
37void FakeUnityApplicationQmlPlugin::registerTypes(const char *uri)
@@ -37,3 +44,11 @@ void FakeUnityApplicationQmlPlugin::registerTypes(const char *uri)
37
44
38 qmlRegisterType<ApplicationImage>(uri, 0, 1, "ApplicationImage");
45 qmlRegisterType<ApplicationImage>(uri, 0, 1, "ApplicationImage");
39}
46}
47+
48+void FakeUnityApplicationQmlPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
49+{
50+ QQmlExtensionPlugin::initializeEngine(engine, uri);
51+
52+ ApplicationManager* appManager = static_cast<ApplicationManager*>(applicationManagerSingleton(engine, NULL));
53+ engine->addImageProvider(QLatin1String("application"), new ApplicationScreenshotProvider(appManager));
54+}
tests/mocks/Unity/Application/plugin.h+1
@@ -25,6 +25,7 @@ class FakeUnityApplicationQmlPlugin : public QQmlExtensionPlugin
25 Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
25 Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
26public:
26public:
27 void registerTypes(const char *uri);
27 void registerTypes(const char *uri);
28+ void initializeEngine(QQmlEngine *engine, const char *uri);
28};
29};
29
30
30#endif
31#endif
tests/plugins/DashViews/listviewwithpageheadertest.cpp−1
@@ -1855,7 +1855,6 @@ private Q_SLOTS:
1855 scrollToBottom();
1855 scrollToBottom();
1856 lvwph->showHeader();
1856 lvwph->showHeader();
1857 QTRY_VERIFY(!lvwph->m_contentYAnimation->isRunning());
1857 QTRY_VERIFY(!lvwph->m_contentYAnimation->isRunning());
1858− QTest::qWait(100); // Make sure stuff is stable
1859 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 100));
1858 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 100));
1860 model->setProperty(3, "size", 10);
1859 model->setProperty(3, "size", 10);
1861 model->setProperty(4, "size", 10);
1860 model->setProperty(4, "size", 10);
tests/plugins/DashViews/listviewwithpageheadertestsection.cpp+3−3
@@ -1680,12 +1680,11 @@ private Q_SLOTS:
1680 verifyItem(18, 700, 50., false, QString(), true);
1680 verifyItem(18, 700, 50., false, QString(), true);
1681 verifyItem(19, 750, 50., false, QString(), true);
1681 verifyItem(19, 750, 50., false, QString(), true);
1682 verifyItem(20, 800, 50., false, QString(), true);
1682 verifyItem(20, 800, 50., false, QString(), true);
1683+ QCOMPARE(lvwph->m_clipItem->y(), lvwph->contentY());
1683− QCOMPARE(lvwph->m_minYExtent, 5 * 1510./21. + 660 - 1970 + 50);
1684+ QCOMPARE(lvwph->m_minYExtent, 5 * 1510./21. + 660 - lvwph->contentY() + 50);
1684− QCOMPARE(lvwph->m_clipItem->y(), 1970.);
1685 QCOMPARE(lvwph->m_clipItem->clip(), false);
1685 QCOMPARE(lvwph->m_clipItem->clip(), false);
1686 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1686 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1687 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1687 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1688− QCOMPARE(lvwph->contentY(), 1970.);
1689 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1688 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1690 }
1689 }
1691
1690
@@ -2108,6 +2107,7 @@ private Q_SLOTS:
2108 QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
2107 QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
2109
2108
2110 QTest::qWait(1);
2109 QTest::qWait(1);
2110+ QCoreApplication::instance()->processEvents();
2111
2111
2112 changeContentY(-15);
2112 changeContentY(-15);
2113
2113
tests/plugins/Ubuntu/Gestures/CMakeLists.txt+2−11
@@ -15,20 +15,11 @@ add_custom_target(UbuntuGesturesTestQmlFiles ALL
15add_definitions(-DUBUNTU_GESTURES_PLUGIN_DIR="${CMAKE_BINARY_DIR}/plugins")
15add_definitions(-DUBUNTU_GESTURES_PLUGIN_DIR="${CMAKE_BINARY_DIR}/plugins")
16
16
17macro(add_gesture_ui_test CLASSNAME)
17macro(add_gesture_ui_test CLASSNAME)
18− set(testCommand
19− LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/plugins/Ubuntu/Gestures
20− ${CMAKE_CURRENT_BINARY_DIR}/${CLASSNAME}TestExec
21− -o ${CMAKE_BINARY_DIR}/${CLASSNAME}Test.xml,xunitxml
22− -o -,txt)
23−
24− add_custom_target(test${CLASSNAME} ${testCommand})
25− add_dependencies(qmluitests test${CLASSNAME})
26− add_dependencies(test${CLASSNAME} ${CLASSNAME}TestExec UbuntuGesturesTestQmlFiles)
27−
28 add_executable(${CLASSNAME}TestExec tst_${CLASSNAME}.cpp GestureTest.cpp)
18 add_executable(${CLASSNAME}TestExec tst_${CLASSNAME}.cpp GestureTest.cpp)
29 qt5_use_modules(${CLASSNAME}TestExec Test Core Qml Gui Quick)
19 qt5_use_modules(${CLASSNAME}TestExec Test Core Qml Gui Quick)
30 target_link_libraries(${CLASSNAME}TestExec UbuntuGestureQml)
20 target_link_libraries(${CLASSNAME}TestExec UbuntuGestureQml)
21+
31− add_manual_qml_test(. ${CLASSNAME} IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
22+ add_binary_qml_test(${CLASSNAME} ${CMAKE_BINARY_DIR}/plugins/Ubuntu/Gestures UbuntuGesturesTestQmlFiles)
32endmacro(add_gesture_ui_test)
23endmacro(add_gesture_ui_test)
33
24
34macro(add_gesture_test CLASSNAME)
25macro(add_gesture_test CLASSNAME)
tests/plugins/Unity/Launcher/launchermodeltest.cpp+5
@@ -39,6 +39,7 @@ public:
39 ApplicationInfoInterface::Stage stage() const { return ApplicationInfoInterface::MainStage; }
39 ApplicationInfoInterface::Stage stage() const { return ApplicationInfoInterface::MainStage; }
40 ApplicationInfoInterface::State state() const { return ApplicationInfoInterface::Running; }
40 ApplicationInfoInterface::State state() const { return ApplicationInfoInterface::Running; }
41 bool focused() const { return m_focused; }
41 bool focused() const { return m_focused; }
42+ QUrl screenshot() const { return QUrl(); }
42
43
43 // Methods used for mocking (not in the interface)
44 // Methods used for mocking (not in the interface)
44 void setFocused(bool focused) { m_focused = focused; Q_EMIT focusedChanged(focused); }
45 void setFocused(bool focused) { m_focused = focused; Q_EMIT focusedChanged(focused); }
@@ -92,6 +93,10 @@ public:
92 m_list.takeAt(index)->deleteLater();
93 m_list.takeAt(index)->deleteLater();
93 endRemoveRows();
94 endRemoveRows();
94 }
95 }
96+ bool updateScreenshot(const QString &appId) { Q_UNUSED(appId); return true; }
97+ bool requestFocusApplication(const QString &appId) { Q_UNUSED(appId); return true; }
98+ bool suspended() const { return false; }
99+ void setSuspended(bool) {}
95
100
96private:
101private:
97 QList<MockApp*> m_list;
102 QList<MockApp*> m_list;
tests/qmltests/CMakeLists.txt+3−7
@@ -11,8 +11,6 @@ set(qmltest_DEFAULT_IMPORT_PATHS
11set(qmltest_DEFAULT_TARGETS qmlunittests)
11set(qmltest_DEFAULT_TARGETS qmlunittests)
12set(qmltest_DEFAULT_NO_ADD_TEST FALSE)
12set(qmltest_DEFAULT_NO_ADD_TEST FALSE)
13set(qmltest_DEFAULT_PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=minimal")
13set(qmltest_DEFAULT_PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=minimal")
14−add_qml_test(Components AnimationControllerWithSignals)
15−add_qml_test(Components Carousel)
16add_qml_test(Panel IndicatorItem)
14add_qml_test(Panel IndicatorItem)
17add_qml_test(utils/Unity/Test UnityTest)
15add_qml_test(utils/Unity/Test UnityTest)
18
16
@@ -22,7 +20,7 @@ set(qmltest_DEFAULT_PROPERTIES "")
22
20
23add_qml_test(. Shell IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/tests/mocks
21add_qml_test(. Shell IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/tests/mocks
24 ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/LightDM/single")
22 ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/LightDM/single")
25−add_manual_qml_test(Components DragHandle IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
23+add_qml_test(Components Carousel)
26add_qml_test(Components DraggingArea)
24add_qml_test(Components DraggingArea)
27add_qml_test(Components EdgeDemoOverlay)
25add_qml_test(Components EdgeDemoOverlay)
28add_qml_test(Components FilterGrid IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/plugins)
26add_qml_test(Components FilterGrid IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/plugins)
@@ -33,14 +31,12 @@ add_qml_test(Components ResponsiveGridView)
33add_qml_test(Components Revealer)
31add_qml_test(Components Revealer)
34add_qml_test(Components SeeMore)
32add_qml_test(Components SeeMore)
35add_qml_test(Components Showable)
33add_qml_test(Components Showable)
36−add_qml_test(Components Stage IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS}
37− ${CMAKE_BINARY_DIR}/tests/mocks
38− ${CMAKE_BINARY_DIR}/plugins)
39add_qml_test(Components PageHeaderLabel)
34add_qml_test(Components PageHeaderLabel)
40add_qml_test(Components ZoomableImage)
35add_qml_test(Components ZoomableImage)
41add_qml_test(Dash Dash IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
36add_qml_test(Dash Dash IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
42add_qml_test(Dash DashContent IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
37add_qml_test(Dash DashContent IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
43add_qml_test(Dash Card)
38add_qml_test(Dash Card)
39+add_qml_benchmark(Dash CardBenchmark 1000)
44add_qml_test(Dash CardHeader)
40add_qml_test(Dash CardHeader)
45add_qml_test(Dash CardTool)
41add_qml_test(Dash CardTool)
46add_qml_test(Dash GenericScopeView IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
42add_qml_test(Dash GenericScopeView IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
@@ -85,6 +81,6 @@ add_qml_test(Panel/Indicators DefaultIndicatorPage IMPORT_PATHS ${CMAKE_BINARY_D
85 ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/QMenuModel")
81 ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/QMenuModel")
86add_qml_test(Panel/Indicators MenuItemFactory IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/plugins)
82add_qml_test(Panel/Indicators MenuItemFactory IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/plugins)
87add_qml_test(Panel/Indicators MessageMenuItemFactory IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/plugins)
83add_qml_test(Panel/Indicators MessageMenuItemFactory IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/plugins)
88−add_qml_test(SideStage SideStage IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS}
84+add_qml_test(Stages PhoneStage IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS}
89 ${CMAKE_BINARY_DIR}/tests/mocks
85 ${CMAKE_BINARY_DIR}/tests/mocks
90 ${CMAKE_BINARY_DIR}/plugins)
86 ${CMAKE_BINARY_DIR}/plugins)
tests/qmltests/Components/CMakeLists.txt+2−10
@@ -6,19 +6,11 @@ include_directories(
6 ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
6 ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
7 )
7 )
8
8
9−set(testCommand
10− LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/plugins/Ubuntu/Gestures
11− ${CMAKE_CURRENT_BINARY_DIR}/DragHandleTestExec
12− -o ${CMAKE_BINARY_DIR}/DragHandleTest.xml,xunitxml
13− -o -,txt)
14−
15−add_custom_target(testDragHandle ${testCommand})
16−add_dependencies(qmluitests testDragHandle)
17−add_dependencies(testDragHandle DragHandleTestExec)
18−
19add_executable(DragHandleTestExec tst_DragHandle.cpp)
9add_executable(DragHandleTestExec tst_DragHandle.cpp)
20qt5_use_modules(DragHandleTestExec Test Core Qml Gui Quick)
10qt5_use_modules(DragHandleTestExec Test Core Qml Gui Quick)
21target_link_libraries(DragHandleTestExec UbuntuGestureQml)
11target_link_libraries(DragHandleTestExec UbuntuGestureQml)
22
12
13+add_binary_qml_test(DragHandle ${CMAKE_BINARY_DIR}/plugins/Ubuntu/Gestures "")
14+
23add_definitions(-DUBUNTU_GESTURES_PLUGIN_DIR="${CMAKE_BINARY_DIR}/plugins")
15add_definitions(-DUBUNTU_GESTURES_PLUGIN_DIR="${CMAKE_BINARY_DIR}/plugins")
24add_definitions(-DTEST_DIR="${CMAKE_CURRENT_LIST_DIR}")
16add_definitions(-DTEST_DIR="${CMAKE_CURRENT_LIST_DIR}")
tests/qmltests/Components/tst_AnimationControllerWithSignals.qmldeleted−142
@@ -1,142 +0,0 @@
1−/*
2− * Copyright 2013 Canonical Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import QtTest 1.0
19−import "../../../qml/Components"
20−
21−TestCase {
22− id: testCase
23− name: "AnimationControllerWithSignals"
24−
25− property real someNumber: 0.0
26−
27− NumberAnimation {
28− id: someNumberAnimation
29− target: testCase
30− properties: "someNumber"
31− from: 1.0
32− to: 100.0
33− }
34−
35− Component {
36− id: testSubjectComponent
37− AnimationControllerWithSignals {}
38− }
39−
40− Loader {
41− id: loader
42− asynchronous: false
43− }
44−
45− SignalSpy {
46− id: signalSpy
47− }
48−
49− function test_completeToEndWithSignal() {
50− loader.sourceComponent = testSubjectComponent;
51− var testSubject = loader.item;
52−
53− testSubject.animation = someNumberAnimation;
54− testSubject.progress = 0;
55−
56− signalSpy.signalName = "animationCompletedAtEnd";
57− signalSpy.target = testSubject;
58− signalSpy.clear();
59−
60− compare(testSubject.completing, false)
61− compare(testSubject.completed, true)
62−
63− testSubject.completeToEndWithSignal();
64− compare(testSubject.completing, true)
65− compare(testSubject.completed, true)
66− compare(signalSpy.count, 0);
67−
68− testSubject.progress = 0.5;
69− compare(testSubject.completing, true)
70− compare(testSubject.completed, false)
71− compare(signalSpy.count, 0);
72−
73− testSubject.progress = 1.0;
74− compare(testSubject.completing, false)
75− compare(testSubject.completed, true)
76− compare(signalSpy.count, 1);
77−
78− testSubject.animation = null
79− loader.sourceComponent = undefined;
80− }
81−
82− function test_completeToBeginningWithSignal() {
83− loader.sourceComponent = testSubjectComponent;
84− var testSubject = loader.item;
85−
86− testSubject.animation = someNumberAnimation;
87− testSubject.progress = 1;
88−
89− signalSpy.signalName = "animationCompletedAtBeginning";
90− signalSpy.target = testSubject;
91− signalSpy.clear();
92−
93− compare(testSubject.completing, false)
94− compare(testSubject.completed, true)
95−
96− testSubject.completeToBeginningWithSignal();
97− compare(testSubject.completing, true)
98− compare(testSubject.completed, true)
99− compare(signalSpy.count, 0);
100−
101− testSubject.progress = 0.5;
102− compare(testSubject.completing, true)
103− compare(testSubject.completed, false)
104− compare(signalSpy.count, 0);
105−
106− testSubject.progress = 0;
107− compare(testSubject.completing, false)
108− compare(testSubject.completed, true)
109− compare(signalSpy.count, 1);
110−
111− testSubject.animation = null
112− loader.sourceComponent = undefined;
113− }
114−
115− function test_settingProgressWithoutCallingCompleteWithSignal() {
116− loader.sourceComponent = testSubjectComponent;
117− var testSubject = loader.item;
118−
119− testSubject.animation = someNumberAnimation;
120− testSubject.progress = 0;
121−
122− signalSpy.signalName = "animationCompletedAtEnd";
123− signalSpy.target = testSubject;
124− signalSpy.clear();
125−
126− compare(testSubject.completing, false)
127− compare(testSubject.completed, true)
128−
129− testSubject.progress = 0.5;
130− compare(testSubject.completing, false)
131− compare(testSubject.completed, false)
132− compare(signalSpy.count, 0);
133−
134− testSubject.progress = 1;
135− compare(testSubject.completing, false)
136− compare(testSubject.completed, true)
137− compare(signalSpy.count, 0);
138−
139− testSubject.animation = null
140− loader.sourceComponent = undefined;
141− }
142−}
tests/qmltests/Components/tst_Carousel.qml+224−171
@@ -16,179 +16,232 @@
16
16
17import QtQuick 2.0
17import QtQuick 2.0
18import QtTest 1.0
18import QtTest 1.0
19+import "../../../qml/Components/"
19import "../../../qml/Components/carousel.js" as Carousel
20import "../../../qml/Components/carousel.js" as Carousel
20−
21+import Unity.Test 0.1 as UT
21−TestCase {
22+
22− id: root
23+Item {
23− name: "Carousel"
24+ width: 700
24−
25+ height: 400
25− property real carouselWidth
26+
27+ Carousel {
28+ anchors.fill: parent
29+ id: carousel
30+ model: 40
31+
26− property int itemCount
32+ property int createdDelegates: 0
27− property real tileWidth
33+ property int destroyedDelegates: 0
28−
34+
29− property real contentWidth: itemCount * tileWidth
35+ itemComponent: BaseCarouselDelegate {
30− // following variables are copied from Carousel.qml
36+ Rectangle {
31− // I'm not using the variables directly from there, as then tileWidth and others would be affected as well
37+ anchors.fill: parent
32− property real gapToMiddlePhase: Math.min(carouselWidth / 2 - tileWidth / 2, (contentWidth - carouselWidth) / 2)
38+ border.color: "black"
33− property real gapToEndPhase: contentWidth - carouselWidth - gapToMiddlePhase
39+ color: "red"
34− property real kMiddleIndex: (carouselWidth / 2) / tileWidth - 0.5
40+ Component.onCompleted: carousel.createdDelegates++
35− property real kGapEnd: kMiddleIndex * (1 - gapToEndPhase / gapToMiddlePhase)
41+ Component.onDestruction: carousel.destroyedDelegates++
36− property real kXBeginningEnd: 1 / tileWidth + kMiddleIndex / gapToMiddlePhase
42+ }
37−
43+ }
38− // test for the getContinuousIndex() function
39− function test_getContinuousIndex_data() {
40− // testing for 10 items of size 100 pixel
41− return [ {tag:"at start",
42− x: 0, carouselWidth:400, tileWidth:100, itemCount:10, result: 0},
43− {tag:"in startup",
44− x: 100, carouselWidth:400, tileWidth:100, itemCount:10, result: 2},
45− {tag:"in the middle",
46− x: 350, carouselWidth:400, tileWidth:100, itemCount:10, result: 5},
47− {tag:"at end",
48− x: 600, carouselWidth:400, tileWidth:100, itemCount:10, result: 9},
49− ]
50 }
44 }
51−
45+ UT.UnityTestCase {
46+ id: root
47+ name: "Carousel"
48+ when: windowShown
49+
50+ property real carouselWidth
51+ property int itemCount
52+ property real tileWidth
53+
54+ property real contentWidth: itemCount * tileWidth
55+ // following variables are copied from Carousel.qml
56+ // I'm not using the variables directly from there, as then tileWidth and others would be affected as well
57+ property real gapToMiddlePhase: Math.min(carouselWidth / 2 - tileWidth / 2, (contentWidth - carouselWidth) / 2)
58+ property real gapToEndPhase: contentWidth - carouselWidth - gapToMiddlePhase
59+ property real kMiddleIndex: (carouselWidth / 2) / tileWidth - 0.5
60+ property real kGapEnd: kMiddleIndex * (1 - gapToEndPhase / gapToMiddlePhase)
61+ property real kXBeginningEnd: 1 / tileWidth + kMiddleIndex / gapToMiddlePhase
62+
63+ // This test needs to run first since it tests a corner case condition
64+ // to where the carousel is just being created
65+ // That is why it has a 1 in the name
66+ function test_1onlyNeededItemsCreated() {
67+ tryCompare(carousel, "createdDelegates", 10);
68+ compare(carousel.destroyedDelegates, 0);
69+ }
70+
71+ // test for the getContinuousIndex() function
72+ function test_getContinuousIndex_data() {
73+ // testing for 10 items of size 100 pixel
74+ return [ {tag:"at start",
75+ x: 0, carouselWidth:400, tileWidth:100, itemCount:10, result: 0},
76+ {tag:"in startup",
77+ x: 100, carouselWidth:400, tileWidth:100, itemCount:10, result: 2},
78+ {tag:"in the middle",
79+ x: 350, carouselWidth:400, tileWidth:100, itemCount:10, result: 5},
80+ {tag:"at end",
81+ x: 600, carouselWidth:400, tileWidth:100, itemCount:10, result: 9},
82+ ]
83+ }
84+
52− function test_getContinuousIndex(data) {
85+ function test_getContinuousIndex(data) {
53− root.carouselWidth = data.carouselWidth
86+ root.carouselWidth = data.carouselWidth
54− root.tileWidth = data.tileWidth
87+ root.tileWidth = data.tileWidth
55− root.itemCount = data.itemCount
88+ root.itemCount = data.itemCount
56−
89+
57− var index = Carousel.getContinuousIndex(data.x,
90+ var index = Carousel.getContinuousIndex(data.x,
91+ data.tileWidth,
92+ gapToMiddlePhase,
93+ gapToEndPhase,
94+ kGapEnd,
95+ kMiddleIndex,
96+ kXBeginningEnd)
97+ compare(index, data.result)
98+ }
99+
100+ // test for the getXFromContinuousIndex() function
101+ function test_getXFromContinuousIndex_data() {
102+ return [ {tag:"at start",
103+ index: 0, carouselWidth:400, tileWidth:100, itemCount:10, drawBuffer:0, result: 0},
104+ {tag:"in startup",
105+ index: 2, carouselWidth:400, tileWidth:100, itemCount:10, drawBuffer:0, result: 100},
106+ {tag:"in startup with drawBuffer",
107+ index: 2, carouselWidth:400, tileWidth:100, itemCount:10, drawBuffer:100, result: 0},
108+ {tag:"in the middle",
109+ index: 5, carouselWidth:400, tileWidth:100, itemCount:10, drawBuffer:0, result: 350},
110+ {tag:"in the middle with drawBuffer",
111+ index: 5, carouselWidth:400, tileWidth:100, itemCount:10, drawBuffer:100, result: 250},
112+ {tag:"at end",
113+ index: 9, carouselWidth:400, tileWidth:100, itemCount:10, drawBuffer:0, result: 600},
114+ {tag:"at end with drawBuffer",
115+ index: 9, carouselWidth:400, tileWidth:100, itemCount:10, drawBuffer:100, result: 500},
116+ ]
117+ }
118+
119+ function test_getXFromContinuousIndex(data) {
120+ root.carouselWidth = data.carouselWidth
121+ root.tileWidth = data.tileWidth
122+ root.itemCount = data.itemCount
123+
124+ var x = Carousel.getXFromContinuousIndex(data.index,
125+ data.carouselWidth,
126+ contentWidth,
127+ data.tileWidth,
128+ gapToMiddlePhase,
129+ gapToEndPhase,
130+ data.drawBuffer)
131+ compare(x, data.result)
132+ }
133+
134+ // test for the getViewTranslation() function
135+ function test_getViewTranslation_data() {
136+ return [ {tag:"at start - viewfactor 1",
137+ x: 0, carouselWidth:400, tileWidth:100, itemCount:10, translationXViewFactor:1, result: 150},
138+ {tag:"at start - viewfactor 2",
139+ x: 0, carouselWidth:400, tileWidth:100, itemCount:10, translationXViewFactor:2, result: 300},
140+ {tag:"in startup",
141+ x: 100, carouselWidth:400, tileWidth:100, itemCount:10, translationXViewFactor:1, result: 50},
142+ {tag:"in the middle",
143+ x: 350, carouselWidth:400, tileWidth:100, itemCount:10, translationXViewFactor:1, result: 0},
144+ {tag:"at end",
145+ x: 600, carouselWidth:400, tileWidth:100, itemCount:10, translationXViewFactor:1, result: -150},
146+ ]
147+ }
148+
149+ function test_getViewTranslation(data) {
150+ root.carouselWidth = data.carouselWidth
151+ root.tileWidth = data.tileWidth
152+ root.itemCount = data.itemCount
153+
154+ var x = Carousel.getViewTranslation(data.x,
58 data.tileWidth,
155 data.tileWidth,
59− gapToMiddlePhase,
156+ root.gapToMiddlePhase,
60− gapToEndPhase,
61− kGapEnd,
62− kMiddleIndex,
63− kXBeginningEnd)
64− compare(index, data.result)
65− }
66−
67− // test for the getXFromContinuousIndex() function
68− function test_getXFromContinuousIndex_data() {
69− return [ {tag:"at start",
70− index: 0, carouselWidth:400, tileWidth:100, itemCount:10, drawBuffer:0, result: 0},
71− {tag:"in startup",
72− index: 2, carouselWidth:400, tileWidth:100, itemCount:10, drawBuffer:0, result: 100},
73− {tag:"in startup with drawBuffer",
74− index: 2, carouselWidth:400, tileWidth:100, itemCount:10, drawBuffer:100, result: 0},
75− {tag:"in the middle",
76− index: 5, carouselWidth:400, tileWidth:100, itemCount:10, drawBuffer:0, result: 350},
77− {tag:"in the middle with drawBuffer",
78− index: 5, carouselWidth:400, tileWidth:100, itemCount:10, drawBuffer:100, result: 250},
79− {tag:"at end",
80− index: 9, carouselWidth:400, tileWidth:100, itemCount:10, drawBuffer:0, result: 600},
81− {tag:"at end with drawBuffer",
82− index: 9, carouselWidth:400, tileWidth:100, itemCount:10, drawBuffer:100, result: 500},
83− ]
84− }
85−
86− function test_getXFromContinuousIndex(data) {
87− root.carouselWidth = data.carouselWidth
88− root.tileWidth = data.tileWidth
89− root.itemCount = data.itemCount
90−
91− var x = Carousel.getXFromContinuousIndex(data.index,
92− data.carouselWidth,
93− contentWidth,
94− data.tileWidth,
95− gapToMiddlePhase,
96− gapToEndPhase,
157+ root.gapToEndPhase,
97− data.drawBuffer)
158+ data.translationXViewFactor)
98− compare(x, data.result)
159+ compare(x, data.result)
99− }
160+ }
100−
161+
101− // test for the getViewTranslation() function
102− function test_getViewTranslation_data() {
103− return [ {tag:"at start - viewfactor 1",
104− x: 0, carouselWidth:400, tileWidth:100, itemCount:10, translationXViewFactor:1, result: 150},
105− {tag:"at start - viewfactor 2",
106− x: 0, carouselWidth:400, tileWidth:100, itemCount:10, translationXViewFactor:2, result: 300},
107− {tag:"in startup",
108− x: 100, carouselWidth:400, tileWidth:100, itemCount:10, translationXViewFactor:1, result: 50},
109− {tag:"in the middle",
110− x: 350, carouselWidth:400, tileWidth:100, itemCount:10, translationXViewFactor:1, result: 0},
111− {tag:"at end",
112− x: 600, carouselWidth:400, tileWidth:100, itemCount:10, translationXViewFactor:1, result: -150},
113− ]
114− }
115−
116− function test_getViewTranslation(data) {
117− root.carouselWidth = data.carouselWidth
118− root.tileWidth = data.tileWidth
119− root.itemCount = data.itemCount
120−
121− var x = Carousel.getViewTranslation(data.x,
122− data.tileWidth,
123− root.gapToMiddlePhase,
124− root.gapToEndPhase,
125− data.translationXViewFactor)
126− compare(x, data.result)
127− }
128−
129− // test for the getItemScale() function
162+ // test for the getItemScale() function
130− function test_getItemScale_data() {
163+ function test_getItemScale_data() {
131− return [ // tests for distance
164+ return [ // tests for distance
132− {distance: 0, continuousIndex: 50, numberOfItems: 100, scaleFactor: 1, result: 1},
165+ {distance: 0, continuousIndex: 50, numberOfItems: 100, scaleFactor: 1, result: 1},
133− {distance: 9, continuousIndex: 50, numberOfItems: 100, scaleFactor: 1, result: 0.01},
166+ {distance: 9, continuousIndex: 50, numberOfItems: 100, scaleFactor: 1, result: 0.01},
134− {distance: 999, continuousIndex: 50, numberOfItems: 100, scaleFactor: 1, result: 0.01},
167+ {distance: 999, continuousIndex: 50, numberOfItems: 100, scaleFactor: 1, result: 0.01},
135− {distance: 1, continuousIndex: 50, numberOfItems: 100, scaleFactor: 1, result: 0.98}, // = 1 - (1 / 50)
168+ {distance: 1, continuousIndex: 50, numberOfItems: 100, scaleFactor: 1, result: 0.98}, // = 1 - (1 / 50)
136− {distance: 3, continuousIndex: 50, numberOfItems: 100, scaleFactor: 1, result: 0.688230855}, // = 1 - (3^2.5 / 50)
169+ {distance: 3, continuousIndex: 50, numberOfItems: 100, scaleFactor: 1, result: 0.688230855}, // = 1 - (3^2.5 / 50)
137− // tests for continuousIndex
170+ // tests for continuousIndex
138− {distance: 1, continuousIndex: 50, numberOfItems: 100, scaleFactor: 1, result: 0.98}, // = 1 - (1 / 50)
171+ {distance: 1, continuousIndex: 50, numberOfItems: 100, scaleFactor: 1, result: 0.98}, // = 1 - (1 / 50)
139− {distance: 1, continuousIndex: 97, numberOfItems: 100, scaleFactor: 1, result: 0.99}, // = 1 - (1 / 100) - distanceToBounds is used
172+ {distance: 1, continuousIndex: 97, numberOfItems: 100, scaleFactor: 1, result: 0.99}, // = 1 - (1 / 100) - distanceToBounds is used
140− // tests for numberOfItems
173+ // tests for numberOfItems
141− {distance: 1, continuousIndex: 50, numberOfItems: 100, scaleFactor: 1, result: 0.98}, // = 1 - (1 / 50)
174+ {distance: 1, continuousIndex: 50, numberOfItems: 100, scaleFactor: 1, result: 0.98}, // = 1 - (1 / 50)
142− {distance: 1, continuousIndex: 50, numberOfItems: 53, scaleFactor: 1, result: 0.99}, // = 1 - (1 / 100) - distanceToBounds is used
175+ {distance: 1, continuousIndex: 50, numberOfItems: 53, scaleFactor: 1, result: 0.99}, // = 1 - (1 / 100) - distanceToBounds is used
143− // tests for scaleFactor
176+ // tests for scaleFactor
144− {distance: 1, continuousIndex: 50, numberOfItems: 100, scaleFactor: 1, result: 0.98}, // = 1 - (1 / 50)
177+ {distance: 1, continuousIndex: 50, numberOfItems: 100, scaleFactor: 1, result: 0.98}, // = 1 - (1 / 50)
145− {distance: 1, continuousIndex: 50, numberOfItems: 100, scaleFactor: 2, result: 0.99}, // = 1 - (1 / 100)
178+ {distance: 1, continuousIndex: 50, numberOfItems: 100, scaleFactor: 2, result: 0.99}, // = 1 - (1 / 100)
146− {distance: 1, continuousIndex: 50, numberOfItems: 100, scaleFactor: 0.5, result: 0.96}, // = 1 - (1 / 25)
179+ {distance: 1, continuousIndex: 50, numberOfItems: 100, scaleFactor: 0.5, result: 0.96}, // = 1 - (1 / 25)
147− {distance: 1, continuousIndex: 50, numberOfItems: 53, scaleFactor: 1, result: 0.99}, // = 1 - (1 / 100) - distanceToBounds is used
180+ {distance: 1, continuousIndex: 50, numberOfItems: 53, scaleFactor: 1, result: 0.99}, // = 1 - (1 / 100) - distanceToBounds is used
148− {distance: 1, continuousIndex: 50, numberOfItems: 53, scaleFactor: 2, result: 0.996666666}, // = 1 - (1 / 300) - distanceToBounds is used
181+ {distance: 1, continuousIndex: 50, numberOfItems: 53, scaleFactor: 2, result: 0.996666666}, // = 1 - (1 / 300) - distanceToBounds is used
149− ]
182+ ]
150− }
183+ }
151−
184+
152− function test_getItemScale(data) {
185+ function test_getItemScale(data) {
153− var scale = Carousel.getItemScale(data.distance,
186+ var scale = Carousel.getItemScale(data.distance,
154− data.continuousIndex,
187+ data.continuousIndex,
155− data.numberOfItems,
188+ data.numberOfItems,
156− data.scaleFactor)
189+ data.scaleFactor)
157− compare(scale, data.result)
190+ compare(scale, data.result)
158− }
191+ }
159−
192+
160− // test for the getItemTranslation() function
193+ // test for the getItemTranslation() function
161− function test_getItemTranslation_data() {
194+ function test_getItemTranslation_data() {
162− return [ // tests for index and selectedIndex
195+ return [ // tests for index and selectedIndex
163− {index: 1, selectedIndex: 1, distance: 1, scale: 1, maxScale: 1, maxTranslation: 10, result: 0},
196+ {index: 1, selectedIndex: 1, distance: 1, scale: 1, maxScale: 1, maxTranslation: 10, result: 0},
164− // tests if distance only affects the sign
197+ // tests if distance only affects the sign
165− {index: 1, selectedIndex: 2, distance: 1, scale: 0, maxScale: 1, maxTranslation: 10, result: 10},
198+ {index: 1, selectedIndex: 2, distance: 1, scale: 0, maxScale: 1, maxTranslation: 10, result: 10},
166− {index: 1, selectedIndex: 2, distance: 99, scale: 0, maxScale: 1, maxTranslation: 10, result: 10},
199+ {index: 1, selectedIndex: 2, distance: 99, scale: 0, maxScale: 1, maxTranslation: 10, result: 10},
167− {index: 1, selectedIndex: 2, distance: -1, scale: 0, maxScale: 1, maxTranslation: 10, result: -10},
200+ {index: 1, selectedIndex: 2, distance: -1, scale: 0, maxScale: 1, maxTranslation: 10, result: -10},
168− {index: 1, selectedIndex: 2, distance: -99, scale: 0, maxScale: 1, maxTranslation: 10, result: -10},
201+ {index: 1, selectedIndex: 2, distance: -99, scale: 0, maxScale: 1, maxTranslation: 10, result: -10},
169− // tests for the scale
202+ // tests for the scale
170− {index: 1, selectedIndex: 2, distance: 1, scale: 1, maxScale: 1, maxTranslation: 10, result: 0},
203+ {index: 1, selectedIndex: 2, distance: 1, scale: 1, maxScale: 1, maxTranslation: 10, result: 0},
171− {index: 1, selectedIndex: 2, distance: 1, scale: 0, maxScale: 1, maxTranslation: 10, result: 10},
204+ {index: 1, selectedIndex: 2, distance: 1, scale: 0, maxScale: 1, maxTranslation: 10, result: 10},
172− {index: 1, selectedIndex: 2, distance: 1, scale: 0.5, maxScale: 1, maxTranslation: 10, result: 5},
205+ {index: 1, selectedIndex: 2, distance: 1, scale: 0.5, maxScale: 1, maxTranslation: 10, result: 5},
173− // tests for maxScale
206+ // tests for maxScale
174− {index: 1, selectedIndex: 2, distance: 1, scale: 0, maxScale: 0.98, maxTranslation: 10, result: 9.8},
207+ {index: 1, selectedIndex: 2, distance: 1, scale: 0, maxScale: 0.98, maxTranslation: 10, result: 9.8},
175− {index: 1, selectedIndex: 2, distance: 1, scale: 0.5, maxScale: 0.95, maxTranslation: 10, result: 4.5},
208+ {index: 1, selectedIndex: 2, distance: 1, scale: 0.5, maxScale: 0.95, maxTranslation: 10, result: 4.5},
176− {index: 1, selectedIndex: 2, distance: 1, scale: 0.8, maxScale: 0.93, maxTranslation: 10, result: 1.3},
209+ {index: 1, selectedIndex: 2, distance: 1, scale: 0.8, maxScale: 0.93, maxTranslation: 10, result: 1.3},
177− // test for maxTranslation
210+ // test for maxTranslation
178− {index: 1, selectedIndex: 2, distance: 1, scale: 1, maxScale: 1, maxTranslation: 1, result: 0},
211+ {index: 1, selectedIndex: 2, distance: 1, scale: 1, maxScale: 1, maxTranslation: 1, result: 0},
179− {index: 1, selectedIndex: 2, distance: 1, scale: 0, maxScale: 1, maxTranslation: 1, result: 1},
212+ {index: 1, selectedIndex: 2, distance: 1, scale: 0, maxScale: 1, maxTranslation: 1, result: 1},
180− {index: 1, selectedIndex: 2, distance: 1, scale: 0, maxScale: 1, maxTranslation: 10, result: 10},
213+ {index: 1, selectedIndex: 2, distance: 1, scale: 0, maxScale: 1, maxTranslation: 10, result: 10},
181− {index: 1, selectedIndex: 2, distance: 1, scale: 0, maxScale: 1, maxTranslation: 0, result: 0},
214+ {index: 1, selectedIndex: 2, distance: 1, scale: 0, maxScale: 1, maxTranslation: 0, result: 0},
182− ]
215+ ]
183− }
216+ }
184−
217+
185− function test_getItemTranslation(data) {
218+ function test_getItemTranslation(data) {
186− var scale = Carousel.getItemTranslation(data.index,
219+ var scale = Carousel.getItemTranslation(data.index,
187− data.selectedIndex,
220+ data.selectedIndex,
188− data.distance,
221+ data.distance,
189− data.scale,
222+ data.scale,
190− data.maxScale,
223+ data.maxScale,
191− data.maxTranslation)
224+ data.maxTranslation)
192− compare(scale, data.result)
225+ compare(scale, data.result)
226+ }
227+
228+ function test_lastItemXPosition() {
229+ var carouselList = findChild(carousel, "listView");
230+ var stepAnimation = findInvisibleChild(carouselList, "stepAnimation");
231+ var stepDiff = 0;
232+ stepAnimation.onRunningChanged.connect(function() { if (stepAnimation.running) stepDiff = Math.abs(carouselList.contentX - stepAnimation.to); })
233+ var overshootOnce = false;
234+ while (!overshootOnce) {
235+ overshootOnce = carouselList.realContentX > carouselList.realContentWidth - carouselList.realWidth;
236+ mouseFlick(carousel, carousel.width - units.gu(1),
237+ carousel.height / 2,
238+ units.gu(2),
239+ carousel.height / 2);
240+ tryCompare(carouselList, "moving", false);
241+ }
242+ verify(stepDiff < 1);
243+ var x = carouselList.getXFromContinuousIndex(carousel.model - 1);
244+ verify(Math.abs(x - carouselList.contentX) < 1);
245+ }
193 }
246 }
194}
247}
tests/qmltests/Components/tst_LazyImage.qml+1
@@ -152,6 +152,7 @@ Rectangle {
152
152
153 function test_lazyimage(data) {
153 function test_lazyimage(data) {
154 data.func();
154 data.func();
155+ waitForRendering(baseRect);
155
156
156 if (data.transition) {
157 if (data.transition) {
157 // wait for the transition to complete
158 // wait for the transition to complete
tests/qmltests/Components/tst_ResponsiveFlowView.qml+1−2
@@ -82,8 +82,7 @@ Item {
82 ResponsiveFlowView {
82 ResponsiveFlowView {
83 id: flow
83 id: flow
84 anchors.fill: parent
84 anchors.fill: parent
85− firstModel: fakeModel
86− secondModel: fakeModel
85+ model: fakeModel
87 minimumHorizontalSpacing:
86 minimumHorizontalSpacing:
88 minHSpacingSelector.values[minHSpacingSelector.selectedIndex]
87 minHSpacingSelector.values[minHSpacingSelector.selectedIndex]
89 verticalSpacing: units.gu(2)
88 verticalSpacing: units.gu(2)
tests/qmltests/Components/tst_Stage.qmldeleted−278
@@ -1,278 +0,0 @@
1−/*
2− * Copyright 2013 Canonical Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import QtTest 1.0
19−import ".."
20−import "../../../qml/Components"
21−import Unity.Application 0.1
22−import Ubuntu.Components 0.1
23−import Unity.Test 0.1 as UT
24−import "tst_Stage"
25−
26−Rectangle {
27− width: units.gu(70)
28− height: stageRect.height
29−
30− // Even though we replace the ApplicationScreenshot instances in Stage
31− // with our own fake ones, ApplicationScreenshot.qml still gives out a warning
32− // if shell.importUbuntuApplicationAvailable is missing.
33− // For we have this here for the sake of keeping a clean log output.
34− Item {
35− id: shell
36− property bool importUbuntuApplicationAvailable: false
37− }
38−
39− // A fake ApplicationManager implementation to be passed to Stage
40− QtObject {
41− id: fakeAppManager
42− property ListModel mainStageApplications: ListModel {}
43− property ListModel sideStageApplications: ListModel {}
44− property var mainStageFocusedApplication
45−
46− property Component fakeAppWindowComponent: Component {
47− Rectangle {
48− width: stage.width
49− height: stage.height
50− property alias text : txt.text
51− Text {id:txt}
52− }
53− }
54−
55− function activateApplication(desktopFile, argument) {
56− var appWindow = fakeAppWindowComponent.createObject(fakeWindowContainer)
57− var application = {
58− 'icon': "foo",
59− 'handle': desktopFile,
60− 'name': "Foo",
61− 'fullscreen': false,
62− 'desktopFile': desktopFile,
63− 'stage': ApplicationInfo.MainStage,
64− 'argument': 0,
65− 'color': desktopFile,
66− 'window': appWindow
67− };
68− appWindow.color = application.color
69− appWindow.text = desktopFile + " actual";
70−
71− mainStageApplications.append(application)
72− updateZOrderOfWindows(mainStageApplications)
73−
74− return application
75− }
76−
77− function focusApplication(application) {
78− mainStageFocusedApplication = application
79− }
80−
81− function getApplicationFromDesktopFile(desktopFile, stageType) {
82− var sideStage = (stage == ApplicationInfo.SideStage);
83− var applications = sideStage ? sideStageApplications
84− : mainStageApplications;
85−
86− for (var i = 0; i < applications.count; i++ ) {
87− var application = applications.get(i);
88− if (application.desktopFile === desktopFile) {
89− return application;
90− }
91− }
92− return null;
93− }
94−
95− function moveRunningApplicationStackPosition(from, to, stage) {
96− var sideStage = (stage == ApplicationInfo.SideStage);
97− var applications = sideStage ? sideStageApplications
98− : mainStageApplications;
99−
100− if (from !== to && applications.count > 0 && from >= 0 && to >= 0) {
101− applications.move(from, to, 1);
102− }
103−
104− updateZOrderOfWindows(applications)
105− }
106−
107− function updateZOrderOfWindows(applications) {
108− var nextZ = 100
109− for (var i = 0; i < applications.count; i++ ) {
110− var application = applications.get(i);
111− application.window.z = nextZ--;
112− }
113− }
114−
115− function deacticateApplication(desktopFile) {
116− for (var i = 0; i < mainStageApplications.count; i++ ) {
117− var application = mainStageApplications.get(i)
118− if (application.desktopFile === desktopFile) {
119− focusApplication(null)
120− application.window.destroy();
121− mainStageApplications.remove(i)
122− updateZOrderOfWindows(mainStageApplications)
123− return;
124− }
125− }
126− }
127− }
128−
129− Rectangle {
130− id: stageRect
131− x:0
132− y:0
133− width: childrenRect.width
134− height: childrenRect.height
135−
136− color: "grey"
137−
138− // This is where the fake windows are held.
139− // They stay behind the stage, so that the stage's screenshots are shown
140− // on top of them
141− // On a real usage scenario, the current application's surface is composited behind
142− // the shell's surface (where Stage lives). fakeWindowContainer simulates this stacking
143− Item {
144− id: fakeWindowContainer
145− anchors.fill: parent
146− }
147−
148−
149− // A black rectangle behind the stage so that the window switch animations
150− // look good, just like in Stage's real usage.
151− Rectangle {
152− anchors.fill: parent
153− color: "black"
154− visible: stage.usingScreenshots
155− }
156−
157− Stage {
158− id: stage
159− y: 0
160− shouldUseScreenshots: false
161− applicationManager: fakeAppManager
162− rightEdgeDraggingAreaWidth: units.gu(2)
163− fullyShown: true
164− fullyHidden: false
165− newApplicationScreenshot: FakeApplicationScreenshot {
166− id: newAppScreenshot
167− parent: stage
168− width: stage.width
169− height: stage.height - stage.y}
170− oldApplicationScreenshot: FakeApplicationScreenshot {
171− id: oldAppScreenshot
172− parent: stage
173− width: stage.width
174− height: stage.height - stage.y}
175− }
176− }
177−
178− Rectangle {
179− id: controlsRect
180− anchors.top: parent.top
181− anchors.bottom: parent.bottom
182− anchors.left: stageRect.right
183− anchors.right: parent.right
184− color: "lightgrey"
185−
186− Column {
187− id: controls
188− spacing: units.gu(1)
189− AppControl {id: redControl; desktopFile:"red"}
190− AppControl {id: greenControl; desktopFile:"green"}
191− AppControl {id: blueControl; desktopFile:"blue"}
192− }
193− }
194−
195− UT.UnityTestCase {
196− name: "Stage"
197− when: windowShown
198−
199− function isCurrentAppFadingOut() {
200− // it should get a bit translucent and smaller
201− return oldAppScreenshot.opacity < 0.99
202− && oldAppScreenshot.opacity >= 0.1
203− && oldAppScreenshot.scale < 0.99
204− && oldAppScreenshot.scale >= 0.1
205− && oldAppScreenshot.visible
206− }
207−
208− function init() {
209− redControl.checked = false;
210− greenControl.checked = false;
211− blueControl.checked = false;
212− // give some room for animations to start
213− wait(50)
214− // wait until animations end, if any
215− tryCompare(stage, "usingScreenshots", false)
216− }
217−
218− /* If you flick from the right edge of the stage leftwards it should cause an
219− application switch. */
220− function test_dragFromRightEdgeToSwitchApplication() {
221− redControl.checked = true
222−
223− tryCompare(stage, "usingScreenshots", true) // wait for the animation to start
224− tryCompare(stage, "usingScreenshots", false) // and then for it to end
225− compare(fakeAppManager.mainStageFocusedApplication.desktopFile, "red")
226− compare(fakeAppManager.mainStageApplications.get(0).desktopFile, "red")
227−
228− greenControl.checked = true
229−
230− tryCompare(stage, "usingScreenshots", true) // wait for the animation to start
231− tryCompare(stage, "usingScreenshots", false) // and then for it to end
232− compare(fakeAppManager.mainStageFocusedApplication.desktopFile, "green")
233− compare(fakeAppManager.mainStageApplications.get(0).desktopFile, "green")
234−
235− var touchX = stage.width - (stage.rightEdgeDraggingAreaWidth / 2)
236− var touchY = stage.height / 2
237− touchFlick(stage, touchX, touchY, stage.width * 0.25, touchY)
238−
239− // wait until animations end, if any
240− tryCompare(stage, "usingScreenshots", false)
241−
242− // "red" should be the new topmost, focused, application
243− compare(fakeAppManager.mainStageFocusedApplication.desktopFile, "red")
244− compare(fakeAppManager.mainStageApplications.get(0).desktopFile, "red")
245− }
246−
247− /* When an application is launched, it needs a background before it's drawn on screen
248− so that the user does not see the previous running app while the new one is launching.
249− When switching between applications, backgrounds are unnecessary, 'cause the
250− applications are in front of them. */
251− function test_background() {
252− redControl.checked = true
253− tryCompare(stage, "usingScreenshots", true) // wait for the animation to start
254−
255− compare(newAppScreenshot.withBackground, true, "starting app screenshot does not have background enabled")
256−
257− tryCompare(stage, "usingScreenshots", false) // wait for the animation to finish
258−
259− greenControl.checked = true
260− tryCompare(stage, "usingScreenshots", true) // wait for the animation to start
261− tryCompare(stage, "usingScreenshots", false) // and finish
262−
263− var draggingAreaCenterX = stage.width - (stage.rightEdgeDraggingAreaWidth / 2)
264− var draggingAreaCenterY = stage.height / 2
265− var finalTouchX = draggingAreaCenterX - units.gu(5)
266− touchFlick(stage, draggingAreaCenterX, draggingAreaCenterY,
267− finalTouchX, draggingAreaCenterY,
268− true /* beginTouch */, false /* endTouch */)
269−
270− // wait for the animation to start
271− tryCompare(stage, "usingScreenshots", true)
272−
273− compare(newAppScreenshot.withBackground, false, "switched app does have background enabled")
274−
275− touchRelease(stage, finalTouchX, draggingAreaCenterY)
276− }
277− }
278−}
tests/qmltests/Components/tst_Stage/AppControl.qmldeleted−42
@@ -1,42 +0,0 @@
1−/*
2− * Copyright 2013 Canonical Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import Ubuntu.Components 0.1
19−
20−Row {
21− id: root
22− property string desktopFile
23− spacing: units.gu(1)
24−
25− property alias checked: checkbox.checked
26−
27− Label {text: root.desktopFile
28− anchors.verticalCenter: parent.verticalCenter
29− width:units.gu(5)}
30−
31− CheckBox {
32− id: checkbox
33− onCheckedChanged: {
34− if (checked) {
35− var app = fakeAppManager.activateApplication(root.desktopFile)
36− stage.activateApplication(root.desktopFile)
37− } else {
38− fakeAppManager.deacticateApplication(root.desktopFile)
39− }
40− }
41− }
42−}
tests/qmltests/Components/tst_Stage/FakeApplicationScreenshot.qmldeleted−53
@@ -1,53 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−
19−Item {
20− property var application
21− property bool withBackground: true
22− property bool ready: application != undefined
23−
24− function setApplication(app) {
25− application = app
26− }
27−
28− function clearApplication() {
29− setApplication(undefined)
30− }
31−
32− function scheduleUpdate() {}
33−
34− function updateFromCache() {}
35−
36− Rectangle {
37− id: rect
38− anchors.fill: parent
39− Text {id:text}
40− }
41−
42− onApplicationChanged: {
43− if (application) {
44− rect.color = application.color
45− text.text = application.desktopFile + " screenshot"
46− rect.visible = true
47− } else {
48− rect.color = "white"
49− text.text = "<NULL> screenshot"
50− rect.visible = false
51− }
52− }
53−}
tests/qmltests/Components/tst_ZoomableImage.qml+9−6
@@ -115,7 +115,7 @@ Rectangle {
115 }
115 }
116 }
116 }
117
117
118− function touchPinch(x1Start, y1Start, x1End, y1End, x2Start, y2Start, x2End, y2End) {
118+ function touchPinch1(x1Start, y1Start, x1End, y1End, x2Start, y2Start, x2End, y2End) {
119 var event1 = touchEvent();
119 var event1 = touchEvent();
120 // first finger
120 // first finger
121 event1.press(0, x1Start, y1Start);
121 event1.press(0, x1Start, y1Start);
@@ -144,13 +144,15 @@ Rectangle {
144 answer1: true,
144 answer1: true,
145 answer2: false,
145 answer2: false,
146 answer3: true,
146 answer3: true,
147− answer4: 1.0 },
147+ answer4: 1.0,
148+ answer5: 1.0 },
148 { source:widgetData2["source"],
149 { source:widgetData2["source"],
149 zoomable:true,
150 zoomable:true,
150 answer1: false,
151 answer1: false,
151 answer2: true,
152 answer2: true,
152 answer3: false,
153 answer3: false,
153− answer4: 1.7740461882048026 }
154+ answer4: 1.7740461882048026,
155+ answer5: 0.5405058029189072 }
154 ]
156 ]
155 }
157 }
156
158
@@ -186,7 +188,7 @@ Rectangle {
186 mouseMove(zoomableImage, zoomableImage.width / 2, zoomableImage.height / 2);
188 mouseMove(zoomableImage, zoomableImage.width / 2, zoomableImage.height / 2);
187
189
188 // pinch
190 // pinch
189− touchPinch(x1Start, y1Start, x1End, y1End, x2Start, y2Start, x2End, y2End);
191+ touchPinch(zoomableImage, x1Start, y1Start, x1End, y1End, x2Start, y2Start, x2End, y2End);
190
192
191 tryCompare(image, "scale", data.answer4);
193 tryCompare(image, "scale", data.answer4);
192 var newScale = image.scale;
194 var newScale = image.scale;
@@ -199,10 +201,11 @@ Rectangle {
199
201
200 // move mouse to center
202 // move mouse to center
201 mouseMove(zoomableImage, zoomableImage.width / 2, zoomableImage.height / 2);
203 mouseMove(zoomableImage, zoomableImage.width / 2, zoomableImage.height / 2);
204+ wait(3000); // have to delay between two pinch event.
202
205
203 // pinch
206 // pinch
204− touchPinch(x1End, y1End, x1Start, y1Start, x2End, y2End, x2Start, y2Start);
207+ touchPinch(zoomableImage, x1End, y1End, x1Start, y1Start, x2End, y2End, x2Start, y2Start);
205− tryCompare(image, "scale", oldScale);
208+ tryCompare(image, "scale", data.answer5);
206 }
209 }
207 }
210 }
208}
211}
tests/qmltests/Dash/Apps/tst_RunningApplicationsGrid.qml+50−117
@@ -18,87 +18,20 @@ import QtQuick 2.0
18import QtTest 1.0
18import QtTest 1.0
19import "../../../../qml/Dash/Apps"
19import "../../../../qml/Dash/Apps"
20import Unity.Test 0.1 as UT
20import Unity.Test 0.1 as UT
21+import Unity.Application 0.1
21
22
22−Item {
23+// Using Rectangle to have an opaque surface because AppManager paints app surfaces behind it.
24+Rectangle {
23 width: units.gu(50)
25 width: units.gu(50)
24 height: units.gu(40)
26 height: units.gu(40)
25
27
26− QtObject {
28+ function resetRunningApplications() {
27− id: fakeApplicationManager
29+ while (ApplicationManager.count > 0) {
28−
30+ ApplicationManager.stopApplication(ApplicationManager.get(0).appId)
29− property bool sideStageEnabled: false
30−
31− function stopApplication(appId) {
32− for (var i=0, len=fakeRunningAppsModel.count; i<len; i++) {
33− if (appId == fakeRunningAppsModel.get(i).appId) {
34− fakeRunningAppsModel.remove(i)
35− }
36− }
37− }
38− }
39−
40− QtObject {
41− id: shell
42− property bool dashShown: true
43− property bool stageScreenshotsReady: false
44− property var applicationManager: fakeApplicationManager
45−
46− function activateApplication(appId) {
47− }
48− }
49−
50− ListModel {
51− id: fakeRunningAppsModel
52−
53− function contains(appId) {
54− for (var i=0, len=fakeRunningAppsModel.count; i<len; i++) {
55− if (appId == fakeRunningAppsModel.get(i).appId) {
56− return true;
57− }
58− }
59− return false;
60− }
61−
62− function rePopulate() {
63− for (var i=0, len=availableAppsModel.count; i<len; i++) {
64− fakeRunningAppsModel.append(availableAppsModel.get(i));
65− }
66− }
67− }
68−
69− ListModel {
70− id: availableAppsModel
71− ListElement {
72− name: "Phone"
73− icon: "phone-app"
74− exec: "/usr/bin/phone-app"
75− appId: "phone"
76− imageQml: "import QtQuick 2.0\n \
77− Rectangle { \n \
78− anchors.fill:parent \n \
79− color:'darkgreen' \n \
80− Text { anchors.centerIn: parent; text: 'PHONE' } \n \
81− }"
82 }
31 }
83
32
84− ListElement {
33+ ApplicationManager.startApplication("phone-app");
85− name: "Calendar"
34+ ApplicationManager.startApplication("webbrowser-app");
86− icon: "calendar-app"
87− exec: "/usr/bin/calendar-app"
88− appId: "calendar"
89− imageQml: "import QtQuick 2.0\n \
90− Rectangle { \n \
91− anchors.fill:parent \n \
92− color:'darkblue' \n \
93− Text { anchors.centerIn: parent; text: 'CALENDAR'\n \
94− color:'white'} \n \
95− }"
96− }
97− }
98−
99− function resetRunningApplications() {
100− fakeRunningAppsModel.clear()
101− fakeRunningAppsModel.rePopulate()
102 }
35 }
103
36
104 Component.onCompleted: {
37 Component.onCompleted: {
@@ -109,7 +42,7 @@ Item {
109 RunningApplicationsGrid {
42 RunningApplicationsGrid {
110 id: runningApplicationsGrid
43 id: runningApplicationsGrid
111 anchors.fill: parent
44 anchors.fill: parent
112− firstModel: fakeRunningAppsModel
45+ model: ApplicationManager
113 }
46 }
114
47
115 UT.UnityTestCase {
48 UT.UnityTestCase {
@@ -121,11 +54,11 @@ Item {
121 resetRunningApplications()
54 resetRunningApplications()
122 }
55 }
123
56
124− property var calendarTile
57+ property var browserTile
125 property var phoneTile
58 property var phoneTile
126
59
127− property var isCalendarLongPressed: false
60+ property var isBrowserLongPressed: false
128− function onCalendarLongPressed() {isCalendarLongPressed = true}
61+ function onBrowserLongPressed() {isBrowserLongPressed = true}
129
62
130 property var isPhoneLongPressed: false
63 property var isPhoneLongPressed: false
131 function onPhoneLongPressed() {isPhoneLongPressed = true}
64 function onPhoneLongPressed() {isPhoneLongPressed = true}
@@ -133,25 +66,25 @@ Item {
133 // Tiles should go to termination mode when any one of them is long-pressed.
66 // Tiles should go to termination mode when any one of them is long-pressed.
134 // Long-pressing when they're in termination mode brings them back to activation mode
67 // Long-pressing when they're in termination mode brings them back to activation mode
135 function test_enterTerminationMode() {
68 function test_enterTerminationMode() {
136− calendarTile = findChild(runningApplicationsGrid, "runningAppTile Calendar")
69+ browserTile = findChild(runningApplicationsGrid, "runningAppTile Browser")
137− verify(calendarTile != undefined)
70+ verify(browserTile != undefined)
138− calendarTile.onPressAndHold.connect(onCalendarLongPressed)
71+ browserTile.onPressAndHold.connect(onBrowserLongPressed)
139
72
140 phoneTile = findChild(runningApplicationsGrid, "runningAppTile Phone")
73 phoneTile = findChild(runningApplicationsGrid, "runningAppTile Phone")
141 verify(phoneTile != undefined)
74 verify(phoneTile != undefined)
142 phoneTile.onPressAndHold.connect(onPhoneLongPressed)
75 phoneTile.onPressAndHold.connect(onPhoneLongPressed)
143
76
144− compare(calendarTile.terminationModeEnabled, false)
77+ compare(browserTile.terminationModeEnabled, false)
145 compare(phoneTile.terminationModeEnabled, false)
78 compare(phoneTile.terminationModeEnabled, false)
146 compare(runningApplicationsGrid.terminationModeEnabled, false)
79 compare(runningApplicationsGrid.terminationModeEnabled, false)
147
80
148− isCalendarLongPressed = false
81+ isBrowserLongPressed = false
149− mousePress(calendarTile, calendarTile.width/2, calendarTile.height/2)
82+ mousePress(browserTile, browserTile.width/2, browserTile.height/2)
150 tryCompareFunction(checkSwitchToTerminationModeAfterLongPress, true)
83 tryCompareFunction(checkSwitchToTerminationModeAfterLongPress, true)
151
84
152− mouseRelease(calendarTile, calendarTile.width/2, calendarTile.height/2)
85+ mouseRelease(browserTile, browserTile.width/2, browserTile.height/2)
153
86
154− compare(calendarTile.terminationModeEnabled, true)
87+ compare(browserTile.terminationModeEnabled, true)
155 compare(phoneTile.terminationModeEnabled, true)
88 compare(phoneTile.terminationModeEnabled, true)
156 compare(runningApplicationsGrid.terminationModeEnabled, true)
89 compare(runningApplicationsGrid.terminationModeEnabled, true)
157
90
@@ -161,23 +94,23 @@ Item {
161
94
162 mouseRelease(phoneTile, phoneTile.width/2, phoneTile.height/2)
95 mouseRelease(phoneTile, phoneTile.width/2, phoneTile.height/2)
163
96
164− compare(calendarTile.terminationModeEnabled, false)
97+ compare(browserTile.terminationModeEnabled, false)
165 compare(phoneTile.terminationModeEnabled, false)
98 compare(phoneTile.terminationModeEnabled, false)
166 compare(runningApplicationsGrid.terminationModeEnabled, false)
99 compare(runningApplicationsGrid.terminationModeEnabled, false)
167
100
168− calendarTile.onPressAndHold.disconnect(onCalendarLongPressed)
101+ browserTile.onPressAndHold.disconnect(onBrowserLongPressed)
169 phoneTile.onPressAndHold.disconnect(onPhoneLongPressed)
102 phoneTile.onPressAndHold.disconnect(onPhoneLongPressed)
170 }
103 }
171
104
172 // Checks that components swicth to termination mode after (and only after) a long
105 // Checks that components swicth to termination mode after (and only after) a long
173− // press happens on Calendar tile.
106+ // press happens on Browser tile.
174 function checkSwitchToTerminationModeAfterLongPress() {
107 function checkSwitchToTerminationModeAfterLongPress() {
175− compare(calendarTile.terminationModeEnabled, isCalendarLongPressed)
108+ compare(browserTile.terminationModeEnabled, isBrowserLongPressed)
176− compare(phoneTile.terminationModeEnabled, isCalendarLongPressed)
109+ compare(phoneTile.terminationModeEnabled, isBrowserLongPressed)
177− compare(runningApplicationsGrid.terminationModeEnabled, isCalendarLongPressed)
110+ compare(runningApplicationsGrid.terminationModeEnabled, isBrowserLongPressed)
178
111
179− return isCalendarLongPressed &&
112+ return isBrowserLongPressed &&
180− calendarTile.terminationModeEnabled &&
113+ browserTile.terminationModeEnabled &&
181 phoneTile.terminationModeEnabled &&
114 phoneTile.terminationModeEnabled &&
182 runningApplicationsGrid.terminationModeEnabled
115 runningApplicationsGrid.terminationModeEnabled
183 }
116 }
@@ -185,12 +118,12 @@ Item {
185 // Checks that components swicth to activation mode after (and only after) a long
118 // Checks that components swicth to activation mode after (and only after) a long
186 // press happens on Phone tile.
119 // press happens on Phone tile.
187 function checkSwitchToActivationModeAfterLongPress() {
120 function checkSwitchToActivationModeAfterLongPress() {
188− compare(calendarTile.terminationModeEnabled, !isPhoneLongPressed)
121+ compare(browserTile.terminationModeEnabled, !isPhoneLongPressed)
189 compare(phoneTile.terminationModeEnabled, !isPhoneLongPressed)
122 compare(phoneTile.terminationModeEnabled, !isPhoneLongPressed)
190 compare(runningApplicationsGrid.terminationModeEnabled, !isPhoneLongPressed)
123 compare(runningApplicationsGrid.terminationModeEnabled, !isPhoneLongPressed)
191
124
192 return isPhoneLongPressed &&
125 return isPhoneLongPressed &&
193− !calendarTile.terminationModeEnabled &&
126+ !browserTile.terminationModeEnabled &&
194 !phoneTile.terminationModeEnabled &&
127 !phoneTile.terminationModeEnabled &&
195 !runningApplicationsGrid.terminationModeEnabled
128 !runningApplicationsGrid.terminationModeEnabled
196 }
129 }
@@ -200,17 +133,17 @@ Item {
200 function test_clickTileNotClose() {
133 function test_clickTileNotClose() {
201 runningApplicationsGrid.terminationModeEnabled = true
134 runningApplicationsGrid.terminationModeEnabled = true
202
135
203− var calendarTile = findChild(runningApplicationsGrid, "runningAppTile Calendar")
136+ var browserTile = findChild(runningApplicationsGrid, "runningAppTile Browser")
204− verify(calendarTile != undefined)
137+ verify(browserTile != undefined)
205
138
206− verify(fakeRunningAppsModel.contains("calendar"))
139+ verify(ApplicationManager.findApplication("webbrowser-app") !== null)
207
140
208− mouseClick(calendarTile, calendarTile.width/2, calendarTile.height/2)
141+ mouseClick(browserTile, browserTile.width/2, browserTile.height/2)
209
142
210− verify(fakeRunningAppsModel.contains("calendar"))
143+ verify(ApplicationManager.findApplication("webbrowser-app") !== null)
211
144
212− // The tile for the Calendar app should stay there
145+ // The tile for the Browser app should stay there
213− tryCompareFunction(checkCalendarTileExists, true)
146+ tryCompareFunction(checkBrowserTileExists, true)
214 }
147 }
215
148
216 // While in termination mode, clicking on a running application tile's close icon
149 // While in termination mode, clicking on a running application tile's close icon
@@ -218,25 +151,25 @@ Item {
218 function test_clickCloseIconToTerminateApp() {
151 function test_clickCloseIconToTerminateApp() {
219 runningApplicationsGrid.terminationModeEnabled = true
152 runningApplicationsGrid.terminationModeEnabled = true
220
153
221− var calendarTile = findChild(runningApplicationsGrid, "runningAppTile Calendar")
154+ var browserTile = findChild(runningApplicationsGrid, "runningAppTile Browser")
222− var calendarTileCloseButton = findChild(runningApplicationsGrid, "closeIcon Calendar")
155+ var browserTileCloseButton = findChild(runningApplicationsGrid, "closeIcon Browser")
223
156
224− verify(calendarTile != undefined)
157+ verify(browserTile != undefined)
225− verify(calendarTileCloseButton != undefined)
158+ verify(browserTileCloseButton != undefined)
226− verify(fakeRunningAppsModel.contains("calendar"))
159+ verify(ApplicationManager.findApplication("webbrowser-app") !== 0)
227
160
228− mouseClick(calendarTileCloseButton, calendarTileCloseButton.width/2, calendarTileCloseButton.height/2)
161+ mouseClick(browserTileCloseButton, browserTileCloseButton.width/2, browserTileCloseButton.height/2)
229 wait(0) // spin event loop to start any pending animation
162 wait(0) // spin event loop to start any pending animation
230
163
231− verify(!fakeRunningAppsModel.contains("calendar"))
164+ verify(ApplicationManager.findApplication("webbrowser-app") === null)
232
165
233− // The tile for the Calendar app should eventually vanish since the
166+ // The tile for the Browser app should eventually vanish since the
234 // application has been terminated.
167 // application has been terminated.
235− tryCompareFunction(checkCalendarTileExists, false)
168+ tryCompareFunction(checkBrowserTileExists, false)
236 }
169 }
237
170
238− function checkCalendarTileExists() {
171+ function checkBrowserTileExists() {
239− return findChild(runningApplicationsGrid, "runningAppTile Calendar")
172+ return findChild(runningApplicationsGrid, "runningAppTile Browser")
240 != undefined
173 != undefined
241 }
174 }
242
175
@@ -245,8 +178,8 @@ Item {
245 function test_clickOutsideTilesDisablesTerminationMode() {
178 function test_clickOutsideTilesDisablesTerminationMode() {
246 runningApplicationsGrid.terminationModeEnabled = true
179 runningApplicationsGrid.terminationModeEnabled = true
247
180
248− var calendarTile = findChild(runningApplicationsGrid, "runningAppTile Calendar")
181+ var browserTile = findChild(runningApplicationsGrid, "runningAppTile Browser")
249− verify(calendarTile != undefined)
182+ verify(browserTile != undefined)
250
183
251 verify(runningApplicationsGrid.terminationModeEnabled);
184 verify(runningApplicationsGrid.terminationModeEnabled);
252
185
tests/qmltests/Dash/qml/FakeScopeView.qmldeleted−45
@@ -1,45 +0,0 @@
1−/*
2− * Copyright 2013 Canonical Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import Unity 0.2
19−
20−import "../../../../qml/Dash"
21−
22−GenericScopeView {
23− id: fakeScopeView
24−
25− property alias backColor : back.color
26− property var categoryView: null
27−
28− onMovementStarted: {
29− if (shell != undefined && shell.scopeStatus != undefined) {
30− shell.scopeStatus[scope.id].movementStarted++;
31− }
32− }
33− onPositionedAtBeginning: {
34− if (shell != undefined && shell.scopeStatus != undefined) {
35− shell.scopeStatus[scope.id].positionedAtBeginning++;
36− }
37− }
38−
39− Rectangle {
40− id: back
41− anchors.fill: parent
42− color: "grey"
43− z: -1
44− }
45−}
tests/qmltests/Dash/qml/fake_generic_scopeView.qmldeleted−24
@@ -1,24 +0,0 @@
1−/*
2− * Copyright 2013 Canonical Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−
19−FakeScopeView {
20− id: fake_generic_scopeView
21− objectName: "fake_generic_scopeView"
22−
23− backColor: "#D0D0D0"
24−}
tests/qmltests/Dash/qml/fake_scopeView1.qmldeleted−24
@@ -1,24 +0,0 @@
1−/*
2− * Copyright 2013 Canonical Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−
19−FakeScopeView {
20− id: fake_scopeView1
21− objectName: "fake_scopeView1"
22−
23− backColor: "#90E090"
24−}
tests/qmltests/Dash/qml/fake_scopeView2.qmldeleted−24
@@ -1,24 +0,0 @@
1−/*
2− * Copyright 2013 Canonical Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−
19−FakeScopeView {
20− id: fake_scopeView2
21− objectName: "fake_scopeView2"
22−
23− backColor: "#A0D0A0"
24−}
tests/qmltests/Dash/qml/fake_scopeView3.qmldeleted−24
@@ -1,24 +0,0 @@
1−/*
2− * Copyright 2013 Canonical Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−
19−FakeScopeView {
20− id: fake_scopeView3
21− objectName: "fake_scopeView3"
22−
23− backColor: "#B0C0B0"
24−}
tests/qmltests/Dash/qml/fake_scopeView4.qmldeleted−24
@@ -1,24 +0,0 @@
1−/*
2− * Copyright 2013 Canonical Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−
19−FakeScopeView {
20− id: fake_scopeView4
21− objectName: "fake_scopeView4"
22−
23− backColor: "#C0C0C0"
24−}
tests/qmltests/Dash/tst_Card.qml+12−10
@@ -182,11 +182,13 @@ Rectangle {
182 when: windowShown
182 when: windowShown
183
183
184 property Item header: findChild(card, "cardHeader")
184 property Item header: findChild(card, "cardHeader")
185+ property Item headerLoader: findChild(card, "cardHeaderLoader")
185 property Item title: findChild(header, "titleLabel")
186 property Item title: findChild(header, "titleLabel")
186 property Item art: findChild(card, "artShape")
187 property Item art: findChild(card, "artShape")
187 property Item artImage: findChild(card, "artImage")
188 property Item artImage: findChild(card, "artImage")
188 property Item summary: findChild(card, "summaryLabel")
189 property Item summary: findChild(card, "summaryLabel")
189 property Item background: findChild(card, "background")
190 property Item background: findChild(card, "background")
191+ property Item backgroundLoader: findChild(card, "backgroundLoader")
190 property Item backgroundImage: findChild(card, "backgroundImage")
192 property Item backgroundImage: findChild(card, "backgroundImage")
191
193
192 function initTestCase() {
194 function initTestCase() {
@@ -244,8 +246,8 @@ Rectangle {
244 { tag: "Wide", width: units.gu(18.5), aspect: 0.5, index: 0 },
246 { tag: "Wide", width: units.gu(18.5), aspect: 0.5, index: 0 },
245 { tag: "Horizontal", width: units.gu(38), index: 5 },
247 { tag: "Horizontal", width: units.gu(38), index: 5 },
246 // Make sure card ends with header when there's no summary
248 // Make sure card ends with header when there's no summary
247− { tag: "NoSummary", height: function() { return header.y + header.height }, index: 6 },
249+ { tag: "NoSummary", height: function() { return headerLoader.y + headerLoader.height }, index: 6 },
248− { tag: "HorizontalNoSummary", height: function() { return header.height }, card_layout: "horizontal", index: 6 },
250+ { tag: "HorizontalNoSummary", height: function() { return headerLoader.height }, card_layout: "horizontal", index: 6 },
249 ]
251 ]
250 }
252 }
251
253
@@ -288,7 +290,7 @@ Rectangle {
288 { tag: "Fit", height: units.gu(38), size: "large", width: units.gu(19), index: 4 },
290 { tag: "Fit", height: units.gu(38), size: "large", width: units.gu(19), index: 4 },
289 { tag: "VerticalWidth", width: function() { return header.width }, index: 0 },
291 { tag: "VerticalWidth", width: function() { return header.width }, index: 0 },
290 { tag: "HorizontalHeight", height: function() { return header.height }, index: 5 },
292 { tag: "HorizontalHeight", height: function() { return header.height }, index: 5 },
291− { tag: "HorizontalWidth", width: function() { return header.x }, index: 5 },
293+ { tag: "HorizontalWidth", width: function() { return headerLoader.x }, index: 5 },
292 ]
294 ]
293 }
295 }
294
296
@@ -332,7 +334,7 @@ Rectangle {
332 function test_art_layout(data) {
334 function test_art_layout(data) {
333 selector.selectedIndex = data.index;
335 selector.selectedIndex = data.index;
334
336
335− tryCompare(testCase.header, "x", data.left());
337+ tryCompare(testCase.headerLoader, "x", data.left());
336 }
338 }
337
339
338 function test_header_layout_data() {
340 function test_header_layout_data() {
@@ -349,13 +351,13 @@ Rectangle {
349 function test_header_layout(data) {
351 function test_header_layout(data) {
350 selector.selectedIndex = data.index;
352 selector.selectedIndex = data.index;
351
353
352− tryCompareFunction(function() { return testCase.header.y === data.top() }, true);
354+ tryCompareFunction(function() { return testCase.headerLoader.y === data.top() }, true);
353− tryCompareFunction(function() { return testCase.header.x === data.left() }, true);
355+ tryCompareFunction(function() { return testCase.headerLoader.x === data.left() }, true);
354 }
356 }
355
357
356 function test_summary_layout_data() {
358 function test_summary_layout_data() {
357 return [
359 return [
358− { tag: "With header", top: function() { return header.y + header.height }, index: 0 },
360+ { tag: "With header", top: function() { return headerLoader.y + headerLoader.height }, index: 0 },
359 { tag: "Without header", top: function() { return art.y + art.height }, index: 7 },
361 { tag: "Without header", top: function() { return art.y + art.height }, index: 7 },
360 ]
362 ]
361 }
363 }
@@ -400,7 +402,7 @@ Rectangle {
400
402
401 waitForRendering(card);
403 waitForRendering(card);
402
404
403− tryCompare(background, "visible", data.visible);
405+ tryCompare(backgroundLoader, "active", data.visible);
404
406
405 if (data.hasOwnProperty("color")) {
407 if (data.hasOwnProperty("color")) {
406 tryCompare(background, "color", data.color);
408 tryCompare(background, "color", data.color);
@@ -467,13 +469,13 @@ Rectangle {
467 function test_mascotShape(data) {
469 function test_mascotShape(data) {
468 selector.selectedIndex = data.index;
470 selector.selectedIndex = data.index;
469
471
470− var shape = findChild(card, "mascotShape");
472+ var shape = findChild(card, "mascotShapeLoader");
471 var image = findChild(card, "mascotImage");
473 var image = findChild(card, "mascotImage");
472
474
473 verify(shape, "Could not find shape.");
475 verify(shape, "Could not find shape.");
474 verify(image, "Could not find image.");
476 verify(image, "Could not find image.");
475
477
476− tryCompare(shape, "visible", data.shape);
478+ tryCompare(shape, "active", data.shape);
477 tryCompare(image, "visible", !data.shape);
479 tryCompare(image, "visible", !data.shape);
478 }
480 }
479 }
481 }
tests/qmltests/Dash/tst_CardBenchmark.qmlnew file+94
@@ -0,0 +1,94 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import QtTest 1.0
19+import Ubuntu.Components 0.1
20+import Unity.Test 0.1 as UT
21+import "../../../qml/Dash"
22+import "CardHelpers.js" as Helpers
23+
24+Rectangle {
25+ id: root
26+ width: units.gu(80)
27+ height: units.gu(72)
28+ color: "#88FFFFFF"
29+
30+ property string cardData: '
31+ {
32+ "art": "../../tests/qmltests/Dash/artwork/music-player-design.png",
33+ "mascot": "../../tests/qmltests/Dash/artwork/avatar.png",
34+ "title": "foo",
35+ "subtitle": "bar",
36+ "summary": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
37+ }'
38+
39+ property string currentModel: '{}'
40+ property string cardTitleArtSubtitleMascotSummaryModel: '{ "components": { "title": "title", "art": "art", "subtitle": "subtitle", "mascot": "mascot", "summary": "summary" } }'
41+ property string cardTitleArtSubtitleMascotModel: '{ "components": { "title": "title", "art": "art", "subtitle": "subtitle", "mascot": "mascot" } }'
42+ property string cardTitleArtSubtitleModel: '{ "components": { "title": "title", "art": "art", "subtitle": "subtitle" } }'
43+ property string cardTitleArtModel: '{ "components": { "title": "title", "art": "art" } }'
44+ property string cardArtModel: '{ "components": { "art": "art" } }'
45+ property string cardTitleModel: '{ "components": { "title": "title" } }'
46+
47+ CardTool {
48+ id: cardTool
49+ template: Helpers.update(JSON.parse(Helpers.defaultLayout), JSON.parse(currentModel))['template'];
50+ components: Helpers.update(JSON.parse(Helpers.defaultLayout), JSON.parse(currentModel))['components'];
51+ viewWidth: units.gu(48)
52+ }
53+
54+ Repeater {
55+ id: cardRepeater
56+ model: 0
57+ Card {
58+ width: cardTool.cardWidth || implicitWidth
59+ height: cardTool.cardHeight || implicitHeight
60+
61+ template: cardTool.template
62+ components: cardTool.components
63+ cardData: Helpers.mapData(root.cardData, components)
64+ }
65+ }
66+
67+ UT.UnityTestCase {
68+ id: testCase
69+ name: "CardBenchmark"
70+
71+ when: windowShown
72+
73+ function init() {
74+ wait(1);
75+ }
76+
77+ function benchmark_time_data() {
78+ return [
79+ { tag: "cardTitleArtSubtitleMascotSummaryModel", model: cardTitleArtSubtitleMascotSummaryModel },
80+ { tag: "cardTitleArtSubtitleMascotModel", model: cardTitleArtSubtitleMascotModel },
81+ { tag: "cardTitleArtSubtitleModel", model: cardTitleArtSubtitleModel },
82+ { tag: "cardTitleArtModel", model: cardTitleArtModel },
83+ { tag: "cardArtModel", model: cardArtModel },
84+ { tag: "cardTitleModel", model: cardTitleModel },
85+ ];
86+ }
87+
88+ function benchmark_time(data) {
89+ currentModel = data.model;
90+ cardRepeater.model = 1;
91+ cardRepeater.model = 0;
92+ }
93+ }
94+}
tests/qmltests/Dash/tst_CardHeader.qml+1−1
@@ -43,7 +43,7 @@ Rectangle {
43
43
44 when: windowShown
44 when: windowShown
45
45
46− property Item mascot: findChild(cardHeader, "mascotShape")
46+ property Item mascot: findChild(cardHeader, "mascotShapeLoader")
47 property Item titleLabel: findChild(cardHeader, "titleLabel")
47 property Item titleLabel: findChild(cardHeader, "titleLabel")
48 property Item subtitleLabel: findChild(cardHeader, "subtitleLabel")
48 property Item subtitleLabel: findChild(cardHeader, "subtitleLabel")
49 property Item oldPriceLabel: findChild(cardHeader, "oldPriceLabel")
49 property Item oldPriceLabel: findChild(cardHeader, "oldPriceLabel")
tests/qmltests/Dash/tst_Dash.qml−11
@@ -41,16 +41,6 @@ Item {
41 showScopeOnLoaded: "MockScope2"
41 showScopeOnLoaded: "MockScope2"
42 }
42 }
43
43
44− ScopeDelegateMapper {
45− id: scopeDelegateMapper
46− scopeDelegateMapping: {
47− "MockScope1": Qt.resolvedUrl("qml/fake_scopeView1.qml"),
48− "MockScope2": Qt.resolvedUrl("qml/fake_scopeView2.qml"),
49− "clickscope": Qt.resolvedUrl("qml/fake_scopeView3.qml")
50− }
51− genericScope: Qt.resolvedUrl("qml/fake_generic_scopeView.qml")
52− }
53−
54 UT.UnityTestCase {
44 UT.UnityTestCase {
55 name: "Dash"
45 name: "Dash"
56 when: windowShown
46 when: windowShown
@@ -59,7 +49,6 @@ Item {
59
49
60 Component.onCompleted: {
50 Component.onCompleted: {
61 var dashContent = findChild(dash, "dashContent");
51 var dashContent = findChild(dash, "dashContent");
62− dashContent.scopeMapper = scopeDelegateMapper;
63 scopes = dashContent.scopes;
52 scopes = dashContent.scopes;
64 }
53 }
65
54
tests/qmltests/Dash/tst_DashContent.qml+27−51
@@ -35,13 +35,6 @@ Item {
35 property var panel: null
35 property var panel: null
36 // BEGIN To reduce warnings
36 // BEGIN To reduce warnings
37
37
38− property var scopeStatus: {
39− 'MockScope1': { 'movementStarted': 0, 'positionedAtBeginning': 0 },
40− 'MockScope2': { 'movementStarted': 0, 'positionedAtBeginning': 0 },
41− 'clickscope': { 'movementStarted': 0, 'positionedAtBeginning': 0 },
42− 'MockScope5': { 'movementStarted': 0, 'positionedAtBeginning': 0 }
43− }
44−
45 Scopes {
38 Scopes {
46 id: scopesModel
39 id: scopesModel
47 }
40 }
@@ -55,34 +48,9 @@ Item {
55 }
48 }
56 scopes : scopesModel
49 scopes : scopesModel
57
50
58− scopeMapper : scopeDelegateMapper
59 searchHistory: SearchHistoryModel {}
51 searchHistory: SearchHistoryModel {}
60 }
52 }
61
53
62− ScopeDelegateMapper {
63− id: scopeDelegateMapper
64− scopeDelegateMapping: {
65− "MockScope1": Qt.resolvedUrl("qml/fake_scopeView1.qml"),
66− "MockScope2": Qt.resolvedUrl("qml/fake_scopeView2.qml"),
67− "clickscope": Qt.resolvedUrl("qml/fake_scopeView3.qml"),
68− }
69− genericScope: Qt.resolvedUrl("qml/fake_generic_scopeView.qml")
70− }
71−
72− function clear_scope_status() {
73− scopeStatus["MockScope1"].movementStarted = 0;
74− scopeStatus["MockScope1"].positionedAtBeginning = 0;
75−
76− scopeStatus["MockScope2"].movementStarted = 0;
77− scopeStatus["MockScope2"].positionedAtBeginning = 0;
78−
79− scopeStatus["clickscope"].movementStarted = 0;
80− scopeStatus["clickscope"].positionedAtBeginning = 0;
81−
82− scopeStatus["MockScope5"].movementStarted = 0;
83− scopeStatus["MockScope5"].positionedAtBeginning = 0;
84− }
85−
86 SignalSpy {
54 SignalSpy {
87 id: scopeLoadedSpy
55 id: scopeLoadedSpy
88 target: dashContent
56 target: dashContent
@@ -97,7 +65,7 @@ Item {
97
65
98 UT.UnityTestCase {
66 UT.UnityTestCase {
99 name: "DashContent"
67 name: "DashContent"
100− when: scopesModel.loaded
68+ when: scopesModel.loaded && windowShown
101
69
102 function init() {
70 function init() {
103 scopesModel.clear();
71 scopesModel.clear();
@@ -108,7 +76,6 @@ Item {
108
76
109 function cleanup() {
77 function cleanup() {
110 movementStartedSpy.clear();
78 movementStartedSpy.clear();
111− clear_scope_status();
112 dashContent.visible = true;
79 dashContent.visible = true;
113
80
114 var dashContentList = findChild(dashContent, "dashContentList");
81 var dashContentList = findChild(dashContent, "dashContentList");
@@ -149,29 +116,38 @@ Item {
149 verify(dashContentList.currentIndex >= 0 && dashContentList.currentIndex < 5);
116 verify(dashContentList.currentIndex >= 0 && dashContentList.currentIndex < 5);
150 }
117 }
151
118
152− function test_movement_started_signal() {
119+ function test_show_header_on_list_movement() {
153− dashContent.setCurrentScopeAtIndex(3, true, false);
154−
155 var dashContentList = findChild(dashContent, "dashContentList");
120 var dashContentList = findChild(dashContent, "dashContentList");
156− verify(dashContentList != undefined)
121+ verify(dashContentList != undefined);
122+ var categoryListView = findChild(dashContentList, "categoryListView");
123+ verify(categoryListView != undefined);
124+
125+ waitForRendering(categoryListView);
157
126
158− dashContentList.movementStarted();
127+ categoryListView.contentY = units.gu(11);
128+ console.log("contentY", categoryListView.contentY);
159
129
160− compare(movementStartedSpy.count, 1, "DashContent should have emitted movementStarted signal when content list did.");
130+ var startX = dashContentList.width/2;
161− compare(scopeStatus["MockScope1"].movementStarted, 1, "MockScope1 should have emitted movementStarted signal when content list did.");
131+ var startY = dashContentList.height/2;
162− compare(scopeStatus["MockScope2"].movementStarted, 1, "MockScope2 should have emitted movementStarted signal when content list did.");
132+ touchFlick(dashContentList, startX - units.gu(2), startY, startX, startY);
163− compare(scopeStatus["clickscope"].movementStarted, 1, "clickscope should have emitted movementStarted signal when content list did.");
133+ tryCompare(categoryListView, "contentY", units.gu(11) - categoryListView.pageHeader.height);
164− compare(scopeStatus["MockScope5"].movementStarted, 1, "MockScope5 should have emitted movementStarted signal when content list did.");
165 }
134 }
166
135
167− function test_positioned_at_beginning_signal() {
136+ function test_set_current_scope_reset() {
137+ var dashContentList = findChild(dashContent, "dashContentList");
138+ verify(dashContentList != undefined);
139+ var categoryListView = findChild(dashContentList, "categoryListView");
140+ verify(categoryListView != undefined);
141+
142+ categoryListView.contentY = units.gu(10);
143+
144+ compare(dashContentList.currentItem.item.objectName, "MockScope1")
145+ compare(categoryListView.contentY, units.gu(10));
146+
168− dashContent.setCurrentScopeAtIndex(3, true, false);
147+ dashContent.setCurrentScopeAtIndex(0, false, true);
169
148
170− dashContent.positionedAtBeginning();
149+ compare(dashContentList.currentItem.item.objectName, "MockScope1")
171− compare(scopeStatus["MockScope1"].positionedAtBeginning, 1, "MockScope1 should have emitted positionedAtBeginning signal when DashContent did.");
150+ compare(categoryListView.contentY, 0);
172− compare(scopeStatus["MockScope2"].positionedAtBeginning, 1, "MockScope2 should have emitted positionedAtBeginning signal when DashContent did.");
173− compare(scopeStatus["clickscope"].positionedAtBeginning, 1, "clickscope should have emitted positionedAtBeginning signal when DashContent did.");
174− compare(scopeStatus["MockScope5"].positionedAtBeginning, 1, "MockScope5 should have emitted positionedAtBeginning signal when DashContent did.");
175 }
151 }
176
152
177 // This tests that setting the current scope index will end up at the correct index even if
153 // This tests that setting the current scope index will end up at the correct index even if
tests/qmltests/Dash/tst_GenericScopeView.qml+14−2
@@ -64,9 +64,10 @@ Item {
64
64
65 UT.UnityTestCase {
65 UT.UnityTestCase {
66 name: "GenericScopeView"
66 name: "GenericScopeView"
67− when: scopes.loaded
67+ when: scopes.loaded && windowShown
68
68
69 function init() {
69 function init() {
70+ genericScopeView.scope = scopes.get(2)
70 shell.width = units.gu(120)
71 shell.width = units.gu(120)
71 genericScopeView.categoryView.positionAtBeginning();
72 genericScopeView.categoryView.positionAtBeginning();
72 tryCompare(genericScopeView.categoryView, "contentY", 0)
73 tryCompare(genericScopeView.categoryView, "contentY", 0)
@@ -105,11 +106,22 @@ Item {
105 tryCompare(previewListView, "open", false);
106 tryCompare(previewListView, "open", false);
106 }
107 }
107
108
109+ function test_searchQuery() {
110+ genericScopeView.scope = scopes.get(0);
111+ genericScopeView.scope.searchQuery = "test";
112+ genericScopeView.scope = scopes.get(1);
113+ genericScopeView.scope.searchQuery = "test2";
114+ genericScopeView.scope = scopes.get(0);
115+ tryCompare(genericScopeView.scope, "searchQuery", "test");
116+ genericScopeView.scope = scopes.get(1);
117+ tryCompare(genericScopeView.scope, "searchQuery", "test2");
118+ }
119+
108 function test_changeScope() {
120 function test_changeScope() {
109 genericScopeView.scope.searchQuery = "test"
121 genericScopeView.scope.searchQuery = "test"
110 genericScopeView.scope = scopes.get(1)
122 genericScopeView.scope = scopes.get(1)
111 genericScopeView.scope = scopes.get(2)
123 genericScopeView.scope = scopes.get(2)
112− tryCompare(genericScopeView.scope, "searchQuery", "")
124+ tryCompare(genericScopeView.scope, "searchQuery", "test")
113 }
125 }
114
126
115 function test_filter_expand_collapse() {
127 function test_filter_expand_collapse() {
tests/qmltests/Launcher/tst_Launcher.qml+1
@@ -66,6 +66,7 @@ Item {
66
66
67 UT.UnityTestCase {
67 UT.UnityTestCase {
68 id: revealer
68 id: revealer
69+ when: windowShown
69
70
70 function dragLauncherIntoView() {
71 function dragLauncherIntoView() {
71 var startX = launcher.dragAreaWidth/2;
72 var startX = launcher.dragAreaWidth/2;
tests/qmltests/Notifications/tst_VisualSnapDecisionsQueue.qml+1
@@ -193,6 +193,7 @@ Row {
193 addSomeSnapDecisionNotifications();
193 addSomeSnapDecisionNotifications();
194
194
195 // make sure the view is properly updated before going on
195 // make sure the view is properly updated before going on
196+ notifications.forceLayout();
196 waitForRendering(notifications);
197 waitForRendering(notifications);
197
198
198 var snap_decision = [findChild(notifications, "notification1"),
199 var snap_decision = [findChild(notifications, "notification1"),
tests/qmltests/Panel/Indicators/tst_MenuItemFactory.qml+1
@@ -44,6 +44,7 @@ Item {
44
44
45 UT.UnityTestCase {
45 UT.UnityTestCase {
46 name: "MenuItemFactory"
46 name: "MenuItemFactory"
47+ when: windowShown
47
48
48 property QtObject menuData: QtObject {
49 property QtObject menuData: QtObject {
49 property string label: "root"
50 property string label: "root"
tests/qmltests/Panel/Indicators/tst_MessageMenuItemFactory.qml+1
@@ -39,6 +39,7 @@ Item {
39
39
40 UT.UnityTestCase {
40 UT.UnityTestCase {
41 name: "MessageMenuItemFactory"
41 name: "MessageMenuItemFactory"
42+ when: windowShown
42
43
43 property QtObject menuData: QtObject {
44 property QtObject menuData: QtObject {
44 property string label: "root"
45 property string label: "root"
tests/qmltests/SideStage/tst_SideStage.qmldeleted−52
@@ -1,52 +0,0 @@
1−/*
2− * Copyright 2013 Canonical Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import QtTest 1.0
19−import Unity.Test 0.1 as UT
20−import ".."
21−import "../../../qml/SideStage"
22−import Ubuntu.Components 0.1
23−
24−UT.UnityTestCase {
25− name: "SideStage"
26−
27− SideStage {
28− id: sideStage
29− width: units.gu(30)
30− height: units.gu(30)
31− handleExpanded: sideStageRevealer.pressed
32− handleSizeCollapsed: units.gu(2)
33− handleSizeExpanded: units.gu(5)
34− }
35−
36− function test_handle_data() {
37− return [
38− {tag: "expanded", expanded: true},
39− {tag: "collapsed", expanded: false}
40− ]
41− }
42−
43− function test_handle(data) {
44− sideStage.handleExpanded = data.expanded
45−
46− if (data.expanded) {
47− tryCompare(findChild(sideStage, "sideStageHandle"), "width", sideStage.handleSizeExpanded);
48− } else {
49− tryCompare(findChild(sideStage, "sideStageHandle"), "width", sideStage.handleSizeCollapsed);
50− }
51− }
52−}
tests/qmltests/Stages/tst_PhoneStage.qmlnew file+288
@@ -0,0 +1,288 @@
1+/*
2+ * Copyright 2014 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import QtTest 1.0
19+import Unity.Test 0.1 as UT
20+import ".."
21+import "../../../qml/Stages"
22+import Ubuntu.Components 0.1
23+import Unity.Application 0.1
24+
25+Item {
26+ width: units.gu(70)
27+ height: units.gu(70)
28+
29+ Rectangle {
30+
31+ }
32+
33+ PhoneStage {
34+ id: phoneStage
35+ anchors { fill: parent; rightMargin: units.gu(30) }
36+ shown: true
37+ dragAreaWidth: units.gu(2)
38+ }
39+
40+ Binding {
41+ target: ApplicationManager
42+ property: "rightMargin"
43+ value: phoneStage.anchors.rightMargin
44+ }
45+
46+ Rectangle {
47+ anchors { fill: parent; leftMargin: phoneStage.width }
48+// color: "blue"
49+
50+ Column {
51+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
52+ spacing: units.gu(1)
53+ Button {
54+ anchors { left: parent.left; right: parent.right }
55+ text: "Add App"
56+ onClicked: {
57+ testCase.addApps();
58+ }
59+ }
60+ Button {
61+ anchors { left: parent.left; right: parent.right }
62+ text: "Add App"
63+ }
64+ }
65+ }
66+
67+ UT.UnityTestCase {
68+ id: testCase
69+ name: "PhoneStage"
70+ when: windowShown
71+
72+ function addApps(count) {
73+ if (count == undefined) count = 1;
74+ for (var i = 0; i < count; i++) {
75+ var app = ApplicationManager.startApplication(ApplicationManager.availableApplications()[ApplicationManager.count])
76+ tryCompare(app, "state", ApplicationInfoInterface.Running)
77+ // Fixme: Right now there is a timeout in the PhoneStage that displays a white splash
78+ // screen rectangle when an app starts. This is because we don't yet have a way of
79+ // knowing when an app has finished launching. That workaround and this wait() should
80+ // go away at some point and the app's state only changing to Running when ready for real.
81+// wait(1000)
82+ waitForRendering(phoneStage)
83+ }
84+ }
85+
86+ function goToSpread() {
87+ var spreadView = findChild(phoneStage, "spreadView");
88+
89+ var startX = phoneStage.width;
90+ var startY = phoneStage.height / 2;
91+ var endY = startY;
92+ var endX = units.gu(2);
93+
94+ touchFlick(phoneStage, startX, startY, endX, endY,
95+ true /* beginTouch */, true /* endTouch */, units.gu(10), 50);
96+ }
97+
98+ function test_shortFlick() {
99+ addApps(2)
100+ var startX = phoneStage.width - units.gu(1);
101+ var startY = phoneStage.height / 2;
102+ var endX = phoneStage.width / 2;
103+ var endY = startY;
104+
105+ var activeApp = ApplicationManager.get(0);
106+ var inactiveApp = ApplicationManager.get(1);
107+
108+ touchFlick(phoneStage, startX, startY, endX, endY,
109+ true /* beginTouch */, true /* endTouch */, units.gu(10), 50);
110+
111+ tryCompare(ApplicationManager, "focusedApplicationId", inactiveApp.appId)
112+
113+ touchFlick(phoneStage, startX, startY, endX, endY,
114+ true /* beginTouch */, true /* endTouch */, units.gu(10), 50);
115+
116+ tryCompare(ApplicationManager, "focusedApplicationId", activeApp.appId)
117+
118+ tryCompare(phoneStage, "painting", false);
119+ }
120+
121+ function test_enterSpread_data() {
122+ return [
123+ {tag: "<position1 (linear movement)", positionMarker: "positionMarker1", linear: true, offset: -1, endPhase: 0, targetPhase: 0, newFocusedIndex: 1 },
124+ {tag: "<position1 (non-linear movement)", positionMarker: "positionMarker1", linear: false, offset: -1, endPhase: 0, targetPhase: 0, newFocusedIndex: 0 },
125+ {tag: ">position1", positionMarker: "positionMarker1", linear: true, offset: +1, endPhase: 0, targetPhase: 0, newFocusedIndex: 1 },
126+ {tag: "<position2 (linear)", positionMarker: "positionMarker2", linear: true, offset: -1, endPhase: 0, targetPhase: 0, newFocusedIndex: 1 },
127+ {tag: "<position2 (non-linear)", positionMarker: "positionMarker2", linear: false, offset: -1, endPhase: 0, targetPhase: 0, newFocusedIndex: 1 },
128+ {tag: ">position2", positionMarker: "positionMarker2", linear: true, offset: +1, endPhase: 1, targetPhase: 0, newFocusedIndex: 1 },
129+ {tag: "<position3", positionMarker: "positionMarker3", linear: true, offset: -1, endPhase: 1, targetPhase: 0, newFocusedIndex: 1 },
130+ {tag: ">position3", positionMarker: "positionMarker3", linear: true, offset: +1, endPhase: 2, targetPhase: 2, newFocusedIndex: 2 },
131+ ];
132+ }
133+
134+ function test_enterSpread(data) {
135+ addApps(5)
136+
137+ var spreadView = findChild(phoneStage, "spreadView");
138+
139+ var startX = phoneStage.width;
140+ var startY = phoneStage.height / 2;
141+ var endY = startY;
142+ var endX = spreadView.width - (spreadView.width * spreadView[data.positionMarker]) - data.offset - phoneStage.dragAreaWidth;
143+
144+ var oldFocusedApp = ApplicationManager.get(0);
145+ var newFocusedApp = ApplicationManager.get(data.newFocusedIndex);
146+
147+ touchFlick(phoneStage, startX, startY, endX, endY,
148+ true /* beginTouch */, false /* endTouch */, units.gu(10), 50);
149+
150+ tryCompare(spreadView, "phase", data.endPhase)
151+
152+ if (!data.linear) {
153+ touchFlick(phoneStage, endX, endY, endX + units.gu(.5), endY,
154+ false /* beginTouch */, false /* endTouch */, units.gu(10), 50);
155+ touchFlick(phoneStage, endY + units.gu(.5), endY, endX, endY,
156+ false /* beginTouch */, false /* endTouch */, units.gu(10), 50);
157+ }
158+
159+ touchRelease(phoneStage, endX, endY);
160+
161+ tryCompare(spreadView, "phase", data.targetPhase)
162+
163+ if (data.targetPhase == 2) {
164+ var app2 = findChild(spreadView, "appDelegate2");
165+ mouseClick(app2, units.gu(1), units.gu(1));
166+ }
167+
168+ tryCompare(phoneStage, "painting", false);
169+ tryCompare(ApplicationManager, "focusedApplicationId", newFocusedApp.appId);
170+ }
171+
172+ function test_selectAppFromSpread_data() {
173+ var appsToTest = 6;
174+ var apps = new Array();
175+ for (var i = 0; i < appsToTest; i++) {
176+ var item = new Object();
177+ item.tag = "App " + i;
178+ item.index = i;
179+ item.total = appsToTest;
180+ apps.push(item)
181+ }
182+ return apps;
183+ }
184+
185+ function test_selectAppFromSpread(data) {
186+ addApps(data.total)
187+
188+ var spreadView = findChild(phoneStage, "spreadView");
189+
190+ goToSpread();
191+
192+ tryCompare(spreadView, "phase", 2);
193+
194+ var tile = findChild(spreadView, "appDelegate" + data.index);
195+ var appId = ApplicationManager.get(data.index).appId;
196+
197+ if (tile.mapToItem(spreadView).x > spreadView.width) {
198+ // Item is not visible... Need to flick the spread
199+ var startX = phoneStage.width - units.gu(1);
200+ var startY = phoneStage.height / 2;
201+ var endY = startY;
202+ var endX = units.gu(2);
203+ touchFlick(phoneStage, startX, startY, endX, endY, true, true, units.gu(10), 50)
204+ tryCompare(spreadView, "flicking", false);
205+ tryCompare(spreadView, "moving", false);
206+// waitForRendering(phoneStage);
207+ }
208+
209+ console.log("clicking app", data.index, "(", appId, ")")
210+ mouseClick(spreadView, tile.mapToItem(spreadView).x + units.gu(1), spreadView.height / 2)
211+ tryCompare(ApplicationManager, "focusedApplicationId", appId);
212+ tryCompare(spreadView, "phase", 0);
213+ }
214+
215+ function test_animateAppStartup() {
216+ compare(phoneStage.painting, false);
217+ addApps(2);
218+ tryCompare(phoneStage, "painting", true);
219+ tryCompare(phoneStage, "painting", false);
220+ addApps(1);
221+ tryCompare(phoneStage, "painting", true);
222+ tryCompare(phoneStage, "painting", false);
223+ }
224+
225+ function test_select_data() {
226+ return [
227+ { tag: "0", index: 0 },
228+ { tag: "2", index: 2 },
229+ { tag: "4", index: 4 },
230+ ]
231+ }
232+
233+ function test_select(data) {
234+ addApps(5);
235+
236+ var spreadView = findChild(phoneStage, "spreadView");
237+ var selectedApp = ApplicationManager.get(data.index);
238+
239+ goToSpread();
240+
241+ phoneStage.select(selectedApp.appId);
242+
243+ tryCompare(phoneStage, "painting", false);
244+ compare(ApplicationManager.focusedApplicationId, selectedApp.appId);
245+ }
246+
247+ function test_fullscreenMode() {
248+ var fullscreenApp = null;
249+ var normalApp = null;
250+
251+ for (var i = 0; i < 5; i++) {
252+ addApps(1);
253+ var newApp = ApplicationManager.get(0);
254+ tryCompare(phoneStage, "fullscreen", newApp.fullscreen);
255+ if (newApp.fullscreen && fullscreenApp == null) {
256+ fullscreenApp = newApp;
257+ } else if (!newApp.fullscreen && normalApp == null){
258+ normalApp = newApp;
259+ }
260+ }
261+ verify(fullscreenApp != null); // Can't continue the test without having a fullscreen app
262+ verify(normalApp != null); // Can't continue the test without having a non-fullscreen app
263+
264+ // Select a normal app
265+ goToSpread();
266+ phoneStage.select(normalApp.appId);
267+ tryCompare(phoneStage, "fullscreen", false);
268+
269+ // Select a fullscreen app
270+ goToSpread();
271+ phoneStage.select(fullscreenApp.appId);
272+ tryCompare(phoneStage, "fullscreen", true);
273+
274+ // Select a normal app
275+ goToSpread();
276+ phoneStage.select(normalApp.appId);
277+ tryCompare(phoneStage, "fullscreen", false);
278+ }
279+
280+ function cleanup() {
281+ while (ApplicationManager.count > 0) {
282+ var oldCount = ApplicationManager.count;
283+ ApplicationManager.stopApplication(ApplicationManager.get(0).appId)
284+ tryCompare(ApplicationManager, "count", oldCount - 1)
285+ }
286+ }
287+ }
288+}
tests/qmltests/tst_Shell.qml+27−8
@@ -117,6 +117,7 @@ Item {
117 while (apps.count > 0) {
117 while (apps.count > 0) {
118 ApplicationManager.stopApplication(apps.get(0).appId);
118 ApplicationManager.stopApplication(apps.get(0).appId);
119 }
119 }
120+ compare(ApplicationManager.count, 0)
120 }
121 }
121
122
122 /*
123 /*
@@ -187,26 +188,34 @@ Item {
187 tapOnAppIconInLauncher();
188 tapOnAppIconInLauncher();
188 waitUntilApplicationWindowIsFullyVisible();
189 waitUntilApplicationWindowIsFullyVisible();
189
190
190− var mainApp = ApplicationManager.focusedApplicationId;
191+ var mainAppId = ApplicationManager.focusedApplicationId;
191− verify(mainApp != "");
192+ verify(mainAppId != "");
193+ var mainApp = ApplicationManager.findApplication(mainAppId);
194+ verify(mainApp);
195+ tryCompare(mainApp, "state", ApplicationInfo.Running);
192
196
193 // Try to suspend while proximity is engaged...
197 // Try to suspend while proximity is engaged...
194 Powerd.displayPowerStateChange(Powerd.Off, Powerd.UseProximity);
198 Powerd.displayPowerStateChange(Powerd.Off, Powerd.UseProximity);
195 tryCompare(greeter, "showProgress", 0);
199 tryCompare(greeter, "showProgress", 0);
196
200
197 // Now really suspend
201 // Now really suspend
202+ print("suspending")
198 Powerd.displayPowerStateChange(Powerd.Off, 0);
203 Powerd.displayPowerStateChange(Powerd.Off, 0);
204+ print("done suspending")
199 tryCompare(greeter, "showProgress", 1);
205 tryCompare(greeter, "showProgress", 1);
206+
200− tryCompare(ApplicationManager, "focusedApplicationId", "");
207+ tryCompare(ApplicationManager, "suspended", true);
208+ compare(mainApp.state, ApplicationInfo.Suspended);
201
209
202 // And wake up
210 // And wake up
203 Powerd.displayPowerStateChange(Powerd.On, 0);
211 Powerd.displayPowerStateChange(Powerd.On, 0);
204− tryCompare(ApplicationManager, "focusedApplicationId", "");
205 tryCompare(greeter, "showProgress", 1);
212 tryCompare(greeter, "showProgress", 1);
206
213
207 // Swipe away greeter to focus app
214 // Swipe away greeter to focus app
208 swipeAwayGreeter();
215 swipeAwayGreeter();
209− tryCompare(ApplicationManager, "focusedApplicationId", mainApp);
216+ tryCompare(ApplicationManager, "suspended", false);
217+ compare(mainApp.state, ApplicationInfo.Running);
218+ tryCompare(ApplicationManager, "focusedApplicationId", mainAppId);
210 }
219 }
211
220
212 function swipeAwayGreeter() {
221 function swipeAwayGreeter() {
@@ -239,7 +248,7 @@ Item {
239 tryCompare(dash, "opacity", 1.0);
248 tryCompare(dash, "opacity", 1.0);
240
249
241 touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY,
250 touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY,
242− true /* beginTouch */, false /* endTouch */);
251+ true /* beginTouch */, false /* endTouch */, units.gu(10), 50);
243
252
244 // check that Dash has been scaled down and had its opacity reduced
253 // check that Dash has been scaled down and had its opacity reduced
245 tryCompareFunction(function() { return dash.contentScale <= 0.9; }, true);
254 tryCompareFunction(function() { return dash.contentScale <= 0.9; }, true);
@@ -270,7 +279,7 @@ Item {
270 tryCompare(dash, "opacity", 1.0);
279 tryCompare(dash, "opacity", 1.0);
271
280
272 touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY,
281 touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY,
273− true /* beginTouch */, false /* endTouch */);
282+ true /* beginTouch */, false /* endTouch */, units.gu(10), 50);
274
283
275 // check that Dash has been scaled down and had its opacity reduced
284 // check that Dash has been scaled down and had its opacity reduced
276 tryCompareFunction(function() { return dash.contentScale <= 0.9; }, true);
285 tryCompareFunction(function() { return dash.contentScale <= 0.9; }, true);
@@ -486,7 +495,6 @@ Item {
486 }
495 }
487
496
488 function test_DashShown(data) {
497 function test_DashShown(data) {
489−
490 if (data.greeter) {
498 if (data.greeter) {
491 // Swipe the greeter in
499 // Swipe the greeter in
492 var greeter = findChild(shell, "greeter");
500 var greeter = findChild(shell, "greeter");
@@ -546,5 +554,16 @@ Item {
546
554
547 tryCompare(searchIndicator, "opacity", 0);
555 tryCompare(searchIndicator, "opacity", 0);
548 }
556 }
557+
558+ function test_focusRequestedHidesGreeter() {
559+ var greeter = findChild(shell, "greeter")
560+
561+ greeter.show()
562+ tryCompare(greeter, "showProgress", 1)
563+
564+ ApplicationManager.focusRequested("notes-app")
565+ tryCompare(greeter, "showProgress", 0)
566+ waitUntilApplicationWindowIsFullyVisible()
567+ }
549 }
568 }
550}
569}
tests/uqmlscene/main.cpp+13
@@ -341,6 +341,16 @@ static void loadDummyDataFiles(QQmlEngine &engine, const QString& directory)
341 }
341 }
342}
342}
343
343
344+static QObject *s_testRootObject = nullptr;
345+static QObject *testRootObject(QQmlEngine *engine, QJSEngine *jsEngine)
346+{
347+ Q_UNUSED(jsEngine);
348+ if (!s_testRootObject) {
349+ s_testRootObject = new QObject(engine);
350+ }
351+ return s_testRootObject;
352+}
353+
344static void usage()
354static void usage()
345{
355{
346 qWarning("Usage: uqmlscene [options] <filename>");
356 qWarning("Usage: uqmlscene [options] <filename>");
@@ -470,6 +480,9 @@ int main(int argc, char ** argv)
470 loadDummyDataFiles(engine, fi.path());
480 loadDummyDataFiles(engine, fi.path());
471 }
481 }
472 QObject::connect(&engine, SIGNAL(quit()), QCoreApplication::instance(), SLOT(quit()));
482 QObject::connect(&engine, SIGNAL(quit()), QCoreApplication::instance(), SLOT(quit()));
483+
484+ qmlRegisterSingletonType<QObject>("Qt.test.qtestroot", 1, 0, "QTestRootObject", testRootObject);
485+
473 component->loadUrl(options.file);
486 component->loadUrl(options.file);
474 if ( !component->isReady() ) {
487 if ( !component->isReady() ) {
475 qWarning("%s", qPrintable(component->errorString()));
488 qWarning("%s", qPrintable(component->errorString()));
tests/utils/modules/Unity/Test/UnityTestCase.qml+26
@@ -260,6 +260,32 @@ TestCase {
260 }
260 }
261 }
261 }
262
262
263+ function touchPinch(item, x1Start, y1Start, x1End, y1End, x2Start, y2Start, x2End, y2End) {
264+ // Make sure the item is rendered
265+ waitForRendering(item);
266+
267+ var event1 = touchEvent();
268+ // first finger
269+ event1.press(0, x1Start, y1Start);
270+ event1.commit();
271+ // second finger
272+ event1.stationary(0);
273+ event1.press(1, x2Start, y2Start);
274+ event1.commit();
275+
276+ // pinch
277+ for (var i = 0.0; i < 1.0; i += 0.02) {
278+ event1.move(0, x1Start + (x1End - x1Start) * i, y1Start + (y1End - y1Start) * i);
279+ event1.move(1, x2Start + (x2End - x2Start) * i, y2Start + (y2End - y2Start) * i);
280+ event1.commit();
281+ }
282+
283+ // release
284+ event1.release(0, x1End, y1End);
285+ event1.release(1, x2End, y2End);
286+ event1.commit();
287+ }
288+
263 function fetchRootItem(item) {
289 function fetchRootItem(item) {
264 if (item.parent)
290 if (item.parent)
265 return fetchRootItem(item.parent)
291 return fetchRootItem(item.parent)
tools/CMakeLists.txt+2
@@ -9,3 +9,5 @@ qt5_use_modules(${SCOPE_TOOL} Qml Quick)
9install(TARGETS ${SCOPE_TOOL}
9install(TARGETS ${SCOPE_TOOL}
10 RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
10 RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
11 )
11 )
12+
13+install(FILES unlock-device DESTINATION ${SHELL_APP_DIR})
tools/scopetool.cpp+1
@@ -88,6 +88,7 @@ int main(int argc, char *argv[])
88
88
89 QUrl source(::qmlDirectory() + "ScopeTool.qml");
89 QUrl source(::qmlDirectory() + "ScopeTool.qml");
90 prependImportPaths(view->engine(), ::overrideImportPaths());
90 prependImportPaths(view->engine(), ::overrideImportPaths());
91+ prependImportPaths(view->engine(), ::nonMirImportPaths());
91 appendImportPaths(view->engine(), ::fallbackImportPaths());
92 appendImportPaths(view->engine(), ::fallbackImportPaths());
92
93
93 view->setSource(source);
94 view->setSource(source);
tools/unlock-devicenew file+42
@@ -0,0 +1,42 @@
1+#!/bin/bash
2+# -*- Mode: sh; indent-tabs-mode: nil; tab-width: 4 -*-
3+
4+# Our goal here is to get an Ubuntu Touch device into a testable unlocked state.
5+# This will include a reboot.
6+
7+WAIT_COMMAND="sleep 20"
8+
9+while getopts s:w: opt; do
10+ case $opt in
11+ s)
12+ export ANDROID_SERIAL=$OPTARG
13+ ;;
14+ w)
15+ WAIT_COMMAND=$OPTARG
16+ ;;
17+ esac
18+done
19+
20+UNLOCK_SCRIPT='
21+import dbus, logging;
22+from unity8 import process_helpers as helpers;
23+logging.basicConfig(level=logging.INFO);
24+bus = dbus.SystemBus().get_object("com.canonical.powerd", "/com/canonical/powerd");
25+cookie = bus.requestSysState("unlock-device-hold", 1, dbus_interface="com.canonical.powerd");
26+helpers.restart_unity_with_testability();
27+bus.clearSysState(cookie, dbus_interface="com.canonical.powerd");
28+helpers.unlock_unity()
29+'
30+
31+adb reboot
32+sleep 5
33+adb "wait-for-device"
34+eval "$WAIT_COMMAND"
35+UNLOCK_OUTPUT=$(adb shell "sudo -u phablet -i python3 -c '$UNLOCK_SCRIPT'" 2>&1)
36+if echo "$UNLOCK_OUTPUT" | grep 'Greeter unlocked' >/dev/null; then
37+ echo "I: Unlock passed"
38+ exit 0
39+else
40+ echo "I: Unlock failed, script output: '$UNLOCK_OUTPUT'"
41+ exit 1
42+fi