on xenial help sign in
[ Albert Astals Cid ]

* Refactor QmlTest.cmake module so that all tests can go through it. Also a bit of cleanup around tests. * Support fallback images for dash card and preview widgets (LP: #1324142) [ CI Train Bot ] * New rebuild forced. * Resync trunk. [ Daniel d'Andrada ] * Fix Shell tests [ Josh Arenson ] * Refactor greeter emulator to unlock the greeter via dbus [ Lukáš Tinkl ] * respect target window's devicePixelRatio in MouseTouchAdaptor [ Michael Zanetti ] * Refactor QmlTest.cmake module so that all tests can go through it. Also a bit of cleanup around tests. [ Michał Sawicz ] * Refactor QmlTest.cmake module so that all tests can go through it. Also a bit of cleanup around tests. [ handsome_feng ] * Forbid closing apps during the edge gesture. (LP: #1445572) * Removed the horizonal rule on pin unlock screen. (LP: #1368798) [ handsome_feng<445865575@qq.com> ] * Forbid closing apps during the edge gesture. (LP: #1445572) * No-change rebuild against Qt 5.4.2. [ Michał Sawicz ] * Implement full-shell rotation (LP: #1210199) [ CI Train Bot ] * New rebuild forced. * Resync trunk. [ Daniel d'Andrada ] * Implemented autopilot-test and fake-sensors for shell-rotation. [ Mirco Müller ] * Implemented autopilot-test and fake-sensors for shell-rotation.

commitaae8d2d8f7a651b0ba680840fa6c0c516faad650 authorLukáš Tinkl <lukas.tinkl@canonical.com> dateWed 24 Jun 2015, 18:33 (11 years ago) parentc0dba63beb7168
162 files changed+6005−1443
CMakeLists.txt+12 CODING+4 cmake/modules/QmlPlugins.cmake+1−3 cmake/modules/QmlTest.cmake+285−191 data/unity8-dash.desktop.in+1 debian/changelog+61 debian/control+6−5 debian/unity8.install+3 plugins/Dash/CardCreator.js+32−10 plugins/Greeter/Unity/Launcher/CMakeLists.txt+1−1 plugins/Unity/Launcher/CMakeLists.txt+1−1 plugins/Utils/CMakeLists.txt+1 plugins/Utils/plugin.cpp+4−1 plugins/Utils/windowscreenshotprovider.cpp+59 plugins/Utils/windowscreenshotprovider.h+31 plugins/Wizard/PageList.cpp+1−1 po/ast.po+86−86 po/de.po+6−6 po/el.po+11−11 po/eo.po+1−1 po/hu.po+4−4 po/ia.po+5−3 po/it.po+9−9 qml/CMakeLists.txt+1 qml/Components/PinLockscreen.qml+1−5 qml/Components/ResponsiveVerticalJournal.qml+7−2 qml/Components/WindowScreenshot.qml+35 qml/Components/ZoomableImage.qml+1 qml/Dash/CardCarousel.qml−1 qml/Dash/CardGrid.qml−1 qml/Dash/CardHorizontalList.qml−1 qml/Dash/CardTool.qml+16−15 qml/Dash/CardVerticalJournal.qml−1 qml/Dash/DashApplication.qml+3−3 qml/Dash/GenericScopeView.qml+1−1 qml/Dash/Previews/PreviewHeader.qml+6 qml/Dash/Previews/PreviewImageGallery.qml+14 qml/Dash/Previews/PreviewZoomableImage.qml+13 qml/Dash/graphics/phone/screenshots/gmail-webapp.svg+343 qml/Dash/graphics/phone/screenshots/settings@12.png qml/Dash/graphics/phone/screenshots/ubuntu-weather-app.svg+201 qml/DeviceConfiguration.qml+81 qml/Greeter/Greeter.qml+2 qml/Greeter/NarrowView.qml+1 qml/Greeter/ShimGreeter.qml+45 qml/Greeter/WideView.qml+1 qml/Notifications/ShapedIcon.qml+4−3 qml/OrientedShell.qml+185 qml/Panel/Panel.qml+9 qml/Rotation/HalfLoopRotationAnimation.qml+46 qml/Rotation/ImmediateRotationAction.qml+45 qml/Rotation/NinetyRotationAnimation.qml+90 qml/Rotation/RotationStates.qml+278 qml/Shell.qml+157−81 qml/Stages/ApplicationWindow.qml+53−18 qml/Stages/DesktopStage.qml+23 qml/Stages/OrientationChangeAnimation.qml+241 qml/Stages/PhoneStage.qml+143−37 qml/Stages/SessionContainer.qml+2−8 qml/Stages/ShimStage.qml+44 qml/Stages/SpreadDelegate.qml+261−20 qml/Stages/SurfaceContainer.qml+2−2 qml/Stages/TabletStage.qml+155−19 qml/Stages/TransformedSpreadDelegate.qml+50−6 qml/Stages/TransformedTabletSpreadDelegate.qml+5−2 run.sh+1−1 runtests.sh+1−1 src/ApplicationArguments.cpp+22 src/ApplicationArguments.h+7−11 src/CMakeLists.txt+5−6 src/Dash/CMakeLists.txt+7−1 src/Dash/main.cpp+12−6 src/UnityCommandLineParser.cpp+5 src/UnityCommandLineParser.h+2 src/main.cpp+18−8 tests/CMakeLists.txt+120−8 tests/autopilot/unity8/application_lifecycle/tests/__init__.py+2−2 tests/autopilot/unity8/fixture_setup.py+131−3 tests/autopilot/unity8/greeter/__init__.py+52 tests/autopilot/unity8/indicators/tests/__init__.py+1−1 tests/autopilot/unity8/indicators/tests/test_display_indicator.py+1−1 tests/autopilot/unity8/process_helpers.py+8−95 tests/autopilot/unity8/sensors.py+93 tests/autopilot/unity8/shell/emulators/dash.py+17−10 tests/autopilot/unity8/shell/emulators/main_window.py+12 tests/autopilot/unity8/shell/tests/test_emulators.py+8−2 tests/autopilot/unity8/shell/tests/test_notifications.py+22−22 tests/autopilot/unity8/shell/tests/test_rotation.py+113 tests/copyright/CMakeLists.txt+1−1 tests/libs/UbuntuGestures/CMakeLists.txt+4−8 tests/mocks/Unity/Application/ApplicationInfo.cpp+43−2 tests/mocks/Unity/Application/ApplicationInfo.h+9−1 tests/mocks/Unity/Application/ApplicationManager.cpp+39−2 tests/mocks/Unity/Application/CMakeLists.txt+1−2 tests/mocks/Unity/Application/MirSurfaceItem.cpp+10−27 tests/mocks/Unity/Application/MirSurfaceItem.h+29−29 tests/mocks/Unity/Application/MirSurfaceItem.qml+12−14 tests/mocks/Unity/Application/SurfaceManager.cpp+4−34 tests/mocks/Unity/Application/SurfaceManager.h+2−5 tests/mocks/Unity/Application/UbuntuKeyboardInfo.cpp+33−1 tests/mocks/Unity/Application/UbuntuKeyboardInfo.h+11−5 tests/mocks/Unity/Application/VirtualKeyboard.cpp+44 tests/mocks/Unity/Application/VirtualKeyboard.h+42 tests/mocks/Unity/Application/VirtualKeyboard.qml+7−1 tests/mocks/Unity/Launcher/MockLauncherModel.cpp+3−1 tests/mocks/Utils/CMakeLists.txt+1 tests/mocks/Utils/plugin.cpp+3 tests/plugins/AccountsService/CMakeLists.txt+5−4 tests/plugins/Dash/CMakeLists.txt+10−17 tests/plugins/Dash/cardcreator/1.res+3−2 tests/plugins/Dash/cardcreator/2.res+3−8 tests/plugins/Dash/cardcreator/3.res+3−2 tests/plugins/Dash/cardcreator/4.res+1−2 tests/plugins/Dash/cardcreator/5.res+3−2 tests/plugins/Dash/cardcreator/5.tst+1−1 tests/plugins/Dash/cardcreator/6.res+3−8 tests/plugins/Dash/cardcreator/6.tst+1−1 tests/plugins/Dash/cardcreator/7.res+3−8 tests/plugins/Dash/cardcreator/7.tst+1−1 tests/plugins/Greeter/Unity/Launcher/CMakeLists.txt+5−6 tests/plugins/LightDM/CMakeLists.txt+13−4 tests/plugins/Ubuntu/Gestures/CMakeLists.txt+22−24 tests/plugins/Unity/Indicators/CMakeLists.txt+9−12 tests/plugins/Unity/Indicators/indicatorsmanagertest.cpp → tests/plugins/Unity/Indicators/IndicatorsManagerTest.cpp+1−1 tests/plugins/Unity/Indicators/indicatorsmodeltest.cpp → tests/plugins/Unity/Indicators/IndicatorsModelTest.cpp+1−1 tests/plugins/Unity/Indicators/menucontentactivatortest.cpp → tests/plugins/Unity/Indicators/MenuContentActivatorTest.cpp+1−1 tests/plugins/Unity/Indicators/rootactionstatetest.cpp → tests/plugins/Unity/Indicators/RootActionStateTest.cpp+1−1 tests/plugins/Unity/Indicators/sharedunitymenumodeltest.cpp → tests/plugins/Unity/Indicators/SharedUnityMenuModelTest.cpp+1−1 tests/plugins/Unity/Indicators/unitymenumodelstacktest.cpp → tests/plugins/Unity/Indicators/UnityMenuModelStackTest.cpp+1−1 tests/plugins/Unity/Launcher/CMakeLists.txt+5−6 tests/plugins/Unity/Launcher/launchermodeltest.cpp+3−1 tests/plugins/Unity/Session/CMakeLists.txt+5−5 tests/plugins/Utils/CMakeLists.txt+12−26 tests/plugins/Utils/homekeywatchertest.cpp → tests/plugins/Utils/HomeKeyWatcherTest.cpp+1−1 tests/plugins/Utils/modeltest.cpp → tests/plugins/Utils/ModelTest.cpp+1−1 tests/plugins/Utils/modeltest.h → tests/plugins/Utils/ModelTest.h tests/plugins/Utils/qlimitproxymodeltest.cpp → tests/plugins/Utils/QLimitProxyModelTest.cpp+2−2 tests/plugins/Utils/timeformattertest.cpp → tests/plugins/Utils/TimeFormatterTest.cpp+1−11 tests/plugins/Utils/unitysortfilterproxymodeltest.cpp → tests/plugins/Utils/UnitySortFilterProxyModelTest.cpp+2−2 tests/plugins/Wizard/CMakeLists.txt+2−2 tests/qmltests/CMakeLists.txt+77−93 tests/qmltests/Components/CMakeLists.txt+4−10 tests/qmltests/Dash/Previews/tst_PreviewHeader.qml+30 tests/qmltests/Dash/Previews/tst_PreviewImageGallery.qml+21−1 tests/qmltests/Dash/Previews/tst_PreviewZoomableImage.qml+27−6 tests/qmltests/Dash/tst_Card.qml+30−1 tests/qmltests/Dash/tst_GenericScopeView.qml+30−7 tests/qmltests/Panel/tst_IndicatorsMenu.qml−2 tests/qmltests/Panel/tst_Panel.qml−2 tests/qmltests/Stages/tst_ApplicationWindow.qml+14−23 tests/qmltests/Stages/tst_PhoneStage.qml+24−58 tests/qmltests/Stages/tst_SessionContainer.qml+1−64 tests/qmltests/Stages/tst_SpreadDelegate.qml+172−37 tests/qmltests/Stages/tst_TabletStage.qml+51 tests/qmltests/Stages/tst_WindowMoveResizeArea.qml+1 tests/qmltests/Tutorial/tst_Tutorial.qml+1 tests/qmltests/tst_OrientedShell.qml+1151 tests/qmltests/tst_Shell.qml+29−84 tests/qmltests/tst_ShellWithPin.qml+7−9 tests/qmltests/utils/Unity/Test/tst_UnityTest.qml+5−8 tests/utils/modules/Unity/Test/UnityTestCase.qml+55−2 tests/whitespace/CMakeLists.txt+1−1
CMakeLists.txt+12
@@ -56,6 +56,8 @@ find_package(Qt5DBus 5.2 REQUIRED)
5656find_package(Qt5Concurrent 5.2 REQUIRED)
5757find_package(Qt5Sql 5.2 REQUIRED)
5858
59+pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=6)
60+
5961# Standard install paths
6062include(GNUInstallDirs)
6163
@@ -111,8 +113,17 @@ set(MOCK_INDICATOR_SERVICE_APP unity-mock-indicator-service)
111113include_directories(
112114 ${CMAKE_SOURCE_DIR}/include
113115 ${CMAKE_BINARY_DIR}/include
116+ ${APPLICATION_API_INCLUDE_DIRS}
114117)
115118
119+set(ARTIFACTS_DIR "" CACHE PATH "Store test artifacts in this directory.")
120+
121+# Quiet logger
122+set(QUIET_LOGGER OFF CACHE BOOL "Disable test output on stdout.")
123+if(NOT QUIET_LOGGER)
124+ set(STDOUT_LOGGER "-o" "-,txt")
125+endif()
126+
116127# add subdirectories to build
117128add_subdirectory(include)
118129add_subdirectory(libs)
@@ -121,6 +132,7 @@ add_subdirectory(tools)
121132add_subdirectory(qml)
122133
123134# Tests
135+set(NO_TESTS OFF CACHE BOOL "Disable tests.")
124136if (NOT NO_TESTS)
125137 include(CTest)
126138 enable_testing()
CODING+4
@@ -158,6 +158,10 @@ To run the tests for a single component/class under gdb (so you can catch SEGFAU
158158
159159$ make gdbtestComponentName
160160
161+To run a single test function of a single component/class:
162+
163+$ make testComponentName FUNCTION="TestCaseName::functionName"
164+
161165To run a test application that allow you to manually test or to simply try out
162166a given component:
163167
cmake/modules/QmlPlugins.cmake+1−3
@@ -107,8 +107,6 @@ macro(export_qmlplugin PLUGIN VERSION PATH)
107107 set(multi TARGETS)
108108 cmake_parse_arguments(QMLPLUGIN "${options}" "${single}" "${multi}" ${ARGN})
109109
110− get_target_property(qmlplugindump_executable qmlplugindump LOCATION)
111−
112110 if(QMLPLUGIN_BINARY_DIR)
113111 set(qmlplugin_dir ${QMLPLUGIN_BINARY_DIR}/${PATH})
114112 else()
@@ -127,7 +125,7 @@ macro(export_qmlplugin PLUGIN VERSION PATH)
127125 set(qmltypes_path ${CMAKE_CURRENT_SOURCE_DIR}/${plugin_suffix}.qmltypes)
128126
129127 add_custom_target(${target_prefix}-qmltypes
130− COMMAND env ${QMLPLUGIN_ENVIRONMENT} ${qmlplugindump_executable} -notrelocatable
128+ COMMAND env ${QMLPLUGIN_ENVIRONMENT} $<TARGET_FILE:qmlplugindump> -notrelocatable
131129 ${PLUGIN} ${VERSION} ${QMLPLUGIN_MODULE_DIR} > ${qmltypes_path}
132130 WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
133131 )
cmake/modules/QmlTest.cmake+285−191
@@ -1,218 +1,312 @@
1−# add_qml_test(path component_name [NO_ADD_TEST] [NO_TARGETS]
2−# [TARGETS target1 [target2 [...]]]
3−# [IMPORT_PATHS import_path1 [import_path2 [...]]
4−# [PROPERTIES prop1 value1 [prop2 value2 [...]]])
1+# If you need to override the qmlscene or qmltestrunner executables,
2+# create the corresponding executable target.
3+
4+# These functions respect the global STDOUT_LOGGER and ARTIFACTS_DIR variables.
5+
6+# You can use those with cmake_parse_arguments
7+# if you need to wrap and mangle arguments.
8+set(QMLTEST_OPTIONS ADD_TEST CACHE INTERNAL "")
9+set(QMLTEST_SINGLE ITERATIONS ARG_PREFIX CACHE INTERNAL "")
10+set(QMLTEST_MULTI ARGS ENVIRONMENT DEPENDS IMPORT_PATHS TARGETS CACHE INTERNAL "")
11+
12+# import_executables(name1 [name2 [...]]
13+# [OPTIONAL] # continue when not found
14+# )
515#
6−# NO_ADD_TEST will prevent adding the test to the "test" target
7−# NO_TARGETS will prevent adding the test to any targets
8−# TARGETS lists the targets the test should be added to
9−# IMPORT_PATHS will pass those paths to qmltestrunner as "-import" arguments
10−# PROPERTIES will be set on the target and test target. See CMake's set_target_properties()
16+# This will find the named executables and import them
17+# to an imported target of the same name.
18+
19+function(import_executables)
20+ cmake_parse_arguments(QMLTEST "OPTIONAL" "" "" ${ARGN})
21+
22+ foreach(NAME ${QMLTEST_UNPARSED_ARGUMENTS})
23+ if(NOT TARGET ${NAME})
24+ add_executable(${NAME} IMPORTED GLOBAL)
25+ find_program(${NAME}_exe ${NAME})
26+
27+ if(NOT QMLTEST_OPTIONAL AND NOT ${NAME}_exe)
28+ message(FATAL_ERROR "Could not locate ${NAME}.")
29+ elseif(NOT ${NAME}_exe)
30+ message(STATUS "Could not locate ${NAME}, skipping.")
31+ else()
32+ set_target_properties(${NAME} PROPERTIES IMPORTED_LOCATION ${${NAME}_exe})
33+ endif()
34+ endif()
35+ endforeach()
36+endfunction()
37+
38+
39+# add_qml_test(path component_name
40+# [...]
41+# )
1142#
12−# Two targets will be created:
13−# - testComponentName - Runs the test with qmltestrunner
14−# - tryComponentName - Runs the test with uqmlscene, for manual interaction
15−# - gdbtestComponentName - Runs the test with qmltestrunner under gdb
43+# Add test targets for ${component_name} under ${path}. It's assumed
44+# that the test file is named ${path}/tst_${component_name}.qml.
45+#
46+# This function wraps add_manual_qml_test and add_qml_unittest,
47+# see below for available arguments.
48+
49+function(add_qml_test PATH COMPONENT_NAME)
50+ cmake_parse_arguments(QMLTEST "${QMLTEST_OPTIONS}" "${QMLTEST_SINGLE}" "${QMLTEST_MULTI}" ${ARGN})
51+ mangle_arguments()
52+
53+ add_qml_unittest(${ARGV})
54+ add_manual_qml_test(${ARGV})
55+endfunction()
56+
57+
58+# add_qml_unittest(path component_name
59+# [...]
60+# )
61+#
62+# Add test targets for ${component_name} under ${path}. It's assumed
63+# that the test file is named ${path}/tst_${component_name}.qml.
64+#
65+# This function wraps add_executable_test, see below for available arguments.
66+
67+function(add_qml_unittest PATH COMPONENT_NAME)
68+ import_executables(qmltestrunner)
69+
70+ add_executable_test(${COMPONENT_NAME} qmltestrunner
71+ ${ARGN}
72+ ARGS -input ${CMAKE_CURRENT_SOURCE_DIR}/${PATH}/tst_${COMPONENT_NAME}.qml ${QMLTEST_ARGS}
73+ )
74+endfunction()
75+
76+
77+# add_manual_qml_test(path component_name
78+# [...]
79+# )
80+#
81+# Add manual test targets for ${component_name} under ${path}. It's assumed
82+# that the test file is named ${path}/tst_${component_name}.qml.
1683#
17−# To change/set a default value for the whole test suite, prior to calling add_qml_test, set:
18−# qmltest_DEFAULT_NO_ADD_TEST (default: FALSE)
19−# qmltest_DEFAULT_TARGETS
20−# qmltest_DEFAULT_IMPORT_PATHS
21−# qmltest_DEFAULT_PROPERTIES
22−
23−find_program(qmltestrunner_exe qmltestrunner)
24−
25−if(NOT qmltestrunner_exe)
26− msg(FATAL_ERROR "Could not locate qmltestrunner.")
27−endif()
28−
29−set(qmlscene_exe ${CMAKE_BINARY_DIR}/tests/uqmlscene/uqmlscene)
30−
31−set(test_env LC_ALL=C UNITY_TESTING=1)
32−
33−macro(add_manual_qml_test SUBPATH COMPONENT_NAME)
34− set(options NO_ADD_TEST NO_TARGETS)
35− set(multi_value_keywords IMPORT_PATHS TARGETS PROPERTIES ENVIRONMENT)
36−
37− cmake_parse_arguments(qmltest "${options}" "" "${multi_value_keywords}" ${ARGN})
38− cmake_parse_arguments(qmltest_default "${options}" "" "${multi_value_keywords}" ${qmltest_DEFAULT_PROPERTIES})
39−
40− set(qmlscene_TARGET try${COMPONENT_NAME})
41− set(qmltest_FILE ${SUBPATH}/tst_${COMPONENT_NAME})
42−
43− set(qmlscene_imports "")
44− if(NOT "${qmltest_IMPORT_PATHS}" STREQUAL "")
45− foreach(IMPORT_PATH ${qmltest_IMPORT_PATHS})
46− list(APPEND qmlscene_imports "-I")
47− list(APPEND qmlscene_imports ${IMPORT_PATH})
48− endforeach(IMPORT_PATH)
49− elseif(NOT "${qmltest_DEFAULT_IMPORT_PATHS}" STREQUAL "")
50− foreach(IMPORT_PATH ${qmltest_DEFAULT_IMPORT_PATHS})
51− list(APPEND qmlscene_imports "-I")
52− list(APPEND qmlscene_imports ${IMPORT_PATH})
53− endforeach(IMPORT_PATH)
84+# This function wraps add_manual_test, see below for available arguments.
85+
86+function(add_manual_qml_test PATH COMPONENT_NAME)
87+ import_executables(qmlscene)
88+ cmake_parse_arguments(QMLTEST "${QMLTEST_OPTIONS}" "${QMLTEST_SINGLE}" "${QMLTEST_MULTI}" ${ARGN})
89+
90+ add_manual_test(${COMPONENT_NAME} qmlscene
91+ ${ARGN}
92+ ARGS ${CMAKE_CURRENT_SOURCE_DIR}/${PATH}/tst_${COMPONENT_NAME}.qml ${QMLTEST_ARGS}
93+ )
94+endfunction()
95+
96+
97+# add_executable_test(target component_name
98+# [...] # see doc for add_manual_qml_test for common arguments
99+# [ADD_TEST] # whether to add to the "test" target
100+# [ARG_PREFIX arg_prefix] # prefix logging arguments with this string
101+# [ARGS] arg1 [arg2 [...]] # pass these arguments to the test executable
102+# [TARGETS target1 [target2 [...]]] # make the listed targets depend on this test
103+# # if a corresponding xvfbtarget1, xvfbtarget2 etc. exists,
104+# # this test running under xvfb will be added as a dependency
105+# # of those targets
106+# [ITERATIONS count] # run this test as a benchmark for ${count} iterations
107+# )
108+#
109+# Logging options in the standard form of "-o filename,format"
110+# will be appended to the arguments list, prefixed with ARG_PREFIX.
111+# XUnitXML files will be stored in current binary dir or under
112+# ARTIFACTS_DIR, if set.
113+#
114+# Three targets will be created:
115+# - test${component_name} - Runs the test
116+# - xvfbtest${component_name} - Runs the test under xvfb
117+# - gdbtest${component_name} - Runs the test under gdb
118+
119+function(add_executable_test COMPONENT_NAME TARGET)
120+ import_executables(gdb xvfb-run OPTIONAL)
121+
122+ cmake_parse_arguments(QMLTEST "${QMLTEST_OPTIONS}" "${QMLTEST_SINGLE}" "${QMLTEST_MULTI}" ${ARGN})
123+ mangle_arguments()
124+
125+ if(ARTIFACTS_DIR)
126+ file(RELATIVE_PATH path ${CMAKE_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
127+ file(MAKE_DIRECTORY ${ARTIFACTS_DIR}/${path})
128+ set(file_logger -o ${ARTIFACTS_DIR}/${path}/test${COMPONENT_NAME}.xml,xunitxml)
129+ else()
130+ set(file_logger -o ${CMAKE_CURRENT_BINARY_DIR}/test${COMPONENT_NAME}.xml,xunitxml)
131+ endif()
132+
133+ bake_arguments("${QMLTEST_ARG_PREFIX}" args ${iterations} ${file_logger} ${STDOUT_LOGGER})
134+
135+ set(qmltest_command
136+ $<TARGET_FILE:${TARGET}>
137+ ${QMLTEST_ARGS}
138+ ${args}
139+ )
140+
141+ add_qmltest_target(test${COMPONENT_NAME} ${TARGET}
142+ COMMAND ${qmltest_command}
143+ ${depends}
144+ ENVIRONMENT QML2_IMPORT_PATH=${imports} ${QMLTEST_ENVIRONMENT}
145+ ${add_test}
146+ ${targets}
147+ )
148+
149+ if(TARGET xvfb-run)
150+ add_qmltest_target(xvfbtest${COMPONENT_NAME} ${TARGET}
151+ COMMAND $<TARGET_FILE:xvfb-run> --server-args "-screen 0 1024x768x24" --auto-servernum ${qmltest_command}
152+ ${depends}
153+ ENVIRONMENT QML2_IMPORT_PATH=${imports} ${QMLTEST_ENVIRONMENT} LD_PRELOAD=/usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}/mesa/libGL.so.1
154+ TARGETS ${xvfb_targets}
155+ )
54156 endif()
55157
56− if("${qmltest_ENVIRONMENT}" STREQUAL "")
57− set(qmltest_ENVIRONMENT "${qmltest_default_ENVIRONMENT}")
158+ if(TARGET gdb)
159+ add_qmltest_target(gdbtest${COMPONENT_NAME} ${TARGET}
160+ COMMAND $<TARGET_FILE:gdb> -ex run -args ${qmltest_command}
161+ ${depends}
162+ ENVIRONMENT QML2_IMPORT_PATH=${imports} ${QMLTEST_ENVIRONMENT}
163+ )
58164 endif()
165+endfunction()
166+
167+
168+# add_manual_test(target component_name
169+# [DEPENDS target1 [target2 [...]]] # make this test depend on the specified targets
170+# [IMPORT_PATHS import_path1 [import_path2 [...]] # use these QML import paths
171+# # (they're searched first to last)
172+# [ENVIRONMENT var1=value1 [var2=value2 [...]]] # set these environment variables
173+# )
174+#
175+# Two targets will be created:
176+# - try${component_name} - Runs the test for manual interaction
177+# - gdbtry${component_name} - Runs the test under gdb
178+
179+function(add_manual_test COMPONENT_NAME TARGET)
180+ import_executables(gdb OPTIONAL)
181+
182+ cmake_parse_arguments(QMLTEST "${QMLTEST_OPTIONS}" "${QMLTEST_SINGLE}" "${QMLTEST_MULTI}" ${ARGN})
183+ mangle_arguments()
184+
185+ bake_arguments("${QMLTEST_ARG_PREFIX}" args -qmljsdebugger=port:3768)
186+
187+ set(qmltry_command
188+ $<TARGET_FILE:${TARGET}>
189+ ${QMLTEST_ARGS}
190+ ${args}
191+ )
59192
60− set(qmlscene_command
61− env ${test_env} ${qmltest_ENVIRONMENT}
62− ${qmlscene_exe} -qmljsdebugger=port:3768 ${CMAKE_CURRENT_SOURCE_DIR}/${qmltest_FILE}.qml
63− ${qmlscene_imports}
193+ add_qmltest_target(try${COMPONENT_NAME} ${TARGET}
194+ COMMAND ${qmltry_command}
195+ ${depends}
196+ ENVIRONMENT QML2_IMPORT_PATH=${imports} ${QMLTEST_ENVIRONMENT}
64197 )
65− add_custom_target(${qmlscene_TARGET} ${qmlscene_command})
66−
67−endmacro(add_manual_qml_test)
68−
69−macro(add_qml_benchmark SUBPATH COMPONENT_NAME ITERATIONS)
70− add_qml_test_internal(${SUBPATH} ${COMPONENT_NAME} ${ITERATIONS} ${ARGN})
71−endmacro(add_qml_benchmark)
72−
73−macro(add_qml_test SUBPATH COMPONENT_NAME)
74− add_qml_test_internal(${SUBPATH} ${COMPONENT_NAME} 0 ${ARGN})
75−endmacro(add_qml_test)
76−
77−macro(add_qml_test_internal SUBPATH COMPONENT_NAME ITERATIONS)
78− set(options NO_ADD_TEST NO_TARGETS)
79− set(multi_value_keywords IMPORT_PATHS TARGETS PROPERTIES ENVIRONMENT)
80−
81− cmake_parse_arguments(qmltest "${options}" "" "${multi_value_keywords}" ${ARGN})
82− cmake_parse_arguments(qmltest_default "${options}" "" "${multi_value_keywords}" ${qmltest_DEFAULT_PROPERTIES})
83−
84− set(qmltest_TARGET test${COMPONENT_NAME})
85− set(qmltest_gdb_TARGET gdbtest${COMPONENT_NAME})
86− set(qmltest_xvfb_TARGET xvfbtest${COMPONENT_NAME})
87− set(qmltest_FILE ${SUBPATH}/tst_${COMPONENT_NAME})
88−
89− set(qmltestrunner_imports "")
90− if(NOT "${qmltest_IMPORT_PATHS}" STREQUAL "")
91− foreach(IMPORT_PATH ${qmltest_IMPORT_PATHS})
92− list(APPEND qmltestrunner_imports "-import")
93− list(APPEND qmltestrunner_imports ${IMPORT_PATH})
94− endforeach(IMPORT_PATH)
95− elseif(NOT "${qmltest_DEFAULT_IMPORT_PATHS}" STREQUAL "")
96− foreach(IMPORT_PATH ${qmltest_DEFAULT_IMPORT_PATHS})
97− list(APPEND qmltestrunner_imports "-import")
98− list(APPEND qmltestrunner_imports ${IMPORT_PATH})
99− endforeach(IMPORT_PATH)
198+
199+ if(TARGET gdb)
200+ add_qmltest_target(gdbtry${COMPONENT_NAME} ${TARGET}
201+ COMMAND $<TARGET_FILE:gdb> -ex run -args ${qmltry_command}
202+ ${depends}
203+ ENVIRONMENT QML2_IMPORT_PATH=${imports} ${QMLTEST_ENVIRONMENT}
204+ )
100205 endif()
206+endfunction()
207+
208+
209+################### INTERNAL ####################
210+
211+# add_qmltest_target(target_name target
212+# COMMAND test_exe [arg1 [...]] # execute this test with arguments
213+# [...] # see above for available arguments:
214+# # ADD_TEST, ENVIRONMENT, DEPENDS and TARGETS
215+# )
101216
217+function(add_qmltest_target TARGET_NAME TARGET)
218+ cmake_parse_arguments(QMLTEST "${QMLTEST_OPTIONS}" "${QMLTEST_SINGLE}" "COMMAND;${QMLTEST_MULTI}" ${ARGN})
219+ mangle_arguments()
220+
221+ # Additional arguments
102222 string(TOLOWER "${CMAKE_GENERATOR}" cmake_generator_lower)
103223 if(cmake_generator_lower STREQUAL "unix makefiles")
104− set(function_ARGS $(FUNCTION))
105− else()
106− set(function_ARGS "")
224+ set(function "$(FUNCTION)")
107225 endif()
108226
109− if (${ITERATIONS} GREATER 0)
110− set(ITERATIONS_STRING "-iterations" ${ITERATIONS})
111− else()
112− set(ITERATIONS_STRING "")
227+ add_custom_target(${TARGET_NAME}
228+ env ${QMLTEST_ENVIRONMENT}
229+ ${QMLTEST_COMMAND} ${function}
230+ DEPENDS ${TARGET} ${QMLTEST_DEPENDS}
231+ )
232+
233+ if(QMLTEST_ADD_TEST)
234+ add_test(
235+ NAME ${TARGET_NAME}
236+ COMMAND ${QMLTEST_COMMAND}
237+ )
238+
239+ foreach(ENV ${QMLTEST_ENVIRONMENT})
240+ set_property(TEST ${TARGET_NAME} APPEND PROPERTY ENVIRONMENT ${ENV})
241+ endforeach()
242+
243+ set_property(TEST ${TARGET_NAME} APPEND PROPERTY DEPENDS ${TARGET})
244+ foreach(DEPEND ${DEPENDS})
245+ set_property(TEST ${TARGET_NAME} APPEND PROPERTY DEPENDS ${DEPEND})
246+ endforeach()
113247 endif()
114248
115− if("${qmltest_ENVIRONMENT}" STREQUAL "")
116− set(qmltest_ENVIRONMENT "${qmltest_default_ENVIRONMENT}")
249+ foreach(UPSTREAM_TARGET ${QMLTEST_TARGETS})
250+ add_dependencies(${UPSTREAM_TARGET} ${TARGET_NAME})
251+ endforeach()
252+endfunction()
253+
254+
255+# mangle_arguments(${ARGN})
256+#
257+# Verify there were no unparsed arguments and
258+# mangle the known ones for further processing.
259+
260+macro(mangle_arguments)
261+ if(QMLTEST_UNPARSED_ARGUMENTS)
262+ message(FATAL_ERROR "Unexpected arguments: ${QMLTEST_UNPARSED_ARGUMENTS}")
117263 endif()
118264
119− set(qmltest_command
120− env ${test_env} ${qmltest_ENVIRONMENT}
121− ${qmltestrunner_exe} -input ${CMAKE_CURRENT_SOURCE_DIR}/${qmltest_FILE}.qml
122− ${qmltestrunner_imports}
123− ${ITERATIONS_STRING}
124− -o ${CMAKE_BINARY_DIR}/${qmltest_TARGET}.xml,xunitxml
125− -o -,txt
126− ${function_ARGS}
127− )
128− find_program(DPKG dpkg-architecture)
129− if(DPKG)
130− exec_program(${DPKG} ARGS "-qDEB_BUILD_MULTIARCH" OUTPUT_VARIABLE ARCH_TRIPLET )
131− set(LD_PRELOAD_PATH "LD_PRELOAD=/usr/lib/${ARCH_TRIPLET}/mesa/libGL.so.1")
265+ if(QMLTEST_ADD_TEST)
266+ set(add_test ADD_TEST)
132267 endif()
133− set(qmltest_xvfb_command
134− env ${test_env} ${qmltest_ENVIRONMENT} ${LD_PRELOAD_PATH}
135− xvfb-run --server-args "-screen 0 1024x768x24" --auto-servernum
136− ${qmltestrunner_exe} -input ${CMAKE_CURRENT_SOURCE_DIR}/${qmltest_FILE}.qml
137− ${qmltestrunner_imports}
138− -o ${CMAKE_BINARY_DIR}/${qmltest_TARGET}.xml,xunitxml
139− -o -,txt
140− ${function_ARGS}
141− )
142268
143− set(qmltest_gdb_command
144− env ${test_env} ${qmltest_ENVIRONMENT} ${LD_PRELOAD_PATH}
145− gdb -e ${qmltestrunner_exe} -ex \"run -input ${CMAKE_CURRENT_SOURCE_DIR}/${qmltest_FILE}.qml ${qmltestrunner_imports} ${ITERATIONS_STRING}\"
146− )
269+ if(QMLTEST_IMPORT_PATHS)
270+ string(REPLACE ";" ":" imports "${QMLTEST_IMPORT_PATHS}")
271+ endif()
147272
148− add_qmltest_target(${qmltest_TARGET} "${qmltest_command}" TRUE ${qmltest_NO_ADD_TEST})
149− add_qmltest_target(${qmltest_xvfb_TARGET} "${qmltest_xvfb_command}" ${qmltest_NO_TARGETS} TRUE)
150− add_qmltest_target(${qmltest_gdb_TARGET} "${qmltest_gdb_command}" TRUE TRUE)
151− add_manual_qml_test(${SUBPATH} ${COMPONENT_NAME} ${ARGN})
152−endmacro(add_qml_test_internal)
153−
154−macro(add_binary_qml_test CLASS_NAME LD_PATH DEPS ENVVAR)
155− set(testCommand
156− ${ENVVAR}
157− LD_LIBRARY_PATH=${LD_PATH}
158− dbus-test-runner
159− -m 300
160− -t ${CMAKE_CURRENT_BINARY_DIR}/${CLASS_NAME}TestExec
161− -p -o -p ${CMAKE_BINARY_DIR}/test${CLASS_NAME}.xml,xunitxml
162− -p -o -p -,txt)
163−
164− add_qmltest_target(test${CLASS_NAME} "${testCommand}" FALSE TRUE)
165− add_dependencies(test${CLASS_NAME} ${CLASS_NAME}TestExec ${DEPS})
166−
167− find_program( HAVE_GCC gcc )
168− if (NOT ${HAVE_GCC} STREQUAL "")
169− exec_program( gcc ARGS "-dumpmachine" OUTPUT_VARIABLE ARCH_TRIPLET )
170− set(LD_PRELOAD_PATH "LD_PRELOAD=/usr/lib/${ARCH_TRIPLET}/mesa/libGL.so.1")
273+ if(QMLTEST_ITERATIONS)
274+ set(iterations -iterations ${QMLTEST_ITERATIONS})
171275 endif()
172− set(xvfbtestCommand
173− ${LD_PRELOAD_PATH}
174− ${ENVVAR}
175− LD_LIBRARY_PATH=${LD_PATH}
176− xvfb-run --server-args "-screen 0 1024x768x24" --auto-servernum
177− dbus-test-runner
178− -m 300
179− -t ${CMAKE_CURRENT_BINARY_DIR}/${CLASS_NAME}TestExec
180− -p -o -p ${CMAKE_BINARY_DIR}/test${CLASS_NAME}.xml,xunitxml
181− -p -o -p -,txt)
182−
183− add_qmltest_target(xvfbtest${CLASS_NAME} "${xvfbtestCommand}" FALSE TRUE)
184− add_dependencies(qmluitests xvfbtest${CLASS_NAME})
185−endmacro(add_binary_qml_test)
186−
187−macro(add_qmltest_target qmltest_TARGET qmltest_command qmltest_NO_TARGETS qmltest_NO_ADD_TEST)
188− add_custom_target(${qmltest_TARGET} ${qmltest_command})
189−
190− if(NOT "${qmltest_PROPERTIES}" STREQUAL "")
191− set_target_properties(${qmltest_TARGET} PROPERTIES ${qmltest_PROPERTIES})
192− elseif(NOT "${qmltest_DEFAULT_PROPERTIES}" STREQUAL "")
193− set_target_properties(${qmltest_TARGET} PROPERTIES ${qmltest_DEFAULT_PROPERTIES})
276+
277+ if(QMLTEST_DEPENDS)
278+ set(depends DEPENDS ${QMLTEST_DEPENDS})
194279 endif()
195280
196− if("${qmltest_NO_ADD_TEST}" STREQUAL FALSE AND NOT "${qmltest_DEFAULT_NO_ADD_TEST}" STREQUAL "TRUE")
197− add_test(${qmltest_TARGET} ${qmltest_command})
281+ if(QMLTEST_TARGETS)
282+ set(targets TARGETS ${QMLTEST_TARGETS})
283+ endif()
198284
199− if(NOT "${qmltest_UNPARSED_ARGUMENTS}" STREQUAL "")
200− set_tests_properties(${qmltest_TARGET} PROPERTIES ${qmltest_PROPERTIES})
201− elseif(NOT "${qmltest_DEFAULT_PROPERTIES}" STREQUAL "")
202− set_tests_properties(${qmltest_TARGET} PROPERTIES ${qmltest_DEFAULT_PROPERTIES})
285+ set(xvfb_targets "")
286+ foreach(target ${QMLTEST_TARGETS})
287+ if(TARGET xvfb${target})
288+ list(APPEND xvfb_targets xvfb${target})
203289 endif()
204− endif("${qmltest_NO_ADD_TEST}" STREQUAL FALSE AND NOT "${qmltest_DEFAULT_NO_ADD_TEST}" STREQUAL "TRUE")
205−
206− if("${qmltest_NO_TARGETS}" STREQUAL "FALSE")
207− if(NOT "${qmltest_TARGETS}" STREQUAL "")
208− foreach(TARGET ${qmltest_TARGETS})
209− add_dependencies(${TARGET} ${qmltest_TARGET})
210− endforeach(TARGET)
211− elseif(NOT "${qmltest_DEFAULT_TARGETS}" STREQUAL "")
212− foreach(TARGET ${qmltest_DEFAULT_TARGETS})
213− add_dependencies(${TARGET} ${qmltest_TARGET})
214− endforeach(TARGET)
215− endif()
216− endif("${qmltest_NO_TARGETS}" STREQUAL "FALSE")
290+ endforeach()
291+ set(xvfb_targets "${xvfb_targets}" PARENT_SCOPE)
292+endmacro()
293+
217294
218−endmacro(add_qmltest_target)
295+# bake_arguments(prefix output
296+# arg1 [arg2 [...]]
297+# )
298+#
299+# If set, add the argument prefix before every passed
300+# argument and store the result in ${OUTPUT} variable.
301+
302+function(bake_arguments PREFIX OUTPUT)
303+ set(args "${ARGN}")
304+ if(PREFIX)
305+ set(args "")
306+ foreach(arg ${ARGN})
307+ list(APPEND args ${PREFIX})
308+ list(APPEND args ${arg})
309+ endforeach()
310+ endif()
311+ set(${OUTPUT} "${args}" PARENT_SCOPE)
312+endfunction()
data/unity8-dash.desktop.in+1
@@ -7,3 +7,4 @@ Terminal=false
77Icon=
88NoDisplay=true
99X-Ubuntu-Touch=true
10+X-Ubuntu-Supported-Orientations=primary
debian/changelog+61
@@ -1,3 +1,64 @@
1+unity8 (8.10+15.10.20150618-0ubuntu1) wily; urgency=medium
2+
3+ [ Albert Astals Cid ]
4+ * Refactor QmlTest.cmake module so that all tests can go through it.
5+ Also a bit of cleanup around tests.
6+ * Support fallback images for dash card and preview widgets (LP:
7+ #1324142)
8+
9+ [ CI Train Bot ]
10+ * New rebuild forced.
11+ * Resync trunk.
12+
13+ [ Daniel d'Andrada ]
14+ * Fix Shell tests
15+
16+ [ Josh Arenson ]
17+ * Refactor greeter emulator to unlock the greeter via dbus
18+
19+ [ Lukáš Tinkl ]
20+ * respect target window's devicePixelRatio in MouseTouchAdaptor
21+
22+ [ Michael Zanetti ]
23+ * Refactor QmlTest.cmake module so that all tests can go through it.
24+ Also a bit of cleanup around tests.
25+
26+ [ Michał Sawicz ]
27+ * Refactor QmlTest.cmake module so that all tests can go through it.
28+ Also a bit of cleanup around tests.
29+
30+ [ handsome_feng ]
31+ * Forbid closing apps during the edge gesture. (LP: #1445572)
32+ * Removed the horizonal rule on pin unlock screen. (LP: #1368798)
33+
34+ [ handsome_feng<445865575@qq.com> ]
35+ * Forbid closing apps during the edge gesture. (LP: #1445572)
36+
37+ -- CI Train Bot <ci-train-bot@canonical.com> Thu, 18 Jun 2015 19:40:06 +0000
38+
39+unity8 (8.10+15.10.20150612-0ubuntu2) wily; urgency=medium
40+
41+ * No-change rebuild against Qt 5.4.2.
42+
43+ -- Timo Jyrinki <timo-jyrinki@ubuntu.com> Mon, 15 Jun 2015 12:33:07 +0300
44+
45+unity8 (8.10+15.10.20150612-0ubuntu1) wily; urgency=medium
46+
47+ [ Michał Sawicz ]
48+ * Implement full-shell rotation (LP: #1210199)
49+
50+ [ CI Train Bot ]
51+ * New rebuild forced.
52+ * Resync trunk.
53+
54+ [ Daniel d'Andrada ]
55+ * Implemented autopilot-test and fake-sensors for shell-rotation.
56+
57+ [ Mirco Müller ]
58+ * Implemented autopilot-test and fake-sensors for shell-rotation.
59+
60+ -- CI Train Bot <ci-train-bot@canonical.com> Fri, 12 Jun 2015 16:07:47 +0000
61+
162unity8 (8.02+15.10.20150603.1-0ubuntu1) wily; urgency=medium
263
364 [ CI Train Bot ]
debian/control+6−5
@@ -12,6 +12,7 @@ Build-Depends: cmake,
1212# append :native to g++-4.9 so we don't try to run armhf g++
1313# on an x86 CPU for eaxmple, when cross-compiling.
1414 g++-4.9:native,
15+ libandroid-properties-dev,
1516 graphviz,
1617 gsettings-ubuntu-schemas (>= 0.0.2+14.10.20140815),
1718 libconnectivity-qt1-dev,
@@ -27,7 +28,7 @@ Build-Depends: cmake,
2728 libqmenumodel-dev (>= 0.2.9),
2829 libqt5xmlpatterns5-dev,
2930 libsystemsettings-dev,
30− libunity-api-dev (>= 7.96),
31+ libunity-api-dev (>= 7.97),
3132 libusermetricsoutput1-dev,
3233 libxcb1-dev,
3334 pkg-config,
@@ -94,7 +95,7 @@ Depends: gsettings-desktop-schemas,
9495 qml-module-qtquick-xmllistmodel,
9596 qml-module-qtsysteminfo,
9697 qtdeclarative5-gsettings1.0,
97− qtdeclarative5-qtmir-plugin (>= 0.4.4),
98+ qtdeclarative5-qtmir-plugin (>= 0.4.5),
9899 qtdeclarative5-ubuntu-telephony0.1,
99100 qtdeclarative5-ubuntu-web-plugin,
100101 ubuntu-system-settings,
@@ -124,7 +125,7 @@ Depends: qml-module-qtquick-layouts,
124125 qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.1.1239) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.1.1239),
125126 qtdeclarative5-unity-notifications-plugin (>= 0.1.2) | unity-notifications-impl,
126127 ubuntu-thumbnailer-impl-0,
127− unity-application-impl-4,
128+ unity-application-impl-6,
128129 unity-notifications-impl-3,
129130 unity-plugin-scopes | unity-scopes-impl,
130131 unity-scopes-impl-6,
@@ -150,7 +151,7 @@ Depends: autopilot-qt5 (>= 1.4),
150151 python3-fixtures,
151152 python3-gi,
152153 qttestability-autopilot (>= 1.4),
153− ubuntu-ui-toolkit-autopilot,
154+ ubuntu-ui-toolkit-autopilot (>= 1.2.1485+15.04.20150417.1-0ubuntu1),
154155 unity-scope-click,
155156 unity8 (= ${source:Version}),
156157 unity8-fake-env (= ${source:Version}),
@@ -168,7 +169,7 @@ Pre-Depends: ${misc:Pre-Depends},
168169Depends: ${misc:Depends},
169170 ${shlibs:Depends},
170171Provides: unity-application-impl,
171− unity-application-impl-4,
172+ unity-application-impl-6,
172173Replaces: unity8-autopilot (<< 8.02+15.04.20150422-0ubuntu1)
173174Description: Fake environment for running Unity 8 shell
174175 Provides fake implementations of some QML modules used by Unity 8 shell
debian/unity8.install+3
@@ -8,6 +8,9 @@ usr/share/applications/unity8.desktop
88usr/share/unity8/Greeter
99usr/share/unity8/Launcher
1010usr/share/unity8/Panel
11+usr/share/unity8/Rotation
12+usr/share/unity8/DeviceConfiguration.qml
13+usr/share/unity8/OrientedShell.qml
1114usr/share/unity8/Shell.qml
1215usr/share/unity8/Stages
1316usr/share/unity8/Tutorial
plugins/Dash/CardCreator.js+32−10
@@ -16,6 +16,9 @@
1616
1717.pragma library
1818
19+// %1 is the template["card-background"] string
20+// %2 is the template["card-background"]["elements"][0]
21+// %3 is the template["card-background"]["elements"][1]
1922var kBackgroundLoaderCode = 'Loader {\n\
2023 id: backgroundLoader; \n\
2124 objectName: "backgroundLoader"; \n\
@@ -34,18 +37,14 @@ var kBackgroundLoaderCode = 'Loader {\n\
3437 objectName: "backgroundImage"; \n\
3538 source: { \n\
3639 if (cardData && typeof cardData["background"] === "string") return cardData["background"]; \n\
37− else if (template && typeof template["card-background"] === "string") return template["card-background"]; \n\
38− else return ""; \n\
40+ else return "%1"; \n\
3941 } \n\
4042 } \n\
4143 function getColor(index) { \n\
4244 if (cardData && typeof cardData["background"] === "object" \n\
4345 && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) { \n\
4446 return cardData["background"]["elements"][index]; \n\
45− } else if (template && typeof template["card-background"] === "object" \n\
46− && (template["card-background"]["type"] === "color" || template["card-background"]["type"] === "gradient")) { \n\
47− return template["card-background"]["elements"][index]; \n\
48− } else return undefined; \n\
47+ } else return index === 0 ? %2 : %3; \n\
4948 } \n\
5049 } \n\
5150 }\n';
@@ -70,6 +69,7 @@ var kArtShapeHolderCode = 'Item { \n\
7069 property bool doShapeItem: components["art"]["conciergeMode"] !== true; \n\
7170 visible: image.status == Image.Ready; \n\
7271 readonly property alias image: artImage.image; \n\
72+ property alias borderSource: artShapeShape.borderSource; \n\
7373 ShaderEffectSource { \n\
7474 id: artShapeSource; \n\
7575 sourceItem: artImage; \n\
@@ -79,6 +79,7 @@ var kArtShapeHolderCode = 'Item { \n\
7979 hideSource: doShapeItem; \n\
8080 } \n\
8181 Shape { \n\
82+ id: artShapeShape; \n\
8283 image: artShapeSource; \n\
8384 anchors.fill: parent; \n\
8485 visible: doShapeItem; \n\
@@ -315,9 +316,11 @@ var kSummaryLabelCode = 'Label { \n\
315316
316317function cardString(template, components) {
317318 var code;
319+
320+ var templateInteractive = (template == null ? true : (template["non-interactive"] !== undefined ? !template["non-interactive"] : true)) ? "true" : "false";
321+
318322 code = 'AbstractButton { \n\
319323 id: root; \n\
320− property var template; \n\
321324 property var components; \n\
322325 property var cardData; \n\
323326 property var artShapeBorderSource: undefined; \n\
@@ -330,8 +333,8 @@ function cardString(template, components) {
330333 property bool asynchronous: true; \n\
331334 property bool showHeader: true; \n\
332335 implicitWidth: childrenRect.width; \n\
333− enabled: root.template == null ? true : (root.template["non-interactive"] !== undefined ? !root.template["non-interactive"] : true); \n\
334− \n';
336+ enabled: %1; \n\
337+ \n'.arg(templateInteractive);
335338
336339 var hasArt = components["art"] && components["art"]["field"] || false;
337340 var hasSummary = components["summary"] || false;
@@ -348,7 +351,18 @@ function cardString(template, components) {
348351 var hasAttributes = hasTitle && components["attributes"]["field"] || false;
349352
350353 if (hasBackground) {
351− code += kBackgroundLoaderCode;
354+ var templateCardBackground = (template && typeof template["card-background"] === "string") ? template["card-background"] : "";
355+ var backgroundElements0;
356+ var backgroundElements1;
357+ if (template && typeof template["card-background"] === "object" && (template["card-background"]["type"] === "color" || template["card-background"]["type"] === "gradient")) {
358+ if (template["card-background"]["elements"][0] !== undefined) {
359+ backgroundElements0 = '"%1"'.arg(template["card-background"]["elements"][0]);
360+ }
361+ if (template["card-background"]["elements"][1] !== undefined) {
362+ backgroundElements1 = '"%1"'.arg(template["card-background"]["elements"][1]);
363+ }
364+ }
365+ code += kBackgroundLoaderCode.arg(templateCardBackground).arg(backgroundElements0).arg(backgroundElements1);
352366 }
353367
354368 if (hasArt) {
@@ -375,6 +389,10 @@ function cardString(template, components) {
375389 }
376390
377391 code += kArtShapeHolderCode.arg(artAnchors).arg(widthCode).arg(heightCode);
392+ var fallback = components["art"] && components["art"]["fallback"] || "";
393+ if (fallback !== "") {
394+ code += 'Connections { target: artShapeLoader.item ? artShapeLoader.item.image : null; onStatusChanged: if (artShapeLoader.item.image.status === Image.Error) artShapeLoader.item.image.source = "%1"; } \n'.arg(fallback);
395+ }
378396 } else {
379397 code += 'readonly property size artShapeSize: Qt.size(-1, -1);\n'
380398 }
@@ -454,6 +472,10 @@ function cardString(template, components) {
454472
455473 var mascotImageVisible = useMascotShape ? 'false' : 'showHeader';
456474 mascotCode = kMascotImageCode.arg(mascotAnchors).arg(mascotImageVisible);
475+ var fallback = components["mascot"] && components["mascot"]["fallback"] || "";
476+ if (fallback !== "") {
477+ code += 'Connections { target: mascotImage.image; onStatusChanged: if (mascotImage.image.status === Image.Error) mascotImage.source = "%1"; } \n'.arg(fallback);
478+ }
457479 }
458480
459481 var summaryColorWithBackground = 'backgroundLoader.active && backgroundLoader.item && root.scopeStyle ? root.scopeStyle.getTextColor(backgroundLoader.item.luminance) : (backgroundLoader.item && backgroundLoader.item.luminance > 0.7 ? Theme.palette.normal.baseText : "white")';
plugins/Greeter/Unity/Launcher/CMakeLists.txt+1−1
@@ -1,5 +1,5 @@
11pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=6)
2−pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=5)
2+pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=6)
33pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
44
55add_definitions(-DSM_BUSNAME=systemBus)
plugins/Unity/Launcher/CMakeLists.txt+1−1
@@ -1,5 +1,5 @@
11pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=6)
2−pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=5)
2+pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=6)
33pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
44
55add_definitions(-DSM_BUSNAME=systemBus)
plugins/Utils/CMakeLists.txt+1
@@ -18,6 +18,7 @@ set(QMLPLUGIN_SRC
1818 Timer.cpp
1919 unitymenumodelpaths.cpp
2020 windowkeysfilter.cpp
21+ windowscreenshotprovider.cpp
2122 easingcurve.cpp
2223 windowstatestorage.cpp
2324 plugin.cpp
plugins/Utils/plugin.cpp+4−1
@@ -23,6 +23,7 @@
2323#include "plugin.h"
2424
2525// local
26+#include "easingcurve.h"
2627#include "HomeKeyWatcher.h"
2728#include "inputwatcher.h"
2829#include "qlimitproxymodelqml.h"
@@ -31,7 +32,7 @@
3132#include "timeformatter.h"
3233#include "unitymenumodelpaths.h"
3334#include "windowkeysfilter.h"
34−#include "easingcurve.h"
35+#include "windowscreenshotprovider.h"
3536#include "windowstatestorage.h"
3637#include "constants.h"
3738
@@ -70,4 +71,6 @@ void UtilsPlugin::registerTypes(const char *uri)
7071void UtilsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
7172{
7273 QQmlExtensionPlugin::initializeEngine(engine, uri);
74+
75+ engine->addImageProvider(QLatin1String("window"), new WindowScreenshotProvider);
7376}
plugins/Utils/windowscreenshotprovider.cppnew file+59
@@ -0,0 +1,59 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "windowscreenshotprovider.h"
18+
19+#include <QGuiApplication>
20+#include <QQuickWindow>
21+
22+WindowScreenshotProvider::WindowScreenshotProvider()
23+ : QQuickImageProvider(QQmlImageProviderBase::Image, 0)
24+{
25+}
26+
27+// A very simple implementation where we assume that there's only one window and that it's a
28+// QQuickWindow. Thus the id parameter is irrelevant.
29+//
30+// Idea: Make the id contain the objectName of the QQuickWindow once we care about a multi-display
31+// compositor?
32+// Strictly speaking that could be the actual QWindow::winId(), but that's mostly a
33+// meaningless arbitrary number.
34+QImage WindowScreenshotProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
35+{
36+ Q_UNUSED(id);
37+ Q_UNUSED(requestedSize);
38+
39+ QWindowList windows = QGuiApplication::topLevelWindows();
40+
41+ if (windows.count() != 1) {
42+ size->rwidth() = 0;
43+ size->rheight() = 0;
44+ return QImage();
45+ }
46+
47+ QQuickWindow *quickWindow = qobject_cast<QQuickWindow *>(windows[0]);
48+
49+ if (!quickWindow) {
50+ size->rwidth() = 0;
51+ size->rheight() = 0;
52+ return QImage();
53+ }
54+
55+ QImage image = quickWindow->grabWindow();
56+ size->rwidth() = image.width();
57+ size->rheight() = image.height();
58+ return image;
59+}
plugins/Utils/windowscreenshotprovider.hnew file+31
@@ -0,0 +1,31 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef WINDOW_SCREENSHOT_PROVIDER_H_
18+#define WINDOW_SCREENSHOT_PROVIDER_H_
19+
20+#include <QQuickImageProvider>
21+
22+class WindowScreenshotProvider : public QQuickImageProvider
23+{
24+public:
25+ WindowScreenshotProvider();
26+
27+ // id is ignored for now
28+ QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override;
29+};
30+
31+#endif // WINDOW_SCREENSHOT_PROVIDER_H_
plugins/Wizard/PageList.cpp+1−1
@@ -45,7 +45,7 @@ PageList::PageList(QObject *parent)
4545 QSet<QString> disabledPages;
4646 QStringList dataDirs;
4747
48− if (!isRunningInstalled() && getenv("UNITY_TEST_ENV") == nullptr) {
48+ if (!isRunningInstalled() && getenv("WIZARD_TESTING") == nullptr) {
4949 dataDirs = QStringList() << qmlDirectory();
5050 } else {
5151 dataDirs = shellDataDirs();
po/ast.po+86−86
@@ -8,15 +8,15 @@ msgstr ""
88"Project-Id-Version: unity8\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2015-04-14 16:13+0200\n"
11−"PO-Revision-Date: 2015-03-02 18:12+0000\n"
12−"Last-Translator: ivarela <ivarela@ubuntu.com>\n"
11+"PO-Revision-Date: 2015-06-11 23:39+0000\n"
12+"Last-Translator: enolp <enolp@softastur.org>\n"
1313"Language-Team: Asturian <ast@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
1717"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2015-04-23 06:48+0000\n"
19−"X-Generator: Launchpad (build 17430)\n"
18+"X-Launchpad-Export-Date: 2015-06-16 11:20+0000\n"
19+"X-Generator: Launchpad (build 17557)\n"
2020
2121#: plugins/LightDM/Greeter.cpp:112
2222msgid "Password: "
@@ -25,11 +25,11 @@ msgstr "Contraseña: "
2525#: plugins/Unity/Launcher/launcheritem.cpp:45
2626#: plugins/Unity/Launcher/launcheritem.cpp:100
2727msgid "Pin shortcut"
28−msgstr "Anclar"
28+msgstr "Anclar atayu"
2929
3030#: plugins/Unity/Launcher/launcheritem.cpp:100
3131msgid "Unpin shortcut"
32−msgstr "Desanclar"
32+msgstr "Desanclar atayu"
3333
3434#: qml/Components/DelayedLockscreen.qml:49
3535msgid "Device Locked"
@@ -37,7 +37,7 @@ msgstr "Preséu bloquiáu"
3737
3838#: qml/Components/DelayedLockscreen.qml:64
3939msgid "You have been locked out due to too many failed passphrase attempts."
40−msgstr "Quedasti bloquiáu polos escesivos intentos fallíos de frase de pasu."
40+msgstr "Quedasti bloquiáu polos escesivos intentos fallíos de fras de pasu."
4141
4242#: qml/Components/DelayedLockscreen.qml:65
4343msgid "You have been locked out due to too many failed passcode attempts."
@@ -48,27 +48,27 @@ msgstr ""
4848#, qt-format
4949msgid "Please wait %1 minute and then try again…"
5050msgid_plural "Please wait %1 minutes and then try again…"
51−msgstr[0] "Espera %1 minutu y vuelvi a intentalo…"
52−msgstr[1] "Espera %1 minutos y vuelvi a intentalo…"
51+msgstr[0] "Por favor, espera %1 minutu y vuelvi a intentalo…"
52+msgstr[1] "Por favor, espera %1 minutos y vuelvi a intentalo…"
5353
5454#: qml/Components/Dialogs.qml:69
5555msgctxt "Title: Lock/Log out dialog"
5656msgid "Log out"
57−msgstr ""
57+msgstr "Zarrar sesión"
5858
5959#: qml/Components/Dialogs.qml:70
6060msgid "Are you sure you want to log out?"
61−msgstr "De xuru que quies zarrar la sesión?"
61+msgstr "¿De xuru que quies zarrar la sesión?"
6262
6363#: qml/Components/Dialogs.qml:72
6464msgctxt "Button: Lock the system"
6565msgid "Lock"
66−msgstr ""
66+msgstr "Bloquiar"
6767
6868#: qml/Components/Dialogs.qml:79
6969msgctxt "Button: Log out from the system"
7070msgid "Log Out"
71−msgstr ""
71+msgstr "Zarrar sesión"
7272
7373#: qml/Components/Dialogs.qml:86 qml/Components/Dialogs.qml:117
7474#: qml/Components/Dialogs.qml:173
@@ -78,30 +78,30 @@ msgstr "Encaboxar"
7878#: qml/Components/Dialogs.qml:98
7979msgctxt "Title: Reboot/Shut down dialog"
8080msgid "Shut down"
81−msgstr ""
81+msgstr "Apagáu"
8282
8383#: qml/Components/Dialogs.qml:99
8484msgid "Are you sure you want to shut down?"
85−msgstr "De xuru que quies apagar?"
85+msgstr "¿De xuru que quies apagar?"
8686
8787#: qml/Components/Dialogs.qml:101
8888msgctxt "Button: Reboot the system"
8989msgid "Reboot"
90−msgstr ""
90+msgstr "Reaniciar"
9191
9292#: qml/Components/Dialogs.qml:109
9393msgctxt "Button: Shut down the system"
9494msgid "Shut down"
95−msgstr ""
95+msgstr "Apagar"
9696
9797#: qml/Components/Dialogs.qml:129
9898msgctxt "Title: Reboot dialog"
9999msgid "Reboot"
100−msgstr ""
100+msgstr "Reaniciu"
101101
102102#: qml/Components/Dialogs.qml:130
103103msgid "Are you sure you want to reboot?"
104−msgstr "De xuru que quies reaniciar?"
104+msgstr "¿De xuru que quies reaniciar?"
105105
106106#: qml/Components/Dialogs.qml:132
107107msgid "No"
@@ -121,18 +121,18 @@ msgid ""
121121"Are you sure you would like\n"
122122"to power off?"
123123msgstr ""
124−"De xuru que quies\n"
124+"¿De xuru que te prestaría\n"
125125"apagar?"
126126
127127#: qml/Components/Dialogs.qml:155
128128msgctxt "Button: Power off the system"
129129msgid "Power off"
130−msgstr ""
130+msgstr "Apagar"
131131
132132#: qml/Components/Dialogs.qml:164
133133msgctxt "Button: Restart the system"
134134msgid "Restart"
135−msgstr ""
135+msgstr "Reaniciar"
136136
137137#: qml/Components/Lockscreen.qml:245
138138msgid "Return to Call"
@@ -157,22 +157,22 @@ msgstr "Ver too"
157157#: qml/Dash/GenericScopeView.qml:594
158158msgctxt "Label: Hint for dash search line edit"
159159msgid "Search"
160−msgstr ""
160+msgstr "Guetar"
161161
162162#: qml/Dash/PageHeader.qml:269
163163msgctxt "Button: Open the Ubuntu Store"
164164msgid "Store"
165−msgstr ""
165+msgstr "Tienda"
166166
167167#: qml/Dash/PageHeader.qml:276
168168msgctxt "Button: Start a search in the current dash scope"
169169msgid "Search"
170−msgstr ""
170+msgstr "Guetar"
171171
172172#: qml/Dash/PageHeader.qml:286
173173msgctxt "Button: Show the current dash scope settings"
174174msgid "Settings"
175−msgstr ""
175+msgstr "Axustes"
176176
177177#: qml/Dash/PageHeader.qml:293
178178msgid "Remove from Favorites"
@@ -204,19 +204,19 @@ msgstr "Unviar"
204204
205205#: qml/Dash/PullToRefreshScopeStyle.qml:55
206206msgid "Pull to refresh…"
207−msgstr "Tirar p'anovar..."
207+msgstr "Tira p'anovar..."
208208
209209#: qml/Dash/PullToRefreshScopeStyle.qml:60
210210msgid "Release to refresh…"
211−msgstr "Soltar p'anovar..."
211+msgstr "Sueltar p'anovar..."
212212
213213#: qml/Dash/ScopeSettings/ScopeSettingBoolean.qml:43
214214msgid "Enable location data"
215−msgstr "Activar datos de llocalización"
215+msgstr "Habilitar datos d'allugamientu"
216216
217217#: qml/Dash/ScopesList.qml:66
218218msgid "Manage"
219−msgstr "Alministrar"
219+msgstr "Xestionar"
220220
221221#: qml/Dash/ScopesList.qml:112
222222msgid "Home"
@@ -232,52 +232,52 @@ msgstr "Desbloquiar"
232232
233233#: qml/Greeter/LoginList.qml:236
234234msgid "Retry"
235−msgstr ""
235+msgstr "Reintentar"
236236
237237#: qml/Greeter/LoginList.qml:237
238238msgid "Tap to unlock"
239−msgstr ""
239+msgstr "Calca pa desbloquiar"
240240
241241#: qml/Greeter/NarrowView.qml:53 qml/Wizard/Pages/passwd-confirm.qml:53
242242msgid "Sorry, incorrect passphrase."
243−msgstr "Fras de pasu incorreuta"
243+msgstr "Perdón, fras de pasu incorreuta"
244244
245245#: qml/Greeter/NarrowView.qml:54 qml/Wizard/Pages/passwd-confirm.qml:54
246246msgid "Sorry, incorrect passcode."
247−msgstr "Códigu de pasu incorreutu"
247+msgstr "Perdón, códigu de pasu incorreutu."
248248
249249#: qml/Greeter/NarrowView.qml:55
250250msgid "This will be your last attempt."
251−msgstr "Esti ye'l caberu intentu."
251+msgstr "Esti sedra'l to caberu intentu"
252252
253253#: qml/Greeter/NarrowView.qml:57
254254msgid ""
255255"If passphrase is entered incorrectly, your phone will conduct a factory "
256256"reset and all personal data will be deleted."
257257msgstr ""
258−"Si s'inxerta la fras incorreutamente, el teléfonu va volver a la "
259−"configuración de fábrica y van perdese tolos datos personales."
258+"Si s'inxerta la fras de pasu incorreutamente, el to teléfonu va reafitase y "
259+"van desaniciase tolos datos personales."
260260
261261#: qml/Greeter/NarrowView.qml:58
262262msgid ""
263263"If passcode is entered incorrectly, your phone will conduct a factory reset "
264264"and all personal data will be deleted."
265265msgstr ""
266−"Si s'inxerta'l códigu incorreutamente, el teléfonu va volver a la "
267−"configuración de fábrica y van perdese tolos datos personales."
266+"Si s'inxerta'l códigu de pasu incorreutamente, el to teléfonu va reafitase y "
267+"van desaniciase tolos datos personales."
268268
269269#: qml/Greeter/NarrowView.qml:115
270270#, qt-format
271271msgid "Enter %1"
272−msgstr "Escribi %1"
272+msgstr "Introduz %1"
273273
274274#: qml/Greeter/NarrowView.qml:116 qml/Wizard/Pages/passwd-set.qml:60
275275msgid "Enter passphrase"
276−msgstr "Escribi la frase contraseña"
276+msgstr "Introduz la fras de pasu"
277277
278278#: qml/Greeter/NarrowView.qml:117
279279msgid "Enter passcode"
280−msgstr "Escribi'l códigu de pasu"
280+msgstr "Introduz el códigu de pasu"
281281
282282#: qml/Greeter/NarrowView.qml:118
283283#, qt-format
@@ -290,7 +290,7 @@ msgstr "Sentímoslo, contraseña incorreuta"
290290
291291#: qml/Greeter/NarrowView.qml:120
292292msgid "Please re-enter"
293−msgstr "Vuelvi a escribila"
293+msgstr "Por favor, reintroduzla"
294294
295295#: qml/Greeter/NarrowView.qml:121
296296msgid "Sorry, incorrect passcode"
@@ -302,7 +302,7 @@ msgstr "Amosar contraseña"
302302
303303#: qml/Panel/ActiveCallHint.qml:79
304304msgid "Tap to return to call..."
305−msgstr "Tocar pa tornar a la llamada..."
305+msgstr "Toca pa tornar a la llamada..."
306306
307307#: qml/Panel/ActiveCallHint.qml:92
308308msgid "Conference"
@@ -310,11 +310,11 @@ msgstr "Conferencia"
310310
311311#: qml/Panel/Indicators/MenuItemFactory.qml:639
312312msgid "Nothing is playing"
313−msgstr "Nun hai nada en reproducción"
313+msgstr "Nun ta reproduciéndose nada"
314314
315315#: qml/Panel/Indicators/MenuItemFactory.qml:787
316316msgid "In queue…"
317−msgstr "N'espera…"
317+msgstr "Na cola..."
318318
319319#: qml/Panel/Indicators/MenuItemFactory.qml:791
320320msgid "Downloading"
@@ -339,7 +339,7 @@ msgstr "Falló, toca pa reintentar"
339339#: qml/Panel/Indicators/MessageMenuItemFactory.qml:149
340340msgctxt "Label: Hint in message indicator line edit"
341341msgid "Reply"
342−msgstr ""
342+msgstr "Responder"
343343
344344#: qml/Panel/Indicators/ModemInfoItem.qml:105
345345msgid "Unlock SIM"
@@ -351,7 +351,7 @@ msgstr "Roaming"
351351
352352#: qml/Tutorial/TutorialBottomFinish.qml:24
353353msgid "This action does different things for different apps"
354−msgstr "Esta aición tiene efeutos distintos en cada aplicación"
354+msgstr "Esta aición fai coses distintes p'aplicaciones estremaes"
355355
356356#: qml/Tutorial/TutorialBottomFinish.qml:25
357357msgid "Tap here to finish."
@@ -363,7 +363,7 @@ msgstr "Abrir menús especiales"
363363
364364#: qml/Tutorial/TutorialBottom.qml:29
365365msgid "Swipe up from the bottom edge."
366−msgstr ""
366+msgstr "Esliza dende'l berbesu d'embaxo."
367367
368368#: qml/Tutorial/TutorialContent.qml:81 qml/Tutorial/TutorialLeftFinish.qml:25
369369#: qml/Tutorial/TutorialRight.qml:144
@@ -372,11 +372,11 @@ msgstr "Toca equí pa siguir."
372372
373373#: qml/Tutorial/TutorialContent.qml:81
374374msgid "Click here to finish."
375−msgstr ""
375+msgstr "Primi equí pa finar."
376376
377377#: qml/Tutorial/TutorialLeftFinish.qml:24
378378msgid "These are the shortcuts to favorite apps"
379−msgstr ""
379+msgstr "Estos son los atayos a les aplicaciones favorites"
380380
381381#: qml/Tutorial/TutorialLeft.qml:26
382382msgid "Open the launcher"
@@ -384,44 +384,44 @@ msgstr "Abrir el llanzador"
384384
385385#: qml/Tutorial/TutorialLeft.qml:27
386386msgid "Short swipe from the left edge."
387−msgstr ""
387+msgstr "Esliza un poco dende'l berbesu esquierdu."
388388
389389#: qml/Tutorial/TutorialPage.qml:178
390390msgid "You almost got it!"
391−msgstr ""
391+msgstr "¡Yá cuasi finamos!"
392392
393393#: qml/Tutorial/TutorialPage.qml:195
394394msgid "Try again."
395−msgstr ""
395+msgstr "Intentar de nueves"
396396
397397#: qml/Tutorial/TutorialRight.qml:31
398398msgid "To view open apps"
399−msgstr ""
399+msgstr "Pa ver aplicaciones abiertes"
400400
401401#: qml/Tutorial/TutorialRight.qml:32
402402msgid "Long swipe from the right edge."
403−msgstr ""
403+msgstr "Eslizamientu llargu dende'l berbesu drechu."
404404
405405#: qml/Tutorial/TutorialRight.qml:122
406406msgid "View all your running tasks."
407−msgstr ""
407+msgstr "Mira toles tos aplicaciones n'execución."
408408
409409#: qml/Wizard/Page.qml:89
410410msgctxt "Button: Go back one page in the Wizard"
411411msgid "Back"
412−msgstr ""
412+msgstr "Atrás"
413413
414414#: qml/Wizard/Pages/10-welcome.qml:27
415415msgid "Hi!"
416−msgstr "Hola!"
416+msgstr "¡Hola!"
417417
418418#: qml/Wizard/Pages/10-welcome.qml:44
419419msgid "Welcome to your Ubuntu phone."
420−msgstr "Dámoste l'acoyida a Ubuntu Phone"
420+msgstr "Bienllegáu/ada al to teléfonu d'Ubuntu"
421421
422422#: qml/Wizard/Pages/10-welcome.qml:52
423423msgid "Let’s get started."
424−msgstr "Entamamos."
424+msgstr "Entamemos."
425425
426426#: qml/Wizard/Pages/10-welcome.qml:88 qml/Wizard/Pages/30-passwd-type.qml:128
427427#: qml/Wizard/Pages/40-wifi.qml:215 qml/Wizard/Pages/50-location.qml:131
@@ -432,7 +432,7 @@ msgstr "Siguir"
432432
433433#: qml/Wizard/Pages/20-sim.qml:25
434434msgid "Add a SIM card and restart your device"
435−msgstr "Amiesta una tarxeta SIM y reanicia'l preséu"
435+msgstr "Amiesta una tarxeta SIM y reanicia'l to preséu"
436436
437437#: qml/Wizard/Pages/20-sim.qml:59
438438msgid "Without it, you won’t be able to make calls or use text messaging."
@@ -440,49 +440,49 @@ msgstr "Ensin ella, nun vas poder facer llamaes nin unviar mensaxes."
440440
441441#: qml/Wizard/Pages/20-sim.qml:73 qml/Wizard/Pages/40-wifi.qml:215
442442msgid "Skip"
443−msgstr "Omitir"
443+msgstr "Saltar"
444444
445445#: qml/Wizard/Pages/30-passwd-type.qml:39
446446msgid "Lock security"
447−msgstr "Seguridá de bloquéu"
447+msgstr "Seguranza del bloquéu"
448448
449449#: qml/Wizard/Pages/30-passwd-type.qml:74
450450msgid "Please select how you’d like to unlock your phone."
451−msgstr "Seleiciona cómo te prestaría desbloquiar el teléfonu."
451+msgstr "Por favor, esbilla como te prestaría desbloquiar el teléfonu."
452452
453453#: qml/Wizard/Pages/30-passwd-type.qml:97
454454msgctxt "Label: Type of security method"
455455msgid "Swipe"
456−msgstr ""
456+msgstr "Eslizar"
457457
458458#: qml/Wizard/Pages/30-passwd-type.qml:98
459459msgctxt "Label: Description of security method"
460460msgid "No security"
461−msgstr ""
461+msgstr "Ensin seguranza"
462462
463463#: qml/Wizard/Pages/30-passwd-type.qml:100
464464msgctxt "Label: Type of security method"
465465msgid "Passcode"
466−msgstr ""
466+msgstr "Códigu de pasu"
467467
468468#: qml/Wizard/Pages/30-passwd-type.qml:101
469469msgctxt "Label: Description of security method"
470470msgid "4 digits only"
471−msgstr ""
471+msgstr "Namái 4 díxitos"
472472
473473#: qml/Wizard/Pages/30-passwd-type.qml:103
474474msgctxt "Label: Type of security method"
475475msgid "Passphrase"
476−msgstr ""
476+msgstr "Fras de pasu"
477477
478478#: qml/Wizard/Pages/30-passwd-type.qml:104
479479msgctxt "Label: Description of security method"
480480msgid "Numbers and letters"
481−msgstr ""
481+msgstr "Númberos y lletres"
482482
483483#: qml/Wizard/Pages/40-wifi.qml:29
484484msgid "Connect to Wi‑Fi"
485−msgstr "Coneutar a WiFi"
485+msgstr "Coneutase al WiFi"
486486
487487#: qml/Wizard/Pages/40-wifi.qml:168
488488msgid "Available networks…"
@@ -494,11 +494,11 @@ msgstr "Nun hai redes disponibles"
494494
495495#: qml/Wizard/Pages/50-location.qml:27
496496msgid "Location"
497−msgstr "Llocalización"
497+msgstr "Allugamientu"
498498
499499#: qml/Wizard/Pages/50-location.qml:62
500500msgid "Let the phone detect your location:"
501−msgstr "Permitir al teléfonu deteutar la llocalización:"
501+msgstr "Dexar al teléfonu deteutar l'allugamientu:"
502502
503503#: qml/Wizard/Pages/50-location.qml:69
504504msgid "Using GPS only (less accurate)"
@@ -507,7 +507,7 @@ msgstr "Namái usando GPS (menos precisión)"
507507#: qml/Wizard/Pages/50-location.qml:86
508508msgid "Using GPS, anonymized Wi-Fi and cellular network info (recommended)"
509509msgstr ""
510−"Usando GPS, Wifi anónimes ya información de redes móviles (recomiéndase)"
510+"Usando GPS, Wi-fi anónimes ya información de redes móviles (aconseyao)"
511511
512512#. TRANSLATORS: HERE is a trademark for Nokia's location service, you probably shouldn't translate it
513513#: qml/Wizard/Pages/50-location.qml:103
@@ -515,8 +515,8 @@ msgid ""
515515"By selecting this option you agree to the Nokia HERE <a href='#'>terms and "
516516"conditions</a>."
517517msgstr ""
518−"Seleicionando esta opción, aceutes los <a href='#'>términos y "
519−"condiciones</a> de Nokia HERE."
518+"Esbillando esta opción aceutes los <a href='#'>términos y condiciones</a> de "
519+"Nokia HERE."
520520
521521#: qml/Wizard/Pages/50-location.qml:112
522522msgid "Not at all"
@@ -528,7 +528,7 @@ msgstr "Pues cambialo dempués n'<b>Axustes del sistema</b>."
528528
529529#: qml/Wizard/Pages/60-reporting.qml:24
530530msgid "Improving your experience"
531−msgstr "Ameyorar la esperiencia"
531+msgstr "Ameyorando la to esperiencia"
532532
533533#: qml/Wizard/Pages/60-reporting.qml:36
534534msgid ""
@@ -543,7 +543,7 @@ msgid ""
543543"This can be disabled in <b>System Settings</b> under <b>Security &amp; "
544544"Privacy</b>"
545545msgstr ""
546−"Esto pue desactivase nos <b>Axustes del sistema</b> en <b>Seguranza y "
546+"Esto pue deshabilitase nos <b>Axustes del sistema</b> en <b>Seguranza y "
547547"privacidá</b>"
548548
549549#: qml/Wizard/Pages/80-finished.qml:24
@@ -552,11 +552,11 @@ msgstr "Too fecho"
552552
553553#: qml/Wizard/Pages/80-finished.qml:39
554554msgid "Nice work!"
555−msgstr "Bon trabayu!"
555+msgstr "¡Bon trabayu!"
556556
557557#: qml/Wizard/Pages/80-finished.qml:46
558558msgid "Your phone is now ready to use."
559−msgstr "El teléfonu yá ta preparáu pa usar."
559+msgstr "El teléfonu yá ta tresnáu pa usase."
560560
561561#: qml/Wizard/Pages/80-finished.qml:53
562562msgid "Finish"
@@ -568,24 +568,24 @@ msgstr "Términos y condiciones"
568568
569569#: qml/Wizard/Pages/passwd-confirm.qml:49
570570msgid "Confirm passphrase"
571−msgstr "Confirmar fras secreta"
571+msgstr "Confirmar frase de pasu"
572572
573573#: qml/Wizard/Pages/passwd-confirm.qml:50
574574msgid "Confirm passcode"
575−msgstr "Confirmar el códigu de pasu"
575+msgstr "Confirmar códigu de pasu"
576576
577577#: qml/Wizard/Pages/passwd-confirm.qml:53
578578#: qml/Wizard/Pages/passwd-confirm.qml:54
579579msgid "Please try again."
580−msgstr "Vuelvi a intentalo."
580+msgstr "Por favor, vuelvi a intentalo."
581581
582582#: qml/Wizard/Pages/passwd-set.qml:61
583583msgid "Choose your passcode"
584−msgstr "Escueyi'l códigu de pasu"
584+msgstr "Escueyi'l to códigu de pasu"
585585
586586#: qml/Wizard/Pages/passwd-set.qml:68
587587msgid "Passphrase must be 4 characters long"
588−msgstr "La fras de contraseña tien de ser de 4 caráuteres"
588+msgstr "La fras de pasu tien de ser 4 caráuteres de llargor"
589589
590590#: qml/Wizard/StackButton.qml:39
591591#, qt-format
po/de.po+6−6
@@ -8,15 +8,15 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2015-04-14 16:13+0200\n"
11−"PO-Revision-Date: 2015-05-14 15:49+0000\n"
12−"Last-Translator: Tobias Bannert <Unknown>\n"
11+"PO-Revision-Date: 2015-06-17 14:05+0000\n"
12+"Last-Translator: JkB <Unknown>\n"
1313"Language-Team: German <de@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
1717"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2015-05-15 05:31+0000\n"
19−"X-Generator: Launchpad (build 17493)\n"
18+"X-Launchpad-Export-Date: 2015-06-23 05:32+0000\n"
19+"X-Generator: Launchpad (build 17572)\n"
2020
2121#: plugins/LightDM/Greeter.cpp:112
2222msgid "Password: "
@@ -117,7 +117,7 @@ msgstr "Ja"
117117#: qml/Components/Dialogs.qml:152
118118msgctxt "Title: Power off/Restart dialog"
119119msgid "Power"
120−msgstr "Leistung"
120+msgstr "Ausschalten"
121121
122122#: qml/Components/Dialogs.qml:153
123123msgid ""
@@ -276,7 +276,7 @@ msgstr "%1 eingeben"
276276
277277#: qml/Greeter/NarrowView.qml:116 qml/Wizard/Pages/passwd-set.qml:60
278278msgid "Enter passphrase"
279−msgstr "Passwort eingeben"
279+msgstr "Passphrase eingeben"
280280
281281#: qml/Greeter/NarrowView.qml:117
282282msgid "Enter passcode"
po/el.po+11−11
@@ -8,15 +8,15 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2015-04-14 16:13+0200\n"
11−"PO-Revision-Date: 2015-04-22 09:03+0000\n"
11+"PO-Revision-Date: 2015-06-18 19:05+0000\n"
1212"Last-Translator: Simos Xenitellis <Unknown>\n"
1313"Language-Team: Greek <el@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
1717"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2015-04-23 06:48+0000\n"
19−"X-Generator: Launchpad (build 17430)\n"
18+"X-Launchpad-Export-Date: 2015-06-23 05:33+0000\n"
19+"X-Generator: Launchpad (build 17572)\n"
2020
2121#: plugins/LightDM/Greeter.cpp:112
2222msgid "Password: "
@@ -117,7 +117,7 @@ msgstr "Ναι"
117117#: qml/Components/Dialogs.qml:152
118118msgctxt "Title: Power off/Restart dialog"
119119msgid "Power"
120−msgstr ""
120+msgstr "Τερματισμός"
121121
122122#: qml/Components/Dialogs.qml:153
123123msgid ""
@@ -165,17 +165,17 @@ msgstr "Αναζήτηση"
165165#: qml/Dash/PageHeader.qml:269
166166msgctxt "Button: Open the Ubuntu Store"
167167msgid "Store"
168−msgstr ""
168+msgstr "Κατάστημα"
169169
170170#: qml/Dash/PageHeader.qml:276
171171msgctxt "Button: Start a search in the current dash scope"
172172msgid "Search"
173−msgstr ""
173+msgstr "Αναζήτηση"
174174
175175#: qml/Dash/PageHeader.qml:286
176176msgctxt "Button: Show the current dash scope settings"
177177msgid "Settings"
178−msgstr ""
178+msgstr "Ρυθμίσεις"
179179
180180#: qml/Dash/PageHeader.qml:293
181181msgid "Remove from Favorites"
@@ -378,7 +378,7 @@ msgstr "Πατήστε εδώ για συνέχεια."
378378
379379#: qml/Tutorial/TutorialContent.qml:81
380380msgid "Click here to finish."
381−msgstr ""
381+msgstr "Πατήστε εδώ για τέλος."
382382
383383#: qml/Tutorial/TutorialLeftFinish.qml:24
384384msgid "These are the shortcuts to favorite apps"
@@ -510,12 +510,12 @@ msgstr "Επιτρέψετε το τηλέφωνο να εντοπίσει τη
510510
511511#: qml/Wizard/Pages/50-location.qml:69
512512msgid "Using GPS only (less accurate)"
513−msgstr "Χρήση μόνο GPS (λιγότερο ακριβή)"
513+msgstr "Χρήση μόνο GPS (λιγότερο ακριβής)"
514514
515515#: qml/Wizard/Pages/50-location.qml:86
516516msgid "Using GPS, anonymized Wi-Fi and cellular network info (recommended)"
517517msgstr ""
518−"Χρήση GPS, ανωνυνοποιημένα δίκτυα Wi-Fi και πληροφορίες δικτύων κινητής "
518+"Χρήση GPS, ανωνυμοποιημένων δικτύων Wi-Fi και πληροφορίες δικτύων κινητής "
519519"τηλεφωνίας (συνιστάται)"
520520
521521#. TRANSLATORS: HERE is a trademark for Nokia's location service, you probably shouldn't translate it
@@ -546,7 +546,7 @@ msgid ""
546546"partners, the makers of the operating system."
547547msgstr ""
548548"Το τηλέφωνό σας είναι ρυθμισμένο να στέλνει αυτόματα αναφορές σφαλμάτων στην "
549−"Canonical και τους συνεργάτες της, τους δημιουργούς τους λειτουργικού "
549+"Canonical και τους συνεργάτες της, τους δημιουργούς του λειτουργικού "
550550"συστήματος."
551551
552552#: qml/Wizard/Pages/60-reporting.qml:43
po/eo.po+1−1
@@ -15,7 +15,7 @@ msgstr ""
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
1717"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2015-06-07 05:29+0000\n"
18+"X-Launchpad-Export-Date: 2015-06-08 05:26+0000\n"
1919"X-Generator: Launchpad (build 17540)\n"
2020
2121#: plugins/LightDM/Greeter.cpp:112
po/hu.po+4−4
@@ -8,15 +8,15 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2015-04-14 16:13+0200\n"
11−"PO-Revision-Date: 2015-05-04 18:02+0000\n"
11+"PO-Revision-Date: 2015-06-19 09:54+0000\n"
1212"Last-Translator: Richard Somlói <ricsipontaz@gmail.com>\n"
1313"Language-Team: Hungarian <hu@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
1717"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2015-05-05 05:47+0000\n"
19−"X-Generator: Launchpad (build 17474)\n"
18+"X-Launchpad-Export-Date: 2015-06-23 05:33+0000\n"
19+"X-Generator: Launchpad (build 17572)\n"
2020
2121#: plugins/LightDM/Greeter.cpp:112
2222msgid "Password: "
@@ -113,7 +113,7 @@ msgstr "Igen"
113113#: qml/Components/Dialogs.qml:152
114114msgctxt "Title: Power off/Restart dialog"
115115msgid "Power"
116−msgstr "Főkapcsoló"
116+msgstr "Menü"
117117
118118#: qml/Components/Dialogs.qml:153
119119msgid ""
po/ia.po+5−3
@@ -8,15 +8,15 @@ msgstr ""
88"Project-Id-Version: unity8\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2015-04-14 16:13+0200\n"
11−"PO-Revision-Date: 2015-06-04 18:36+0000\n"
11+"PO-Revision-Date: 2015-06-08 18:58+0000\n"
1212"Last-Translator: karm <melo@carmu.com>\n"
1313"Language-Team: Interlingua <ia@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
1717"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2015-06-05 05:58+0000\n"
19−"X-Generator: Launchpad (build 17540)\n"
18+"X-Launchpad-Export-Date: 2015-06-09 05:42+0000\n"
19+"X-Generator: Launchpad (build 17549)\n"
2020
2121#: plugins/LightDM/Greeter.cpp:112
2222msgid "Password: "
@@ -518,6 +518,8 @@ msgid ""
518518"By selecting this option you agree to the Nokia HERE <a href='#'>terms and "
519519"conditions</a>."
520520msgstr ""
521+"Per le selection de iste option tu esser de accordo con le Nokia HERE <a "
522+"href='#'>terminos e conditiones</a>."
521523
522524#: qml/Wizard/Pages/50-location.qml:112
523525msgid "Not at all"
po/it.po+9−9
@@ -8,15 +8,15 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2015-04-14 16:13+0200\n"
11−"PO-Revision-Date: 2015-03-21 12:42+0000\n"
11+"PO-Revision-Date: 2015-06-17 09:56+0000\n"
1212"Last-Translator: Claudio Arseni <claudio.arseni@gmail.com>\n"
1313"Language-Team: Italian <it@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
1717"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2015-04-23 06:48+0000\n"
19−"X-Generator: Launchpad (build 17430)\n"
18+"X-Launchpad-Export-Date: 2015-06-23 05:33+0000\n"
19+"X-Generator: Launchpad (build 17572)\n"
2020
2121#: plugins/LightDM/Greeter.cpp:112
2222msgid "Password: "
@@ -375,7 +375,7 @@ msgstr "Toccare qui per continuare."
375375
376376#: qml/Tutorial/TutorialContent.qml:81
377377msgid "Click here to finish."
378−msgstr ""
378+msgstr "Fare clic qui per terminare."
379379
380380#: qml/Tutorial/TutorialLeftFinish.qml:24
381381msgid "These are the shortcuts to favorite apps"
@@ -640,11 +640,6 @@ msgstr "%1 〉"
640640#~ msgstr ""
641641#~ "Provare a far scorrere dal bordo in alto per accedere agli indicatori"
642642
643−#~ msgid "Swipe up again to close the settings screen"
644−#~ msgstr ""
645−#~ "Far scorrere verso l'alto nuovamente per chiudere la schermata delle "
646−#~ "impostazioni"
647−
648643#~ msgid "Swipe from the left to reveal the launcher for quick access to apps"
649644#~ msgstr ""
650645#~ "Far scorrere il bordo sinistro per mostrare il Launcher e avere un rapido "
@@ -698,3 +693,8 @@ msgstr "%1 〉"
698693
699694#~ msgid "Manage Scopes"
700695#~ msgstr "Gestisci Scope"
696+
697+#~ msgid "Swipe up again to close the settings screen"
698+#~ msgstr ""
699+#~ "Far scorrere nuovamente verso l'alto per chiudere la schermata delle "
700+#~ "impostazioni"
qml/CMakeLists.txt+1
@@ -13,6 +13,7 @@ set(QML_DIRS
1313 Notifications
1414 Panel
1515 Stages
16+ Rotation
1617 Tutorial
1718 Wizard
1819 )
qml/Components/PinLockscreen.qml+1−5
@@ -52,7 +52,7 @@ Item {
5252 verticalCenter: parent.verticalCenter;
5353 verticalCenterOffset: Math.max(-units.gu(10), -(root.height - height) / 2) + units.gu(4)
5454 }
55− spacing: units.gu(2)
55+ spacing: units.gu(4)
5656
5757 Column {
5858 id: shakeContainer
@@ -154,10 +154,6 @@ Item {
154154 }
155155 }
156156
157− ThinDivider {
158− anchors { left: parent.left; right: parent.right; margins: units.gu(2) }
159− }
160−
161157 Grid {
162158 id: numbersGrid
163159 objectName: "numbersGrid"
qml/Components/ResponsiveVerticalJournal.qml+7−2
@@ -39,6 +39,8 @@ import Dash 0.1
3939 +-----+
4040*/
4141Item {
42+ id: root
43+
4244 property real minimumColumnSpacing: units.gu(1)
4345
4446 property alias columnWidth: verticalJournalView.columnWidth
@@ -46,8 +48,8 @@ Item {
4648 property alias model: verticalJournalView.model
4749 property alias delegate: verticalJournalView.delegate
4850 property alias cacheBuffer: verticalJournalView.cacheBuffer
49− property alias displayMarginBeginning: verticalJournalView.displayMarginBeginning
50− property alias displayMarginEnd: verticalJournalView.displayMarginEnd
51+ property real displayMarginBeginning: 0
52+ property real displayMarginEnd: 0
5153
5254 implicitHeight: verticalJournalView.implicitHeight + rowSpacing
5355 clip: height < implicitHeight
@@ -63,6 +65,9 @@ Item {
6365 bottomMargin: rowSpacing / 2
6466 }
6567
68+ displayMarginBeginning: -Math.max(-root.displayMarginBeginning - anchors.topMargin, 0)
69+ displayMarginEnd:-Math.max(-root.displayMarginEnd - anchors.topMargin, 0)
70+
6671 function px2gu(pixels) {
6772 return Math.floor(pixels / units.gu(1))
6873 }
qml/Components/WindowScreenshot.qmlnew file+35
@@ -0,0 +1,35 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.2
18+
19+Item {
20+ id: root
21+
22+ function take() {
23+ var timeNow = new Date().getTime();
24+ image.source = "image://window/" + timeNow;
25+ }
26+
27+ // Unload the image to free up memory
28+ function discard() {
29+ image.source = "";
30+ }
31+
32+ Image {
33+ id: image
34+ }
35+}
qml/Components/ZoomableImage.qml+1
@@ -30,6 +30,7 @@ Item {
3030 property var zoomable: false
3131 property alias imageStatus: imageRenderer.status
3232 property alias asynchronous: imageRenderer.asynchronous
33+ readonly property alias status: imageRenderer.status
3334
3435 Flickable {
3536 id: flickable
qml/Dash/CardCarousel.qml−1
@@ -61,7 +61,6 @@ DashRenderer {
6161 item.fixedHeaderHeight = Qt.binding(function() { return carousel.headerHeight; });
6262 item.height = Qt.binding(function() { return cardTool.cardHeight; });
6363 item.cardData = Qt.binding(function() { return model; });
64− item.template = Qt.binding(function() { return cardTool.template; });
6564 item.components = Qt.binding(function() { return cardTool.components; });
6665 item.fontScale = Qt.binding(function() { return carousel.fontScale; });
6766 item.showHeader = Qt.binding(function() { return loader.explicitlyScaled; });
qml/Dash/CardGrid.qml−1
@@ -66,7 +66,6 @@ DashRenderer {
6666 item.fixedHeaderHeight = Qt.binding(function() { return cardTool.headerHeight; });
6767 item.fixedArtShapeSize = Qt.binding(function() { return cardTool.artShapeSize; });
6868 item.cardData = Qt.binding(function() { return model; });
69− item.template = Qt.binding(function() { return cardTool.template; });
7069 item.components = Qt.binding(function() { return cardTool.components; });
7170 item.titleAlignment = Qt.binding(function() { return cardTool.titleAlignment; });
7271 item.scopeStyle = root.scopeStyle;
qml/Dash/CardHorizontalList.qml−1
@@ -53,7 +53,6 @@ DashRenderer {
5353 item.fixedArtShapeSize = Qt.binding(function() { return cardTool.artShapeSize; });
5454 item.fixedHeaderHeight = Qt.binding(function() { return cardTool.headerHeight; });
5555 item.cardData = Qt.binding(function() { return model; });
56− item.template = Qt.binding(function() { return cardTool.template; });
5756 item.components = Qt.binding(function() { return cardTool.components; });
5857 item.titleAlignment = Qt.binding(function() { return cardTool.titleAlignment; });
5958 item.scopeStyle = root.scopeStyle;
qml/Dash/CardTool.qml+16−15
@@ -198,8 +198,8 @@ Item {
198198
199199 Loader {
200200 id: cardLoader
201− property var fields: ["art", "mascot", "title", "subtitle", "summary", "attributes"]
202− property var maxData: {
201+ readonly property var fields: ["art", "mascot", "title", "subtitle", "summary", "attributes"]
202+ readonly property var maxData: {
203203 "art": Qt.resolvedUrl("graphics/pixel.png"),
204204 "mascot": Qt.resolvedUrl("graphics/pixel.png"),
205205 "title": "—\n—",
@@ -211,26 +211,27 @@ Item {
211211 onLoaded: {
212212 item.objectName = "cardToolCard";
213213 item.asynchronous = false;
214− item.template = Qt.binding(function() { return cardTool.template; });
215214 item.components = Qt.binding(function() { return cardTool.components; });
216215 item.width = Qt.binding(function() { return cardTool.cardWidth || item.implicitWidth; });
217216 item.height = Qt.binding(function() { return cardTool.cardHeight || item.implicitHeight; });
218217 }
219218 Connections {
220− target: cardLoader.item
221− onComponentsChanged: {
222− var data = {};
223− for (var k in cardLoader.fields) {
224− var component = cardLoader.item.components[cardLoader.fields[k]];
225− var key = cardLoader.fields[k];
226− if ((typeof component === "string" && component.length > 0) ||
227− (typeof component === "object" && component !== null
228− && typeof component["field"] === "string" && component["field"].length > 0)) {
229− data[key] = cardLoader.maxData[key];
230− }
219+ target: cardTool
220+ onTemplateChanged: cardLoader.updateCardData();
221+ onComponentsChanged: cardLoader.updateCardData();
222+ }
223+ function updateCardData() {
224+ var data = {};
225+ for (var k in fields) {
226+ var component = cardTool.components[fields[k]];
227+ var key = fields[k];
228+ if ((typeof component === "string" && component.length > 0) ||
229+ (typeof component === "object" && component !== null
230+ && typeof component["field"] === "string" && component["field"].length > 0)) {
231+ data[key] = maxData[key];
231232 }
232− cardLoader.item.cardData = data;
233233 }
234+ item.cardData = data;
234235 }
235236 }
236237}
qml/Dash/CardVerticalJournal.qml−1
@@ -72,7 +72,6 @@ DashRenderer {
7272 item.fixedArtShapeSize = Qt.binding(function() { return cardTool.artShapeSize; });
7373 item.fixedHeaderHeight = Qt.binding(function() { return cardTool.headerHeight; });
7474 item.cardData = Qt.binding(function() { return model; });
75− item.template = Qt.binding(function() { return cardTool.template; });
7675 item.components = Qt.binding(function() { return cardTool.components; });
7776 item.titleAlignment = Qt.binding(function() { return cardTool.titleAlignment; });
7877 item.scopeStyle = root.scopeStyle;
qml/Dash/DashApplication.qml+3−3
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014,2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -19,8 +19,8 @@ import Ubuntu.Components 1.0
1919import Ubuntu.Thumbnailer 0.1 // Register support for image://thumbnailer/ and image://albumart/
2020
2121MainView {
22− width: applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
23− height: applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(68)
22+ implicitWidth: units.gu(40)
23+ implicitHeight: units.gu(68)
2424
2525 useDeprecatedToolbar: false
2626
qml/Dash/GenericScopeView.qml+1−1
@@ -425,7 +425,7 @@ FocusScope {
425425 // to the next, we set the visible range to the viewport so
426426 // items are not culled (invisible) but still use no cacheBuffer
427427 // (it will be set once the scope is the current one)
428− var displayMarginBeginning = baseItem.y;
428+ var displayMarginBeginning = baseItem.y + rendererLoader.anchors.topMargin;
429429 displayMarginBeginning = -Math.max(-displayMarginBeginning, 0);
430430 displayMarginBeginning = -Math.min(-displayMarginBeginning, baseItem.height);
431431 displayMarginBeginning = Math.round(displayMarginBeginning);
qml/Dash/Previews/PreviewHeader.qml+6
@@ -22,6 +22,7 @@ import "../"
2222/*! This preview widget shows a header
2323 * The title comes in widgetData["title"]
2424 * The mascot comes in widgetData["mascot"]
25+ * The mascot fall back image comes in widgetData["fallback"]
2526 * The subtitle comes in widgetData["subtitle"]
2627 * The attributes comes in widgetData["attributes"]
2728 */
@@ -35,11 +36,15 @@ PreviewWidget {
3536 id: headerRoot
3637 objectName: "innerPreviewHeader"
3738 readonly property url mascot: root.widgetData["mascot"] || ""
39+ readonly property url fallback: root.widgetData["fallback"] || ""
3840 readonly property string title: root.widgetData["title"] || ""
3941 readonly property string subtitle: root.widgetData["subtitle"] || ""
4042 readonly property var attributes: root.widgetData["attributes"] || null
4143 readonly property color fontColor: root.scopeStyle ? root.scopeStyle.foreground : Theme.palette.normal.baseText
4244
45+ // Rewire the source since we may have unwired it on onStatusChanged
46+ onMascotChanged: if (mascotShapeLoader.item) mascotShapeLoader.item.image.source = mascot;
47+
4348 implicitHeight: row.height + row.margins * 2
4449 width: parent.width
4550
@@ -82,6 +87,7 @@ PreviewWidget {
8287 fillMode: Image.PreserveAspectCrop
8388 horizontalAlignment: Image.AlignHCenter
8489 verticalAlignment: Image.AlignVCenter
90+ onStatusChanged: if (status === Image.Error) source = headerRoot.fallback;
8591 }
8692 }
8793 }
qml/Dash/Previews/PreviewImageGallery.qml+14
@@ -20,6 +20,7 @@ import "../../Components"
2020
2121/*! This preview widget shows a horizontal list of images.
2222 * The URIs for the images should be an array in widgetData["sources"].
23+ * Images fall back to widgetData["fallback"] if loading fails
2324 */
2425
2526PreviewWidget {
@@ -72,6 +73,14 @@ PreviewWidget {
7273 overlay.show();
7374 }
7475 }
76+
77+ Connections {
78+ target: sourceImage
79+ // If modelData would change after failing to load it would not be
80+ // reloaded since the source binding is destroyed by the source = fallback
81+ // But at the moment the model never changes
82+ onStatusChanged: if (sourceImage.status === Image.Error) sourceImage.source = widgetData["fallback"];
83+ }
7584 }
7685 }
7786
@@ -122,6 +131,11 @@ PreviewWidget {
122131 source: modelData ? modelData : ""
123132 fillMode: Image.PreserveAspectFit
124133 sourceSize { width: screenshot.width; height: screenshot.height }
134+
135+ // If modelData would change after failing to load it would not be
136+ // reloaded since the source binding is destroyed by the source = fallback
137+ // But at the moment the model never changes
138+ onStatusChanged: if (status === Image.Error) source = widgetData["fallback"];
125139 }
126140
127141 MouseArea {
qml/Dash/Previews/PreviewZoomableImage.qml+13
@@ -21,6 +21,7 @@ import "../../Components"
2121/*! \brief Preview widget for image.
2222
2323 This widget shows image contained in widgetData["source"],
24+ and falls back to widgetData["fallback"] if loading fails
2425 can be zoomable accordingly with widgetData["zoomable"].
2526 */
2627
@@ -53,6 +54,14 @@ PreviewWidget {
5354 overlay.show();
5455 }
5556 }
57+
58+ Connections {
59+ target: lazyImage.sourceImage
60+ // If modelData would change after failing to load it would not be
61+ // reloaded since the source binding is destroyed by the source = fallback
62+ // But at the moment the model never changes
63+ onStatusChanged: if (lazyImage.sourceImage.status === Image.Error) lazyImage.sourceImage.source = widgetData["fallback"];
64+ }
5665 }
5766
5867 PreviewOverlay {
@@ -67,6 +76,10 @@ PreviewWidget {
6776 anchors.fill: parent
6877 source: widgetData["source"]
6978 zoomable: widgetData["zoomable"] ? widgetData["zoomable"] : false
79+ // If modelData would change after failing to load it would not be
80+ // reloaded since the source binding is destroyed by the source = fallback
81+ // But at the moment the model never changes
82+ onStatusChanged: if (status === Image.Error) source = widgetData["fallback"];
7083 }
7184 }
7285}
qml/Dash/graphics/phone/screenshots/gmail-webapp.svgnew file+343
@@ -0,0 +1,343 @@
1+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2+<!-- Created with Inkscape (http://www.inkscape.org/) -->
3+
4+<svg
5+ xmlns:dc="http://purl.org/dc/elements/1.1/"
6+ xmlns:cc="http://creativecommons.org/ns#"
7+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
8+ xmlns:svg="http://www.w3.org/2000/svg"
9+ xmlns="http://www.w3.org/2000/svg"
10+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
11+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
12+ width="768"
13+ height="1280"
14+ id="svg2"
15+ version="1.1"
16+ inkscape:version="0.48.5 r10040"
17+ sodipodi:docname="gmail-webapp.svg">
18+ <defs
19+ id="defs4" />
20+ <sodipodi:namedview
21+ id="base"
22+ pagecolor="#ffffff"
23+ bordercolor="#666666"
24+ borderopacity="1.0"
25+ inkscape:pageopacity="0.0"
26+ inkscape:pageshadow="2"
27+ inkscape:zoom="0.49497475"
28+ inkscape:cx="117.33439"
29+ inkscape:cy="668.80479"
30+ inkscape:document-units="px"
31+ inkscape:current-layer="layer1"
32+ showgrid="false"
33+ inkscape:window-width="1920"
34+ inkscape:window-height="1056"
35+ inkscape:window-x="0"
36+ inkscape:window-y="24"
37+ inkscape:window-maximized="1" />
38+ <metadata
39+ id="metadata7">
40+ <rdf:RDF>
41+ <cc:Work
42+ rdf:about="">
43+ <dc:format>image/svg+xml</dc:format>
44+ <dc:type
45+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
46+ <dc:title></dc:title>
47+ </cc:Work>
48+ </rdf:RDF>
49+ </metadata>
50+ <g
51+ inkscape:label="Layer 1"
52+ inkscape:groupmode="layer"
53+ id="layer1"
54+ transform="translate(0,227.63782)">
55+ <rect
56+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.69999999000000002;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
57+ id="rect2985"
58+ width="769.73627"
59+ height="1276.8328"
60+ x="-2.0203052"
61+ y="3.1671834"
62+ transform="translate(0,-227.63782)" />
63+ <rect
64+ style="fill:#e6e6e6;fill-opacity:1;stroke:none;stroke-width:4;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
65+ id="rect3797"
66+ width="773.77686"
67+ height="129.29953"
68+ x="-6.0609155"
69+ y="-0.87342685"
70+ transform="translate(0,-227.63782)" />
71+ <text
72+ xml:space="preserve"
73+ style="font-size:72px;font-style:normal;font-weight:500;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Ubuntu;-inkscape-font-specification:Ubuntu Medium;font-stretch:normal;font-variant:normal"
74+ x="20.203053"
75+ y="-139.61781"
76+ id="text3755"
77+ sodipodi:linespacing="125%"><tspan
78+ sodipodi:role="line"
79+ id="tspan3757"
80+ x="20.203053"
81+ y="-139.61781">GMail</tspan></text>
82+ <text
83+ xml:space="preserve"
84+ style="font-size:72px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Ubuntu;-inkscape-font-specification:Ubuntu"
85+ x="14.142137"
86+ y="-28.501045"
87+ id="text3759"
88+ sodipodi:linespacing="125%"><tspan
89+ sodipodi:role="line"
90+ id="tspan3761"
91+ x="14.142137"
92+ y="-28.501045">Inbox</tspan></text>
93+ <path
94+ sodipodi:type="arc"
95+ style="fill:#8effff;fill-opacity:1;stroke:#000000;stroke-width:0.69999999;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
96+ id="path3765"
97+ sodipodi:cx="107.07617"
98+ sodipodi:cy="337.52768"
99+ sodipodi:rx="64.649765"
100+ sodipodi:ry="61.619305"
101+ d="m 171.72594,337.52768 a 64.649765,61.619305 0 1 1 -129.299533,0 64.649765,61.619305 0 1 1 129.299533,0 z"
102+ transform="translate(-28.284271,-251.88148)" />
103+ <path
104+ style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
105+ d="m 14.142136,237.52257 729.330134,0"
106+ id="path3769"
107+ inkscape:connector-curvature="0"
108+ transform="translate(0,-227.63782)" />
109+ <text
110+ xml:space="preserve"
111+ style="font-size:64px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Ubuntu;-inkscape-font-specification:Ubuntu"
112+ x="167.68533"
113+ y="78.575127"
114+ id="text3771"
115+ sodipodi:linespacing="125%"><tspan
116+ sodipodi:role="line"
117+ id="tspan3773"
118+ x="167.68533"
119+ y="78.575127">Lorem ipsum</tspan></text>
120+ <text
121+ xml:space="preserve"
122+ style="font-size:64px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Ubuntu;-inkscape-font-specification:Ubuntu"
123+ x="167.68532"
124+ y="131.10306"
125+ id="text3775"
126+ sodipodi:linespacing="125%"><tspan
127+ sodipodi:role="line"
128+ id="tspan3777"
129+ x="167.68532"
130+ y="131.10306"
131+ style="font-size:48px;fill:#b3b3b3">bla bla bla bla bla bla bla ...</tspan></text>
132+ <path
133+ style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
134+ d="m 13.13199,165.44825 729.33013,0"
135+ id="path3769-2"
136+ inkscape:connector-curvature="0" />
137+ <path
138+ sodipodi:type="arc"
139+ style="fill:#8eff58;fill-opacity:1;stroke:#000000;stroke-width:0.69999999000000002;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
140+ id="path3765-3"
141+ sodipodi:cx="107.07617"
142+ sodipodi:cy="337.52768"
143+ sodipodi:rx="64.649765"
144+ sodipodi:ry="61.619305"
145+ d="m 171.72594,337.52768 a 64.649765,61.619305 0 1 1 -129.299533,0 64.649765,61.619305 0 1 1 129.299533,0 z"
146+ transform="translate(-26.263969,-93.102383)" />
147+ <text
148+ xml:space="preserve"
149+ style="font-size:64px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Ubuntu;-inkscape-font-specification:Ubuntu"
150+ x="169.70564"
151+ y="237.35422"
152+ id="text3771-9"
153+ sodipodi:linespacing="125%"><tspan
154+ sodipodi:role="line"
155+ id="tspan3773-7"
156+ x="169.70564"
157+ y="237.35422">Lorem ipsum</tspan></text>
158+ <text
159+ xml:space="preserve"
160+ style="font-size:64px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Ubuntu;-inkscape-font-specification:Ubuntu"
161+ x="169.70561"
162+ y="289.88217"
163+ id="text3775-1"
164+ sodipodi:linespacing="125%"><tspan
165+ sodipodi:role="line"
166+ id="tspan3777-1"
167+ x="169.70561"
168+ y="289.88217"
169+ style="font-size:48px;fill:#b3b3b3">bla bla bla bla bla bla bla ...</tspan></text>
170+ <path
171+ style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
172+ d="m 15.152292,324.22735 729.330128,0"
173+ id="path3769-2-6"
174+ inkscape:connector-curvature="0" />
175+ <path
176+ sodipodi:type="arc"
177+ style="fill:#8effff;fill-opacity:1;stroke:#000000;stroke-width:0.69999999;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
178+ id="path3765-7"
179+ sodipodi:cx="107.07617"
180+ sodipodi:cy="337.52768"
181+ sodipodi:rx="64.649765"
182+ sodipodi:ry="61.619305"
183+ d="m 171.72594,337.52768 a 64.649765,61.619305 0 1 1 -129.299533,0 64.649765,61.619305 0 1 1 129.299533,0 z"
184+ transform="translate(-28.284274,64.481414)" />
185+ <text
186+ xml:space="preserve"
187+ style="font-size:64px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Ubuntu;-inkscape-font-specification:Ubuntu"
188+ x="167.68533"
189+ y="394.93799"
190+ id="text3771-92"
191+ sodipodi:linespacing="125%"><tspan
192+ sodipodi:role="line"
193+ id="tspan3773-0"
194+ x="167.68533"
195+ y="394.93799">Lorem ipsum</tspan></text>
196+ <text
197+ xml:space="preserve"
198+ style="font-size:64px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Ubuntu;-inkscape-font-specification:Ubuntu"
199+ x="167.68532"
200+ y="447.46594"
201+ id="text3775-9"
202+ sodipodi:linespacing="125%"><tspan
203+ sodipodi:role="line"
204+ id="tspan3777-8"
205+ x="167.68532"
206+ y="447.46594"
207+ style="font-size:48px;fill:#b3b3b3">bla bla bla bla bla bla bla ...</tspan></text>
208+ <path
209+ style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
210+ d="m 13.131986,481.81114 729.330124,0"
211+ id="path3769-2-3"
212+ inkscape:connector-curvature="0" />
213+ <path
214+ sodipodi:type="arc"
215+ style="fill:#8ea2ff;fill-opacity:1;stroke:#000000;stroke-width:0.69999999000000002;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
216+ id="path3765-2"
217+ sodipodi:cx="107.07617"
218+ sodipodi:cy="337.52768"
219+ sodipodi:rx="64.649765"
220+ sodipodi:ry="61.619305"
221+ d="m 171.72594,337.52768 a 64.649765,61.619305 0 1 1 -129.299533,0 64.649765,61.619305 0 1 1 129.299533,0 z"
222+ transform="translate(-26.263969,226.10582)" />
223+ <text
224+ xml:space="preserve"
225+ style="font-size:64px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Ubuntu;-inkscape-font-specification:Ubuntu"
226+ x="169.70564"
227+ y="556.56238"
228+ id="text3771-1"
229+ sodipodi:linespacing="125%"><tspan
230+ sodipodi:role="line"
231+ id="tspan3773-1"
232+ x="169.70564"
233+ y="556.56238">Lorem ipsum</tspan></text>
234+ <text
235+ xml:space="preserve"
236+ style="font-size:64px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Ubuntu;-inkscape-font-specification:Ubuntu"
237+ x="169.70563"
238+ y="609.09033"
239+ id="text3775-8"
240+ sodipodi:linespacing="125%"><tspan
241+ sodipodi:role="line"
242+ id="tspan3777-85"
243+ x="169.70563"
244+ y="609.09033"
245+ style="font-size:48px;fill:#b3b3b3">bla bla bla bla bla bla bla ...</tspan></text>
246+ <path
247+ style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
248+ d="m 15.152292,643.43555 729.330128,0"
249+ id="path3769-2-1"
250+ inkscape:connector-curvature="0" />
251+ <path
252+ sodipodi:type="arc"
253+ style="fill:#8effff;fill-opacity:1;stroke:#000000;stroke-width:0.69999999;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
254+ id="path3765-8"
255+ sodipodi:cx="107.07617"
256+ sodipodi:cy="337.52768"
257+ sodipodi:rx="64.649765"
258+ sodipodi:ry="61.619305"
259+ d="m 171.72594,337.52768 a 64.649765,61.619305 0 1 1 -129.299533,0 64.649765,61.619305 0 1 1 129.299533,0 z"
260+ transform="translate(-24.243664,391.77084)" />
261+ <text
262+ xml:space="preserve"
263+ style="font-size:64px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Ubuntu;-inkscape-font-specification:Ubuntu"
264+ x="171.72594"
265+ y="722.22742"
266+ id="text3771-7"
267+ sodipodi:linespacing="125%"><tspan
268+ sodipodi:role="line"
269+ id="tspan3773-6"
270+ x="171.72594"
271+ y="722.22742">Lorem ipsum</tspan></text>
272+ <text
273+ xml:space="preserve"
274+ style="font-size:64px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Ubuntu;-inkscape-font-specification:Ubuntu"
275+ x="171.72592"
276+ y="774.75537"
277+ id="text3775-5"
278+ sodipodi:linespacing="125%"><tspan
279+ sodipodi:role="line"
280+ id="tspan3777-2"
281+ x="171.72592"
282+ y="774.75537"
283+ style="font-size:48px;fill:#b3b3b3">bla bla bla bla bla bla bla ...</tspan></text>
284+ <path
285+ style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
286+ d="m 17.172597,809.10057 729.330133,0"
287+ id="path3769-2-13"
288+ inkscape:connector-curvature="0" />
289+ <path
290+ sodipodi:type="arc"
291+ style="fill:#e44738;fill-opacity:1;stroke:#000000;stroke-width:0.69999999000000002;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
292+ id="path3765-9"
293+ sodipodi:cx="107.07617"
294+ sodipodi:cy="337.52768"
295+ sodipodi:rx="64.649765"
296+ sodipodi:ry="61.619305"
297+ d="m 171.72594,337.52768 a 64.649765,61.619305 0 1 1 -129.299533,0 64.649765,61.619305 0 1 1 129.299533,0 z"
298+ transform="translate(-24.243664,557.43585)" />
299+ <text
300+ xml:space="preserve"
301+ style="font-size:64px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Ubuntu;-inkscape-font-specification:Ubuntu"
302+ x="171.72594"
303+ y="887.89246"
304+ id="text3771-2"
305+ sodipodi:linespacing="125%"><tspan
306+ sodipodi:role="line"
307+ id="tspan3773-8"
308+ x="171.72594"
309+ y="887.89246">Lorem ipsum</tspan></text>
310+ <text
311+ xml:space="preserve"
312+ style="font-size:64px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Ubuntu;-inkscape-font-specification:Ubuntu"
313+ x="171.72592"
314+ y="940.42041"
315+ id="text3775-3"
316+ sodipodi:linespacing="125%"><tspan
317+ sodipodi:role="line"
318+ id="tspan3777-7"
319+ x="171.72592"
320+ y="940.42041"
321+ style="font-size:48px;fill:#b3b3b3">bla bla bla bla bla bla bla ...</tspan></text>
322+ <path
323+ style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
324+ d="m 17.172597,974.76558 729.330123,0"
325+ id="path3769-2-9"
326+ inkscape:connector-curvature="0" />
327+ <path
328+ sodipodi:type="arc"
329+ style="fill:none;stroke:#000000;stroke-width:8;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
330+ id="path3929"
331+ sodipodi:cx="590.93921"
332+ sodipodi:cy="-119.06127"
333+ sodipodi:rx="33.335033"
334+ sodipodi:ry="33.335033"
335+ d="m 624.27424,-119.06127 a 33.335033,33.335033 0 1 1 -66.67006,0 33.335033,33.335033 0 1 1 66.67006,0 z"
336+ transform="matrix(0.85096826,0,0,0.85096826,175.43863,-68.81729)" />
337+ <path
338+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:3.40387297;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
339+ d="m 692.92249,-145.20602 20.63059,24.06901 12.03451,-13.75372 -20.63059,-17.19215"
340+ id="path3931"
341+ inkscape:connector-curvature="0" />
342+ </g>
343+</svg>
qml/Dash/graphics/phone/screenshots/settings@12.pngdeleted

Binary file not shown.

qml/Dash/graphics/phone/screenshots/ubuntu-weather-app.svgnew file+201
@@ -0,0 +1,201 @@
1+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2+<!-- Created with Inkscape (http://www.inkscape.org/) -->
3+
4+<svg
5+ xmlns:dc="http://purl.org/dc/elements/1.1/"
6+ xmlns:cc="http://creativecommons.org/ns#"
7+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
8+ xmlns:svg="http://www.w3.org/2000/svg"
9+ xmlns="http://www.w3.org/2000/svg"
10+ xmlns:xlink="http://www.w3.org/1999/xlink"
11+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
12+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
13+ width="829"
14+ height="480"
15+ id="svg2740"
16+ sodipodi:version="0.32"
17+ inkscape:version="0.48.5 r10040"
18+ version="1.0"
19+ sodipodi:docname="ubuntu-weather-app.svg"
20+ inkscape:output_extension="org.inkscape.output.svg.inkscape">
21+ <defs
22+ id="defs2742">
23+ <linearGradient
24+ id="linearGradient3824">
25+ <stop
26+ style="stop-color:#e6e6e6;stop-opacity:1;"
27+ offset="0"
28+ id="stop3826" />
29+ <stop
30+ style="stop-color:#23abff;stop-opacity:1;"
31+ offset="1"
32+ id="stop3828" />
33+ </linearGradient>
34+ <linearGradient
35+ inkscape:collect="always"
36+ xlink:href="#linearGradient3824"
37+ id="linearGradient3830"
38+ x1="348.55862"
39+ y1="343.23914"
40+ x2="348.55862"
41+ y2="-17.422215"
42+ gradientUnits="userSpaceOnUse" />
43+ </defs>
44+ <sodipodi:namedview
45+ id="base"
46+ pagecolor="#ffffff"
47+ bordercolor="#666666"
48+ borderopacity="1.0"
49+ gridtolerance="10000"
50+ guidetolerance="10"
51+ objecttolerance="10"
52+ inkscape:pageopacity="0.0"
53+ inkscape:pageshadow="2"
54+ inkscape:zoom="0.82625984"
55+ inkscape:cx="331.28234"
56+ inkscape:cy="125.54212"
57+ inkscape:document-units="px"
58+ inkscape:current-layer="layer1"
59+ showgrid="false"
60+ inkscape:window-width="1145"
61+ inkscape:window-height="847"
62+ inkscape:window-x="268"
63+ inkscape:window-y="63"
64+ inkscape:window-maximized="0" />
65+ <metadata
66+ id="metadata2745">
67+ <rdf:RDF>
68+ <cc:Work
69+ rdf:about="">
70+ <dc:format>image/svg+xml</dc:format>
71+ <dc:type
72+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
73+ </cc:Work>
74+ </rdf:RDF>
75+ </metadata>
76+ <g
77+ inkscape:label="Layer 1"
78+ inkscape:groupmode="layer"
79+ id="layer1"
80+ transform="translate(-321.13452,-104.68346)">
81+ <rect
82+ style="fill:url(#linearGradient3830);fill-opacity:1;stroke:#000000;stroke-width:0.69999999000000002;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
83+ id="rect2990"
84+ width="832.66785"
85+ height="480.47839"
86+ x="-1.210273"
87+ y="0.73188055"
88+ transform="translate(321.13452,104.68346)" />
89+ <path
90+ d="m 310.66382,132.06057 c -1.82703,2.0443 -22.21039,-18.38308 -24.89334,-17.37811 -2.68295,1.00497 -6.73783,33.64192 -9.57527,34.01165 -2.83744,0.36973 -7.71546,-33.08881 -10.48084,-33.88783 -2.76539,-0.79903 -18.7793,19.34152 -21.17964,17.82892 -2.40034,-1.51259 5.69972,-26.09808 4.36486,-28.67057 -1.33487,-2.57249 -31.86305,-3.78032 -32.49182,-6.6501 -0.62876,-2.86978 27.66855,-10.399376 27.90248,-13.241764 0.23394,-2.842387 -18.99828,-18.90215 -17.89698,-21.393798 1.10131,-2.491647 26.85456,8.177399 28.59429,5.99159 1.73972,-2.185809 -3.39182,-29.683489 -0.75169,-30.507098 2.64013,-0.823608 18.50306,18.485327 21.32153,18.392972 2.81847,-0.09235 11.30312,-24.287685 14.18258,-23.722317 2.87947,0.565368 1.44329,25.667631 3.67226,27.34365 2.22897,1.676019 24.27161,-6.28288 25.90675,-4.004945 1.63514,2.277936 -12.63923,24.445025 -11.77955,27.07291 0.85968,2.627885 27.01183,5.1355 26.65457,7.852474 -0.35726,2.716973 -32.69913,3.875594 -34.04684,6.507203 -1.3477,2.631613 12.32368,32.410863 10.49665,34.455163 z"
91+ id="path11949"
92+ inkscape:flatsided="false"
93+ inkscape:randomized="-0.092"
94+ inkscape:rounded="0.1"
95+ sodipodi:arg1="0.79570711"
96+ sodipodi:arg2="1.144773"
97+ sodipodi:cx="275.15002"
98+ sodipodi:cy="88.090233"
99+ sodipodi:r1="57.019234"
100+ sodipodi:r2="32.818508"
101+ sodipodi:sides="9"
102+ sodipodi:type="star"
103+ style="fill:#f5ff12;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.2;marker:none;visibility:visible;display:inline;overflow:visible"
104+ transform="matrix(2.527571,0,0,2.527571,85.214779,60.619097)" />
105+ <path
106+ d="m 194.51692,77.283737 c 0,20.659371 -17.86424,37.407103 -39.90091,37.407103 -22.03666,0 -39.90091,-16.747732 -39.90091,-37.407103 0,-20.659371 17.86425,-37.4071 39.90091,-37.4071 22.03667,0 39.90091,16.747729 39.90091,37.4071 z"
107+ id="path11951"
108+ sodipodi:cx="154.61601"
109+ sodipodi:cy="77.283737"
110+ sodipodi:rx="39.900909"
111+ sodipodi:ry="37.407101"
112+ sodipodi:type="arc"
113+ style="fill:#f5ff12;fill-opacity:1;fill-rule:nonzero;stroke:#f5ff12;stroke-width:10.27388287;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:0.5;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
114+ transform="matrix(2.4568175,0,0,2.1990879,404.91256,120.05076)" />
115+ <path
116+ transform="matrix(3.2663208,0,0,3.4016021,515.92101,-254.59331)"
117+ style="fill:#6798e9;fill-opacity:1;fill-rule:nonzero;stroke:#6798e9;stroke-width:7.30059433;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0.5;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
118+ sodipodi:type="inkscape:offset"
119+ inkscape:radius="0"
120+ inkscape:original="M 110.53125 144.96875 C 100.76599 144.96875 92.384060 150.03292 88.281250 157.34375 C 86.642607 156.42892 84.835190 155.87500 82.906250 155.87500 C 77.073071 155.87500 72.219030 160.56753 70.718750 166.93750 C 68.157973 165.92533 65.180470 165.31250 62.000000 165.31250 C 52.458571 165.31250 44.718750 170.53296 44.718750 176.96875 C 44.718749 182.77816 51.066430 187.55402 59.312500 188.43750 C 58.942922 189.06456 58.656250 189.68551 58.656250 190.34375 C 58.656248 196.50568 75.538140 201.53125 96.343750 201.53125 C 117.14936 201.53125 134.03126 196.50568 134.03125 190.34375 C 134.03125 189.92916 133.77393 189.55916 133.62500 189.15625 C 134.53629 189.38287 135.43860 189.62500 136.43750 189.62500 C 142.00333 189.62501 146.53125 185.84318 146.53125 181.18750 C 146.53125 176.53182 142.00332 172.75000 136.43750 172.75000 C 135.75542 172.75000 135.17544 172.98560 134.53125 173.09375 C 135.09454 171.23218 135.46875 169.33268 135.46875 167.31250 C 135.46875 154.98864 124.31330 144.96875 110.53125 144.96875 z "
121+ id="path11953"
122+ d="m 110.53125,144.96875 c -9.76526,0 -18.14719,5.06417 -22.25,12.375 -1.638643,-0.91483 -3.44606,-1.46875 -5.375,-1.46875 -5.833179,0 -10.68722,4.69253 -12.1875,11.0625 -2.560777,-1.01217 -5.53828,-1.625 -8.71875,-1.625 -9.541429,0 -17.28125,5.22046 -17.28125,11.65625 -10e-7,5.80941 6.34768,10.58527 14.59375,11.46875 -0.369578,0.62706 -0.65625,1.24801 -0.65625,1.90625 -2e-6,6.16193 16.88189,11.1875 37.6875,11.1875 20.80561,0 37.68751,-5.02557 37.6875,-11.1875 0,-0.41459 -0.25732,-0.78459 -0.40625,-1.1875 0.91129,0.22662 1.8136,0.46875 2.8125,0.46875 5.56583,1e-5 10.09375,-3.78182 10.09375,-8.4375 0,-4.65568 -4.52793,-8.4375 -10.09375,-8.4375 -0.68208,0 -1.26206,0.2356 -1.90625,0.34375 0.56329,-1.86157 0.9375,-3.76107 0.9375,-5.78125 0,-12.32386 -11.15545,-22.34375 -24.9375,-22.34375 z" />
123+ <path
124+ inkscape:connector-curvature="0"
125+ style="fill:#386195;fill-opacity:1;fill-rule:nonzero;stroke:#386195;stroke-width:24.07697487;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0.5;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
126+ id="path11963"
127+ d="m 975.70833,305.75079 c -17.87875,0 -33.1897,9.10731 -40.70137,22.18913 -3.00012,-1.63696 -6.34124,-2.6392 -9.87286,-2.6392 -10.67967,0 -19.5639,8.39604 -22.31073,19.7943 -4.6884,-1.81117 -10.09942,-2.93251 -15.92242,-2.93251 -17.46892,0 -31.65122,9.35352 -31.65122,20.86955 0,10.39528 11.61748,18.9466 26.71485,20.52749 -0.67665,1.12204 -1.20995,2.24338 -1.20995,3.42121 0,11.02604 30.92124,19.98985 69.01327,19.98985 38.09195,0 69.0132,-8.96381 69.0132,-19.98985 0,-0.74183 -0.5017,-1.42952 -0.7743,-2.15046 1.6684,0.40556 3.3496,0.83088 5.1784,0.83088 10.1902,0 18.439,-6.7716 18.439,-15.10234 0,-8.33078 -8.2488,-15.05348 -18.439,-15.05348 -1.2488,0 -2.3535,0.39297 -3.5329,0.58647 1.0312,-3.33102 1.7422,-6.7466 1.7422,-10.36143 0,-22.05212 -20.4533,-39.97961 -45.68617,-39.97961 z" />
128+ <path
129+ inkscape:connector-curvature="0"
130+ style="fill:#6798e9;fill-opacity:1;fill-rule:nonzero;stroke:#6798e9;stroke-width:15.64964962;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3.20000005;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
131+ sodipodi:nodetypes="cccc"
132+ id="path11965"
133+ d="m 717.80594,482.73655 c -32.27924,28.4432 5.62151,52.69793 20.03284,13.63938 l 11.08203,-36.22961 -31.11487,22.59023 z" />
134+ <path
135+ inkscape:connector-curvature="0"
136+ style="fill:#6798e9;fill-opacity:1;fill-rule:nonzero;stroke:#6798e9;stroke-width:15.64964962;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3.20000005;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
137+ sodipodi:nodetypes="cccc"
138+ id="path11967"
139+ d="m 799.47,485.76116 c -32.27925,28.4432 5.62151,52.69792 20.03284,13.63937 l 11.08202,-36.2296 -31.11486,22.59023 z" />
140+ <text
141+ xml:space="preserve"
142+ style="font-size:32px;font-style:normal;font-weight:bold;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans Bold"
143+ x="68.985565"
144+ y="92.712631"
145+ id="text2992"
146+ sodipodi:linespacing="125%"
147+ transform="translate(321.13452,104.68346)"><tspan
148+ sodipodi:role="line"
149+ id="tspan2994"
150+ x="68.985565"
151+ y="92.712631" /></text>
152+ <text
153+ xml:space="preserve"
154+ style="font-size:10px;font-style:normal;font-weight:bold;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans Bold"
155+ x="-202.1156"
156+ y="155.64684"
157+ id="text2996"
158+ sodipodi:linespacing="125%"
159+ transform="translate(321.13452,104.68346)"><tspan
160+ sodipodi:role="line"
161+ id="tspan2998"
162+ x="-202.1156"
163+ y="155.64684" /></text>
164+ <text
165+ xml:space="preserve"
166+ style="font-size:24px;font-style:normal;font-weight:bold;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans Bold"
167+ x="342.91943"
168+ y="155.03653"
169+ id="text3000"
170+ sodipodi:linespacing="125%"><tspan
171+ sodipodi:role="line"
172+ id="tspan3002"
173+ x="342.91943"
174+ y="155.03653"
175+ style="font-size:32px;font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Ubuntu;-inkscape-font-specification:Ubuntu Medium">Wheather App</tspan></text>
176+ <text
177+ xml:space="preserve"
178+ style="font-size:24px;font-style:normal;font-weight:bold;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#ff0000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans Bold"
179+ x="460.31592"
180+ y="358.3624"
181+ id="text3004"
182+ sodipodi:linespacing="125%"><tspan
183+ sodipodi:role="line"
184+ id="tspan3006"
185+ x="460.31592"
186+ y="358.3624"
187+ style="font-size:48px;fill:#ff0000;-inkscape-font-specification:Ubuntu;font-family:Ubuntu;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal">22C</tspan></text>
188+ <text
189+ xml:space="preserve"
190+ style="font-size:24px;font-style:normal;font-weight:bold;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#0000ff;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans Bold"
191+ x="460.19751"
192+ y="412.14325"
193+ id="text3004-0"
194+ sodipodi:linespacing="125%"><tspan
195+ sodipodi:role="line"
196+ id="tspan3006-4"
197+ x="460.19751"
198+ y="412.14325"
199+ style="font-size:48px;fill:#0000ff;-inkscape-font-specification:Ubuntu Medium;font-family:Ubuntu;font-weight:500;font-style:normal;font-stretch:normal;font-variant:normal">14C</tspan></text>
200+ </g>
201+</svg>
qml/DeviceConfiguration.qmlnew file+81
@@ -0,0 +1,81 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+
19+StateGroup {
20+ id: root
21+
22+ readonly property int useNativeOrientation: -1
23+
24+ property int primaryOrientation: useNativeOrientation
25+
26+ property int supportedOrientations: Qt.PortraitOrientation
27+ | Qt.InvertedPortraitOrientation
28+ | Qt.LandscapeOrientation
29+ | Qt.InvertedLandscapeOrientation
30+
31+ // Supported values so far:
32+ // "phone", "tablet" or "desktop"
33+ property string category: "phone"
34+
35+
36+ property alias name: root.state
37+
38+ states: [
39+ State {
40+ name: "mako"
41+ PropertyChanges {
42+ target: root
43+ supportedOrientations: Qt.PortraitOrientation
44+ | Qt.LandscapeOrientation
45+ | Qt.InvertedLandscapeOrientation
46+ }
47+ },
48+ State {
49+ name: "krillin"
50+ PropertyChanges {
51+ target: root
52+ supportedOrientations: Qt.PortraitOrientation
53+ | Qt.LandscapeOrientation
54+ | Qt.InvertedLandscapeOrientation
55+ }
56+ },
57+ State {
58+ name: "manta"
59+ PropertyChanges {
60+ target: root
61+ category: "tablet"
62+ }
63+ },
64+ State {
65+ name: "flo"
66+ PropertyChanges {
67+ target: root
68+ primaryOrientation: Qt.InvertedLandscapeOrientation
69+ category: "tablet"
70+ }
71+ },
72+ State {
73+ name: "desktop"
74+ PropertyChanges {
75+ target: root
76+ category: "desktop"
77+ }
78+ }
79+ ]
80+
81+}
qml/Greeter/Greeter.qml+2
@@ -52,6 +52,8 @@ Showable {
5252 property int failedLoginsDelayAttempts: 7 // number of failed logins
5353 property real failedLoginsDelayMinutes: 5 // minutes of forced waiting
5454
55+ readonly property bool animating: loader.item ? loader.item.animating : false
56+
5557 signal tease()
5658 signal sessionStarted()
5759 signal emergencyCall()
qml/Greeter/NarrowView.qml+1
@@ -33,6 +33,7 @@ FocusScope {
3333 property alias infographicModel: coverPage.infographicModel
3434 readonly property bool fullyShown: coverPage.showProgress === 1 || lockscreen.shown
3535 readonly property bool required: coverPage.required || lockscreen.required
36+ readonly property bool animating: coverPage.showAnimation.running || coverPage.hideAnimation.running
3637
3738 signal selected(int index) // unused
3839 signal responded(string response)
qml/Greeter/ShimGreeter.qmlnew file+45
@@ -0,0 +1,45 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.3
18+
19+/*
20+ * This is a shim greeter that is only used to provide a shell,
21+ * running without a greeter, the requisite information
22+ *
23+ */
24+
25+/* FIXME: this shuld be fine as a QtObject, but bug lp:1447391
26+ * dictates wrapping as an item instead
27+ */
28+Item {
29+ id: shimGreeter
30+
31+ readonly property bool active: false
32+ readonly property bool hasLockedApp: lockedApp !== ""
33+ readonly property bool locked: false
34+ readonly property bool shown: false
35+ readonly property bool waiting: false
36+
37+ property string lockedApp: ""
38+
39+ // Since ShimGreeter is never active, these can just return
40+ property var forceShow: (function() { return; })
41+ property var notifyAboutToFocusApp: (function(appId) { return; })
42+ property var notifyAppFocused: (function(appId) { return; })
43+ property var notifyShowingDashFromDrag: (function(appId) { return false; })
44+
45+}
qml/Greeter/WideView.qml+1
@@ -32,6 +32,7 @@ FocusScope {
3232 property alias infographicModel: coverPage.infographicModel
3333 readonly property bool fullyShown: coverPage.showProgress === 1
3434 readonly property bool required: coverPage.required
35+ readonly property bool animating: coverPage.showAnimation.running || coverPage.hideAnimation.running
3536
3637 // so that it can be replaced in tests with a mock object
3738 property var inputMethod: Qt.inputMethod
qml/Notifications/ShapedIcon.qml+4−3
@@ -14,8 +14,8 @@
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1515 */
1616
17−import QtQuick 2.0
18−import Ubuntu.Components 0.1
17+import QtQuick 2.3
18+import Ubuntu.Components 1.2
1919
2020Item {
2121 property string fileSource
@@ -25,7 +25,8 @@ Item {
2525 objectName: "shapedIcon"
2626 anchors.fill: parent
2727 visible: shaped
28− image: realImage
28+ source: realImage
29+ sourceFillMode: UbuntuShape.PreserveAspectCrop
2930 }
3031
3132 Image {
qml/OrientedShell.qmlnew file+185
@@ -0,0 +1,185 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import QtQuick.Window 2.0
19+import Unity.Session 0.1
20+import GSettings 1.0
21+import "Components"
22+import "Rotation"
23+
24+Rectangle {
25+ id: root
26+ color: "black"
27+
28+ implicitWidth: units.gu(40)
29+ implicitHeight: units.gu(71)
30+
31+ // NB: native and primary orientations here don't map exactly to their QScreen counterparts
32+ readonly property int nativeOrientation: width > height ? Qt.LandscapeOrientation : Qt.PortraitOrientation
33+
34+ readonly property int primaryOrientation:
35+ deviceConfiguration.primaryOrientation == deviceConfiguration.useNativeOrientation
36+ ? nativeOrientation : deviceConfiguration.primaryOrientation
37+
38+ DeviceConfiguration {
39+ id: deviceConfiguration
40+ name: applicationArguments.deviceName
41+ }
42+
43+ // to be overwritten by tests
44+ property var usageModeSettings: GSettings { schema.id: "com.canonical.Unity8" }
45+ property int physicalOrientation: Screen.orientation
46+ property bool orientationLocked: OrientationLock.enabled
47+ property var orientationLock: OrientationLock
48+
49+ property int orientation
50+ onPhysicalOrientationChanged: {
51+ if (!orientationLocked) {
52+ orientation = physicalOrientation;
53+ }
54+ }
55+ onOrientationLockedChanged: {
56+ if (orientationLocked) {
57+ orientationLock.savedOrientation = physicalOrientation;
58+ } else {
59+ orientation = physicalOrientation;
60+ }
61+ }
62+ Component.onCompleted: {
63+ if (orientationLocked) {
64+ orientation = orientationLock.savedOrientation;
65+ }
66+ }
67+
68+ readonly property int supportedOrientations: shell.supportedOrientations
69+ & deviceConfiguration.supportedOrientations
70+ property int acceptedOrientationAngle: {
71+ if (orientation & supportedOrientations) {
72+ return Screen.angleBetween(nativeOrientation, orientation);
73+ } else if (shell.orientation & supportedOrientations) {
74+ // stay where we are
75+ return shell.orientationAngle;
76+ } else if (angleToOrientation(shell.mainAppWindowOrientationAngle) & supportedOrientations) {
77+ return shell.mainAppWindowOrientationAngle;
78+ } else {
79+ // rotate to some supported orientation as we can't stay where we currently are
80+ // TODO: Choose the closest to the current one
81+ if (supportedOrientations & Qt.PortraitOrientation) {
82+ return Screen.angleBetween(nativeOrientation, Qt.PortraitOrientation);
83+ } else if (supportedOrientations & Qt.LandcscapeOrientation) {
84+ return Screen.angleBetween(nativeOrientation, Qt.LandscapeOrientation);
85+ } else if (supportedOrientations & Qt.InvertedPortraitOrientation) {
86+ return Screen.angleBetween(nativeOrientation, Qt.InvertedPortraitOrientation);
87+ } else if (supportedOrientations & Qt.InvertedLandscapeOrientation) {
88+ return Screen.angleBetween(nativeOrientation, Qt.InvertedLandscapeOrientation);
89+ } else {
90+ // if all fails, fallback to primary orientation
91+ return Screen.angleBetween(nativeOrientation, primaryOrientation);
92+ }
93+ }
94+ }
95+
96+ function angleToOrientation(angle) {
97+ switch (angle) {
98+ case 0:
99+ return nativeOrientation;
100+ break;
101+ case 90:
102+ return nativeOrientation === Qt.PortraitOrientation ? Qt.InvertedLandscapeOrientation
103+ : Qt.PortraitOrientation;
104+ break;
105+ case 180:
106+ return nativeOrientation === Qt.PortraitOrientation ? Qt.InvertedPortraitOrientation
107+ : Qt.InvertedLandscapeOrientation;
108+ break;
109+ case 270:
110+ return nativeOrientation === Qt.PortraitOrientation ? Qt.LandscapeOrientation
111+ : Qt.InvertedPortraitOrientation;
112+ break;
113+ default:
114+ console.warn("angleToOrientation: Invalid orientation angle: " + angle);
115+ return primaryOrientation;
116+ }
117+ }
118+
119+ RotationStates {
120+ id: rotationStates
121+ objectName: "rotationStates"
122+ orientedShell: root
123+ shell: shell
124+ shellCover: shellCover
125+ windowScreenshot: windowScreenshot
126+ }
127+
128+ Shell {
129+ id: shell
130+ objectName: "shell"
131+ width: root.width
132+ height: root.height
133+ orientation: root.angleToOrientation(orientationAngle)
134+ primaryOrientation: root.primaryOrientation
135+ nativeOrientation: root.nativeOrientation
136+ nativeWidth: root.width
137+ nativeHeight: root.height
138+
139+ // TODO: Factor in the connected input devices (eg: physical keyboard, mouse, touchscreen),
140+ // what's the output device (eg: big TV, desktop monitor, phone display), etc.
141+ usageScenario: {
142+ if (root.usageModeSettings.usageMode === "Windowed") {
143+ return "desktop";
144+ } else if (root.usageModeSettings.usageMode === "Staged"
145+ && deviceConfiguration.category === "desktop") {
146+ return "tablet";
147+ } else {
148+ return deviceConfiguration.category;
149+ }
150+ }
151+
152+ property real transformRotationAngle
153+ property real transformOriginX
154+ property real transformOriginY
155+
156+ transform: Rotation {
157+ origin.x: shell.transformOriginX; origin.y: shell.transformOriginY; axis { x: 0; y: 0; z: 1 }
158+ angle: shell.transformRotationAngle
159+ }
160+ }
161+
162+ Rectangle {
163+ id: shellCover
164+ color: "black"
165+ anchors.fill: parent
166+ visible: false
167+ }
168+
169+ WindowScreenshot {
170+ id: windowScreenshot
171+ visible: false
172+ width: root.width
173+ height: root.height
174+
175+ property real transformRotationAngle
176+ property real transformOriginX
177+ property real transformOriginY
178+
179+ transform: Rotation {
180+ origin.x: windowScreenshot.transformOriginX; origin.y: windowScreenshot.transformOriginY;
181+ axis { x: 0; y: 0; z: 1 }
182+ angle: windowScreenshot.transformRotationAngle
183+ }
184+ }
185+}
qml/Panel/Panel.qml+9
@@ -27,6 +27,9 @@ Item {
2727 property alias indicators: __indicators
2828 property alias callHint: __callHint
2929 property bool fullscreenMode: false
30+ property real indicatorAreaShowProgress: 1.0
31+
32+ opacity: fullscreenMode && indicators.fullyClosed ? 0.0 : 1.0
3033
3134 Rectangle {
3235 id: darkenedArea
@@ -58,6 +61,12 @@ Item {
5861 NumberAnimation { duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing }
5962 }
6063
64+ transform: Translate {
65+ y: indicators.state === "initial"
66+ ? (1.0 - indicatorAreaShowProgress) * -d.indicatorHeight
67+ : 0
68+ }
69+
6170 BorderImage {
6271 id: dropShadow
6372 anchors {
qml/Rotation/HalfLoopRotationAnimation.qmlnew file+46
@@ -0,0 +1,46 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.3
18+
19+SequentialAnimation {
20+ id: root
21+
22+ // set from outside
23+ property int fromAngle
24+ property int toAngle
25+ property var info
26+ property var shell
27+
28+ readonly property bool flipShellDimensions: toAngle == 90 || toAngle == 270
29+
30+ ScriptAction { script: {
31+ info.transitioning = true;
32+ shell.orientationAngle = root.toAngle;
33+ shell.x = (orientedShell.width - shell.width) / 2
34+ shell.y = (orientedShell.height - shell.height) / 2;
35+ shell.transformOriginX = shell.width / 2;
36+ shell.transformOriginY = shell.height / 2;
37+ shell.updateFocusedAppOrientation();
38+ } }
39+ NumberAnimation {
40+ target: shell
41+ property: "transformRotationAngle"
42+ from: root.fromAngle; to: root.toAngle
43+ duration: rotationDuration; easing.type: rotationEasing
44+ }
45+ ScriptAction { script: { info.transitioning = false; } }
46+}
qml/Rotation/ImmediateRotationAction.qmlnew file+45
@@ -0,0 +1,45 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.3
18+
19+ScriptAction {
20+ property var info
21+ property var shell
22+
23+ script: {
24+ info.transitioning = true;
25+ shell.orientationAngle = info.requestedOrientationAngle;
26+ shell.transformRotationAngle = info.requestedOrientationAngle;
27+
28+ // Making bindings as orientedShell's dimensions might wiggle during startup.
29+ if (info.requestedOrientationAngle === 90 || info.requestedOrientationAngle === 270) {
30+ shell.width = Qt.binding(function() { return orientedShell.height; });
31+ shell.height = Qt.binding(function() { return orientedShell.width; });
32+ } else {
33+ shell.width = Qt.binding(function() { return orientedShell.width; });
34+ shell.height = Qt.binding(function() { return orientedShell.height; });
35+ }
36+
37+ shell.x = Qt.binding(function() { return (orientedShell.width - shell.width) / 2; });
38+ shell.y = Qt.binding(function() { return (orientedShell.height - shell.height) / 2; });
39+ shell.transformOriginX = Qt.binding(function() { return shell.width / 2; });
40+ shell.transformOriginY = Qt.binding(function() { return shell.height / 2; });
41+
42+ shell.updateFocusedAppOrientation();
43+ info.transitioning = false;
44+ }
45+}
qml/Rotation/NinetyRotationAnimation.qmlnew file+90
@@ -0,0 +1,90 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.3
18+
19+SequentialAnimation {
20+ id: root
21+
22+ property int fromAngle
23+ property int toAngle
24+ property var info
25+ property var shell
26+
27+ readonly property real fromY: fromAngle === 0 || fromAngle === 90 ? 0 : orientedShell.height - orientedShell.width;
28+ readonly property real toY: toAngle === 0 || toAngle === 90 ? 0 : orientedShell.height - orientedShell.width;
29+ readonly property bool flipShellDimensions: toAngle == 90 || toAngle == 270
30+
31+ ScriptAction { script: {
32+ info.transitioning = true;
33+ windowScreenshot.take();
34+ windowScreenshot.visible = true;
35+ shell.orientationAngle = root.toAngle;
36+ shell.x = 0;
37+ shell.width = flipShellDimensions ? orientedShell.height : orientedShell.width;
38+ shell.height = flipShellDimensions ? orientedShell.width : orientedShell.height;
39+ shell.transformOriginX = orientedShell.width / 2;
40+ shell.transformOriginY = orientedShell.width / 2;
41+ shell.updateFocusedAppOrientation();
42+ shellCover.visible = true;
43+
44+ windowScreenshot.transformOriginX = orientedShell.width / 2;
45+ if (fromAngle == 180 || fromAngle == 270) {
46+ windowScreenshot.transformOriginY = orientedShell.height - (orientedShell.width / 2);
47+ } else {
48+ windowScreenshot.transformOriginY = orientedShell.width / 2;
49+ }
50+ } }
51+ ParallelAnimation {
52+ NumberAnimation {
53+ target: shellCover; property: "opacity"; from: 1; to: 0;
54+ duration: rotationDuration; easing.type: rotationEasing
55+ }
56+ RotationAnimation {
57+ target: shell; property: "transformRotationAngle";
58+ from: root.fromAngle; to: root.toAngle
59+ direction: RotationAnimation.Shortest
60+ duration: rotationDuration; easing.type: rotationEasing
61+ }
62+ NumberAnimation {
63+ target: shell; property: "y"
64+ from: root.fromY; to: root.toY
65+ duration: rotationDuration; easing.type: rotationEasing
66+ }
67+
68+ NumberAnimation {
69+ target: windowScreenshot; property: "opacity"; from: 1; to: 0;
70+ duration: rotationDuration; easing.type: rotationEasing
71+ }
72+ RotationAnimation {
73+ target: windowScreenshot; property: "transformRotationAngle";
74+ from: 0; to: root.toAngle - root.fromAngle
75+ direction: RotationAnimation.Shortest
76+ duration: rotationDuration; easing.type: rotationEasing
77+ }
78+ NumberAnimation {
79+ target: windowScreenshot; property: "y"
80+ from: 0; to: root.toY - root.fromY
81+ duration: rotationDuration; easing.type: rotationEasing
82+ }
83+ }
84+ ScriptAction { script: {
85+ windowScreenshot.visible = false;
86+ windowScreenshot.discard();
87+ shellCover.visible = false;
88+ info.transitioning = false;
89+ } }
90+}
qml/Rotation/RotationStates.qmlnew file+278
@@ -0,0 +1,278 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import Ubuntu.Components 1.1
19+import Powerd 0.1
20+
21+// Why the state machine is done that way:
22+// We cannot use regular PropertyChanges{} inside the State elements as steps in the
23+// transition animations must take place in a well defined order.
24+// Which means that we also cannot jump to a new state in the middle of a transition
25+// as that would make hell brake loose.
26+StateGroup {
27+ id: root
28+
29+ // to be set from the outside
30+ property Item orientedShell
31+ property Item shell
32+ property Item shellCover
33+ property Item windowScreenshot
34+
35+ property int rotationDuration: 450
36+ property int rotationEasing: Easing.InOutCubic
37+ // Those values are good for debugging/development
38+ //property int rotationDuration: 3000
39+ //property int rotationEasing: Easing.Linear
40+
41+ state: "0"
42+ states: [
43+ State { name: "0" },
44+ State { name: "90" },
45+ State { name: "180" },
46+ State { name: "270" }
47+ ]
48+
49+ property QtObject d: QtObject {
50+ id: d
51+
52+ property bool startingUp: true
53+ property var finishStartUpTimer: Timer {
54+ interval: 500
55+ onTriggered: d.startingUp = false
56+ }
57+ Component.onCompleted: {
58+ finishStartUpTimer.start();
59+ }
60+
61+ property bool transitioning: false
62+ onTransitioningChanged: {
63+ d.tryUpdateState();
64+ }
65+
66+ readonly property int requestedOrientationAngle: root.orientedShell.acceptedOrientationAngle
67+
68+ // Avoiding a direct call to tryUpdateState() as the state change might trigger an immediate
69+ // change to Shell.orientationAngle which, in its turn, causes a reevaluation of
70+ // requestedOrientationAngle (ie., OrientedShell.acceptedOrientationAngle). A reentrant evaluation
71+ // of a binding is detected by QML as a binding loop and QML will deny the reevalutation, which
72+ // will leave us in a bogus state.
73+ //
74+ // To avoid this mess we update the state in the next event loop iteration, ensuring a clean
75+ // call stack.
76+ onRequestedOrientationAngleChanged: {
77+ stateUpdateTimer.start();
78+ }
79+ property Timer stateUpdateTimer: Timer {
80+ id: stateUpdateTimer
81+ interval: 1
82+ onTriggered: { d.tryUpdateState(); }
83+ }
84+
85+ function tryUpdateState() {
86+ if (d.transitioning || (!d.startingUp && !root.shell.orientationChangesEnabled)) {
87+ return;
88+ }
89+
90+ var requestedState = d.requestedOrientationAngle.toString();
91+ if (requestedState !== root.state) {
92+ d.resolveAnimationType();
93+ root.state = requestedState;
94+ }
95+ }
96+
97+ property Connections shellConnections: Connections {
98+ target: root.shell
99+ onOrientationChangesEnabledChanged: {
100+ d.tryUpdateState();
101+ }
102+ }
103+
104+ property var shellBeingResized: Binding {
105+ target: root.shell
106+ property: "beingResized"
107+ value: d.transitioning
108+ }
109+
110+ readonly property int fullAnimation: 0
111+ readonly property int indicatorsBarAnimation: 1
112+ readonly property int noAnimation: 2
113+
114+ property int animationType
115+
116+ // animationType update *must* take place *before* the state update.
117+ // If animationType and state were updated through bindings, as with normal qml code,
118+ // there would be no guarantee in the order of the binding updates, which could then
119+ // cause the wrong transitions to be chosen for the state changes.
120+ function resolveAnimationType() {
121+ if (d.startingUp) {
122+ // During start up, inital property values are still settling while we're still
123+ // to render the very first frame
124+ d.animationType = d.noAnimation;
125+ } else if (Powerd.status === Powerd.Off) {
126+ // There's no point in animating if the user can't see it (display is off).
127+ d.animationType = d.noAnimation;
128+ } else if (root.shell.showingGreeter) {
129+ // A rotating greeter looks weird.
130+ d.animationType = d.noAnimation;
131+ } else {
132+ if (!root.shell.mainApp) {
133+ // shouldn't happen but, anyway
134+ d.animationType = d.fullAnimation;
135+ return;
136+ }
137+
138+ if (root.shell.mainApp.rotatesWindowContents) {
139+ // The application will animate its own GUI, so we don't have to do anything ourselves.
140+ d.animationType = d.noAnimation;
141+ } else if (root.shell.mainAppWindowOrientationAngle == d.requestedOrientationAngle) {
142+ // The app window is already on its final orientation angle.
143+ // So we just animate the indicators bar
144+ // TODO: what if the app is fullscreen?
145+ d.animationType = d.indicatorsBarAnimation;
146+ } else {
147+ d.animationType = d.fullAnimation;
148+ }
149+ }
150+ }
151+
152+ // When an application switch takes place, d.requestedOrientationAngle and
153+ // root.shell.mainAppWindowOrientationAngle get updated separately, at different moments.
154+ // So, when one of those properties change, we shouldn't make a decision straight away
155+ // as the other might be stale and about to be changed. So let's give it a bit of time for
156+ // them to get properly updated.
157+ // This approach is indeed a bit hacky.
158+ property bool appWindowOrientationAngleNeedsUpdateUnstable:
159+ root.shell.orientationAngle === d.requestedOrientationAngle
160+ && root.shell.mainApp
161+ && root.shell.mainAppWindowOrientationAngle !== root.shell.orientationAngle
162+ && !d.transitioning
163+ onAppWindowOrientationAngleNeedsUpdateUnstableChanged: {
164+ stableTimer.restart();
165+ }
166+ property Timer stableTimer: Timer {
167+ interval: 200
168+ onTriggered: {
169+ if (d.appWindowOrientationAngleNeedsUpdateUnstable) {
170+ shell.updateFocusedAppOrientationAnimated();
171+ }
172+ }
173+ }
174+ }
175+
176+ transitions: [
177+ Transition {
178+ from: "90"; to: "0"
179+ enabled: d.animationType == d.fullAnimation
180+ NinetyRotationAnimation { fromAngle: 90; toAngle: 0
181+ info: d; shell: root.shell }
182+ },
183+ Transition {
184+ from: "0"; to: "90"
185+ enabled: d.animationType == d.fullAnimation
186+ NinetyRotationAnimation { fromAngle: 0; toAngle: 90
187+ info: d; shell: root.shell }
188+ },
189+ Transition {
190+ from: "0"; to: "270"
191+ enabled: d.animationType == d.fullAnimation
192+ NinetyRotationAnimation { fromAngle: 0; toAngle: 270
193+ info: d; shell: root.shell }
194+ },
195+ Transition {
196+ from: "270"; to: "0"
197+ enabled: d.animationType == d.fullAnimation
198+ NinetyRotationAnimation { fromAngle: 270; toAngle: 0
199+ info: d; shell: root.shell }
200+ },
201+ Transition {
202+ from: "90"; to: "180"
203+ enabled: d.animationType == d.fullAnimation
204+ NinetyRotationAnimation { fromAngle: 90; toAngle: 180
205+ info: d; shell: root.shell }
206+ },
207+ Transition {
208+ from: "180"; to: "90"
209+ enabled: d.animationType == d.fullAnimation
210+ NinetyRotationAnimation { fromAngle: 180; toAngle: 90
211+ info: d; shell: root.shell }
212+ },
213+ Transition {
214+ from: "180"; to: "270"
215+ enabled: d.animationType == d.fullAnimation
216+ NinetyRotationAnimation { fromAngle: 180; toAngle: 270
217+ info: d; shell: root.shell }
218+ },
219+ Transition {
220+ from: "270"; to: "180"
221+ enabled: d.animationType == d.fullAnimation
222+ NinetyRotationAnimation { fromAngle: 270; toAngle: 180
223+ info: d; shell: root.shell }
224+ },
225+ Transition {
226+ from: "0"; to: "180"
227+ enabled: d.animationType == d.fullAnimation
228+ HalfLoopRotationAnimation { fromAngle: 0; toAngle: 180
229+ info: d; shell: root.shell }
230+ },
231+ Transition {
232+ from: "180"; to: "0"
233+ enabled: d.animationType == d.fullAnimation
234+ HalfLoopRotationAnimation { fromAngle: 180; toAngle: 0
235+ info: d; shell: root.shell }
236+ },
237+ Transition {
238+ from: "90"; to: "270"
239+ enabled: d.animationType == d.fullAnimation
240+ HalfLoopRotationAnimation { fromAngle: 90; toAngle: 270
241+ info: d; shell: root.shell }
242+ },
243+ Transition {
244+ from: "270"; to: "90"
245+ enabled: d.animationType == d.fullAnimation
246+ HalfLoopRotationAnimation { fromAngle: 270; toAngle: 90
247+ info: d; shell: root.shell }
248+ },
249+ Transition {
250+ objectName: "immediateTransition"
251+ enabled: d.animationType == d.noAnimation
252+ ImmediateRotationAction { info: d; shell: root.shell }
253+ },
254+ Transition {
255+ enabled: d.animationType == d.indicatorsBarAnimation
256+ SequentialAnimation {
257+ ScriptAction { script: {
258+ d.transitioning = true;
259+ } }
260+ NumberAnimation {
261+ duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing
262+ target: root.shell; property: "indicatorAreaShowProgress"
263+ from: 1.0; to: 0.0
264+ }
265+ ImmediateRotationAction { info: d; shell: root.shell }
266+ NumberAnimation {
267+ duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing
268+ target: root.shell; property: "indicatorAreaShowProgress"
269+ from: 0.0; to: 1.0
270+ }
271+ ScriptAction { script: {
272+ d.transitioning = false;
273+ }}
274+ }
275+ }
276+ ]
277+
278+}
qml/Shell.qml+157−81
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -45,47 +45,80 @@ import Unity.Indicators 0.1 as Indicators
4545Item {
4646 id: shell
4747
48+ // to be set from outside
49+ property int orientationAngle: 0
50+ property int orientation
51+ property int primaryOrientation
52+ property int nativeOrientation
53+ property real nativeWidth
54+ property real nativeHeight
55+ property alias indicatorAreaShowProgress: panel.indicatorAreaShowProgress
56+ property bool beingResized
57+ property string usageScenario: "phone" // supported values: "phone", "tablet" or "desktop"
58+ function updateFocusedAppOrientation() {
59+ applicationsDisplayLoader.item.updateFocusedAppOrientation();
60+ }
61+ function updateFocusedAppOrientationAnimated() {
62+ applicationsDisplayLoader.item.updateFocusedAppOrientationAnimated();
63+ }
64+
65+ // to be read from outside
66+ readonly property int mainAppWindowOrientationAngle:
67+ applicationsDisplayLoader.item ? applicationsDisplayLoader.item.mainAppWindowOrientationAngle : 0
68+
69+ readonly property bool orientationChangesEnabled: panel.indicators.fullyClosed
70+ && (applicationsDisplayLoader.item && applicationsDisplayLoader.item.orientationChangesEnabled)
71+ && !greeter.animating
72+
73+ readonly property bool showingGreeter: greeter.shown
74+
75+ property bool startingUp: true
76+ Timer { id: finishStartUpTimer; interval: 500; onTriggered: startingUp = false }
77+
78+ property int supportedOrientations: {
79+ if (startingUp) {
80+ // Ensure we don't rotate during start up
81+ return Qt.PrimaryOrientation;
82+ } else if (greeter.shown) {
83+ return Qt.PrimaryOrientation;
84+ } else if (mainApp) {
85+ return mainApp.supportedOrientations;
86+ } else {
87+ // we just don't care
88+ return Qt.PortraitOrientation
89+ | Qt.LandscapeOrientation
90+ | Qt.InvertedPortraitOrientation
91+ | Qt.InvertedLandscapeOrientation;
92+ }
93+ }
94+
95+ // For autopilot consumption
96+ readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
97+
98+ // internal props from here onwards
99+ readonly property var mainApp:
100+ applicationsDisplayLoader.item ? applicationsDisplayLoader.item.mainApp : null
101+
48102 // Disable everything while greeter is waiting, so that the user can't swipe
49103 // the greeter or launcher until we know whether the session is locked.
50104 enabled: !greeter.waiting
51105
52− // this is only here to select the width / height of the window if not running fullscreen
53− property bool tablet: false
54− width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
55− height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
56−
57106 property real edgeSize: units.gu(2)
58107 property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
59108 property url background: asImageTester.status == Image.Ready ? asImageTester.source
60109 : gsImageTester.status == Image.Ready ? gsImageTester.source : defaultBackground
61110
62− property bool sideStageEnabled: shell.width >= units.gu(100)
63− readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
64−
65− property int orientation
66− readonly property int deviceOrientationAngle: Screen.angleBetween(Screen.primaryOrientation, Screen.orientation)
67− onDeviceOrientationAngleChanged: {
68− if (!OrientationLock.enabled) {
69− orientation = Screen.orientation;
70− }
71− }
72− readonly property bool orientationLockEnabled: OrientationLock.enabled
73− onOrientationLockEnabledChanged: {
74− if (orientationLockEnabled) {
75− OrientationLock.savedOrientation = Screen.orientation;
76− } else {
77− orientation = Screen.orientation;
78− }
79− }
80−
81111 // This is _only_ used to expose the property to autopilot tests
82112 readonly property string testShellMode: shellMode
83113
114+ readonly property alias greeter: greeterLoader.item
115+
84116 function activateApplication(appId) {
85117 if (ApplicationManager.findApplication(appId)) {
86118 ApplicationManager.requestFocusApplication(appId);
87119 } else {
88− var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
120+ var execFlags = shell.usageScenario === "phone" ? ApplicationManager.ForceMainStage
121+ : ApplicationManager.NoFlag;
89122 ApplicationManager.startApplication(appId, execFlags);
90123 }
91124 }
@@ -122,11 +155,6 @@ Item {
122155 sourceSize.width: 0
123156 }
124157
125− GSettings {
126− id: usageModeSettings
127− schema.id: "com.canonical.Unity8"
128− }
129−
130158 Binding {
131159 target: LauncherModel
132160 property: "applicationManager"
@@ -138,9 +166,7 @@ Item {
138166 if (ApplicationManager.count > 0) {
139167 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
140168 }
141− if (orientationLockEnabled) {
142− orientation = OrientationLock.savedOrientation;
143− }
169+ finishStartUpTimer.start();
144170 }
145171
146172 VolumeControl {
@@ -233,9 +259,21 @@ Item {
233259 // theoretical attack where user enters lockedApp mode, then makes
234260 // the screen larger (maybe connects to monitor) and tries to enter
235261 // tablet mode.
236− property bool tabletMode: shell.sideStageEnabled && !greeter.hasLockedApp
237− source: usageModeSettings.usageMode === "Windowed" ? "Stages/DesktopStage.qml"
238− : tabletMode ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
262+
263+ property string usageScenario: shell.usageScenario === "phone" || greeter.hasLockedApp
264+ ? "phone"
265+ : shell.usageScenario
266+ source: {
267+ if(shellMode === "greeter") {
268+ return "Stages/ShimStage.qml"
269+ } else if (applicationsDisplayLoader.usageScenario === "phone") {
270+ return "Stages/PhoneStage.qml";
271+ } else if (applicationsDisplayLoader.usageScenario === "tablet") {
272+ return "Stages/TabletStage.qml";
273+ } else {
274+ return "Stages/DesktopStage.qml";
275+ }
276+ }
239277
240278 property bool interactive: tutorial.spreadEnabled
241279 && !greeter.shown
@@ -278,7 +316,12 @@ Item {
278316 }
279317 Binding {
280318 target: applicationsDisplayLoader.item
281− property: "orientation"
319+ property: "shellOrientationAngle"
320+ value: shell.orientationAngle
321+ }
322+ Binding {
323+ target: applicationsDisplayLoader.item
324+ property: "shellOrientation"
282325 value: shell.orientation
283326 }
284327 Binding {
@@ -286,6 +329,31 @@ Item {
286329 property: "background"
287330 value: shell.background
288331 }
332+ Binding {
333+ target: applicationsDisplayLoader.item
334+ property: "shellPrimaryOrientation"
335+ value: shell.primaryOrientation
336+ }
337+ Binding {
338+ target: applicationsDisplayLoader.item
339+ property: "nativeOrientation"
340+ value: shell.nativeOrientation
341+ }
342+ Binding {
343+ target: applicationsDisplayLoader.item
344+ property: "nativeWidth"
345+ value: shell.nativeWidth
346+ }
347+ Binding {
348+ target: applicationsDisplayLoader.item
349+ property: "nativeHeight"
350+ value: shell.nativeHeight
351+ }
352+ Binding {
353+ target: applicationsDisplayLoader.item
354+ property: "beingResized"
355+ value: shell.beingResized
356+ }
289357 }
290358
291359 Tutorial {
@@ -350,50 +418,59 @@ Item {
350418 }
351419 }
352420
353− Greeter {
354− id: greeter
355− objectName: "greeter"
356−
357− hides: [launcher, panel.indicators]
358− tabletMode: shell.sideStageEnabled
359− launcherOffset: launcher.progress
360− forcedUnlock: tutorial.running
361− background: shell.background
362−
421+ Loader {
422+ id: greeterLoader
363423 anchors.fill: parent
364424 anchors.topMargin: panel.panelHeight
425+ sourceComponent: shellMode != "shell" ? integratedGreeter :
426+ Qt.createComponent(Qt.resolvedUrl("Greeter/ShimGreeter.qml"));
427+ onLoaded: {
428+ item.objectName = "greeter"
429+ }
430+ }
365431
366− // avoid overlapping with Launcher's edge drag area
367− // FIXME: Fix TouchRegistry & friends and remove this workaround
368− // Issue involves launcher's DDA getting disabled on a long
369− // left-edge drag
370− dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
432+ Component {
433+ id: integratedGreeter
434+ Greeter {
371435
372− onSessionStarted: {
373− launcher.hide();
374− }
436+ hides: [launcher, panel.indicators]
437+ tabletMode: shell.sideStageEnabled
438+ launcherOffset: launcher.progress
439+ forcedUnlock: tutorial.running
440+ background: shell.background
375441
376− onTease: {
377− if (!tutorial.running) {
378− launcher.tease();
442+ // avoid overlapping with Launcher's edge drag area
443+ // FIXME: Fix TouchRegistry & friends and remove this workaround
444+ // Issue involves launcher's DDA getting disabled on a long
445+ // left-edge drag
446+ dragHandleLeftMargin: launcher.available ? launcher.dragAreaWidth + 1 : 0
447+
448+ onSessionStarted: {
449+ launcher.hide();
379450 }
380− }
381451
382− onEmergencyCall: startLockedApp("dialer-app")
452+ onTease: {
453+ if (!tutorial.running) {
454+ launcher.tease();
455+ }
456+ }
383457
384− Timer {
385− // See powerConnection for why this is useful
386− id: showGreeterDelayed
387− interval: 1
388− onTriggered: {
389− greeter.forceShow();
458+ onEmergencyCall: startLockedApp("dialer-app")
459+
460+ Binding {
461+ target: ApplicationManager
462+ property: "suspended"
463+ value: greeter.shown
390464 }
391465 }
466+ }
392467
393− Binding {
394− target: ApplicationManager
395− property: "suspended"
396− value: greeter.shown
468+ Timer {
469+ // See powerConnection for why this is useful
470+ id: showGreeterDelayed
471+ interval: 1
472+ onTriggered: {
473+ greeter.forceShow();
397474 }
398475 }
399476
@@ -482,20 +559,18 @@ Item {
482559 expandedPanelHeight: units.gu(7)
483560
484561 indicatorsModel: Indicators.IndicatorsModel {
485− // TODO: This should be sourced by device type (e.g. "desktop", "tablet", "phone"...)
486− profile: indicatorProfile
487− Component.onCompleted: load()
562+ // tablet and phone both use the same profile
563+ profile: shell.usageScenario === "desktop" ? "desktop" : "phone"
564+ Component.onCompleted: load();
488565 }
489566 }
567+
490568 callHint {
491569 greeterShown: greeter.shown
492570 }
493571
494− property bool topmostApplicationIsFullscreen:
495− ApplicationManager.focusedApplicationId &&
496− ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
497−
498− fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
572+ property bool mainAppIsFullscreen: shell.mainApp && shell.mainApp.fullscreen
573+ fullscreenMode: (mainAppIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
499574 || greeter.hasLockedApp
500575 }
501576
@@ -513,7 +588,7 @@ Item {
513588 available: tutorial.launcherEnabled
514589 && (!greeter.locked || AccountsService.enableLauncherWhileLocked)
515590 && !greeter.hasLockedApp
516− inverted: usageModeSettings.usageMode === "Staged"
591+ inverted: shell.usageScenario !== "desktop"
517592 shadeBackground: !tutorial.running
518593
519594 onShowDashHome: showHome()
@@ -555,7 +630,7 @@ Item {
555630 Rectangle {
556631 id: modalNotificationBackground
557632
558− visible: notifications.useModal && (notifications.state == "narrow")
633+ visible: notifications.useModal
559634 color: "#000000"
560635 anchors.fill: parent
561636 opacity: 0.9
@@ -600,6 +675,7 @@ Item {
600675
601676 Dialogs {
602677 id: dialogs
678+ objectName: "dialogs"
603679 anchors.fill: parent
604680 z: overlay.z + 10
605681 onPowerOffClicked: {
qml/Stages/ApplicationWindow.qml+53−18
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2014 Canonical Ltd.
2+ * Copyright 2014-2015 Canonical Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU Lesser General Public License as published by
@@ -24,10 +24,11 @@ FocusScope {
2424 // to be read from outside
2525 readonly property bool fullscreen: application ? application.fullscreen : false
2626 property alias interactive: sessionContainer.interactive
27+ property bool orientationChangesEnabled: d.supportsSurfaceResize ? d.surfaceOldEnoughToBeResized : true
2728
2829 // to be set from outside
2930 property QtObject application
30− property int orientation
31+ property int surfaceOrientationAngle
3132
3233 QtObject {
3334 id: d
@@ -62,6 +63,15 @@ FocusScope {
6263 // Remove this when possible
6364 property bool surfaceInitialized: false
6465
66+ property bool supportsSurfaceResize:
67+ application &&
68+ ((application.supportedOrientations & Qt.PortraitOrientation)
69+ || (application.supportedOrientations & Qt.InvertedPortraitOrientation))
70+ &&
71+ ((application.supportedOrientations & Qt.LandscapeOrientation)
72+ || (application.supportedOrientations & Qt.InvertedLandscapeOrientation))
73+
74+ property bool surfaceOldEnoughToBeResized: false
6575 }
6676
6777 Timer {
@@ -70,6 +80,12 @@ FocusScope {
7080 onTriggered: { if (sessionContainer.surface) {d.surfaceInitialized = true;} }
7181 }
7282
83+ Timer {
84+ id: surfaceIsOldTimer
85+ interval: 1000
86+ onTriggered: { if (stateGroup.state === "surface") { d.surfaceOldEnoughToBeResized = true; } }
87+ }
88+
7389 Image {
7490 id: screenshotImage
7591 objectName: "screenshotImage"
@@ -113,7 +129,8 @@ FocusScope {
113129 // A fake application might not even have a session property.
114130 session: application && application.session ? application.session : null
115131 anchors.fill: parent
116− orientation: root.orientation
132+
133+ surfaceOrientationAngle: application && application.rotatesWindowContents ? root.surfaceOrientationAngle : 0
117134
118135 onSurfaceChanged: {
119136 if (sessionContainer.surface) {
@@ -179,15 +196,21 @@ FocusScope {
179196 UbuntuNumberAnimation { target: sessionContainer.surfaceContainer; property: "opacity";
180197 from: 0.0; to: 1.0
181198 duration: UbuntuAnimation.BriskDuration }
182− PropertyAction { target: splashLoader; property: "active"; value: false }
199+ ScriptAction { script: {
200+ splashLoader.active = false;
201+ surfaceIsOldTimer.start();
202+ } }
183203 }
184204 },
185205 Transition {
186206 from: "surface"; to: "splashScreen"
187207 SequentialAnimation {
188− PropertyAction { target: splashLoader; property: "active"; value: true }
189− PropertyAction { target: sessionContainer.surfaceContainer
190− property: "visible"; value: true }
208+ ScriptAction { script: {
209+ surfaceIsOldTimer.stop();
210+ d.surfaceOldEnoughToBeResized = false;
211+ splashLoader.active = true;
212+ sessionContainer.surfaceContainer.visible = true;
213+ } }
191214 UbuntuNumberAnimation { target: splashLoader; property: "opacity";
192215 from: 0.0; to: 1.0
193216 duration: UbuntuAnimation.BriskDuration }
@@ -198,14 +221,18 @@ FocusScope {
198221 Transition {
199222 from: "surface"; to: "screenshot"
200223 SequentialAnimation {
201− PropertyAction { target: screenshotImage
202− property: "visible"; value: true }
224+ ScriptAction { script: {
225+ surfaceIsOldTimer.stop();
226+ d.surfaceOldEnoughToBeResized = false;
227+ screenshotImage.visible = true;
228+ } }
203229 UbuntuNumberAnimation { target: screenshotImage; property: "opacity";
204230 from: 0.0; to: 1.0
205231 duration: UbuntuAnimation.BriskDuration }
206− PropertyAction { target: sessionContainer.surfaceContainer
207− property: "visible"; value: false }
208− ScriptAction { script: { if (sessionContainer.session) { sessionContainer.session.release(); } } }
232+ ScriptAction { script: {
233+ sessionContainer.surfaceContainer.visible = false;
234+ if (sessionContainer.session) { sessionContainer.session.release(); }
235+ } }
209236 }
210237 },
211238 Transition {
@@ -216,8 +243,11 @@ FocusScope {
216243 UbuntuNumberAnimation { target: screenshotImage; property: "opacity";
217244 from: 1.0; to: 0.0
218245 duration: UbuntuAnimation.BriskDuration }
219− PropertyAction { target: screenshotImage; property: "visible"; value: false }
220− PropertyAction { target: screenshotImage; property: "source"; value: "" }
246+ ScriptAction { script: {
247+ screenshotImage.visible = false;
248+ screenshotImage.source = "";
249+ surfaceIsOldTimer.start();
250+ } }
221251 }
222252 },
223253 Transition {
@@ -233,10 +263,12 @@ FocusScope {
233263 },
234264 Transition {
235265 from: "surface"; to: "void"
236− SequentialAnimation {
237− PropertyAction { target: sessionContainer.surfaceContainer; property: "visible"; value: false }
238− ScriptAction { script: { if (sessionContainer.session) { sessionContainer.session.release(); } } }
239− }
266+ ScriptAction { script: {
267+ surfaceIsOldTimer.stop();
268+ d.surfaceOldEnoughToBeResized = false;
269+ sessionContainer.surfaceContainer.visible = false;
270+ if (sessionContainer.session) { sessionContainer.session.release(); }
271+ } }
240272 },
241273 Transition {
242274 from: "void"; to: "surface"
@@ -246,6 +278,9 @@ FocusScope {
246278 UbuntuNumberAnimation { target: sessionContainer.surfaceContainer; property: "opacity";
247279 from: 0.0; to: 1.0
248280 duration: UbuntuAnimation.BriskDuration }
281+ ScriptAction { script: {
282+ surfaceIsOldTimer.start();
283+ } }
249284 }
250285 }
251286 ]
qml/Stages/DesktopStage.qml+23
@@ -26,6 +26,29 @@ Rectangle {
2626
2727 anchors.fill: parent
2828
29+ // Controls to be set from outside
30+ property int dragAreaWidth // just to comply with the interface shared between stages
31+ property real maximizedAppTopMargin
32+ property bool interactive
33+ property bool spreadEnabled // just to comply with the interface shared between stages
34+ property real inverseProgress: 0 // just to comply with the interface shared between stages
35+ property int shellOrientationAngle: 0
36+ property int shellOrientation
37+ property int shellPrimaryOrientation
38+ property int nativeOrientation
39+ property bool beingResized: false
40+
41+ // functions to be called from outside
42+ function updateFocusedAppOrientation() { /* TODO */ }
43+ function updateFocusedAppOrientationAnimated() { /* TODO */}
44+
45+ // To be read from outside
46+ readonly property var mainApp: ApplicationManager.focusedApplicationId
47+ ? ApplicationManager.findApplication(ApplicationManager.focusedApplicationId)
48+ : null
49+ property int mainAppWindowOrientationAngle: 0
50+ readonly property bool orientationChangesEnabled: false
51+
2952 property alias background: wallpaper.source
3053
3154 CrossFadeImage {
qml/Stages/OrientationChangeAnimation.qmlnew file+241
@@ -0,0 +1,241 @@
1+/*
2+ * Copyright 2015 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU 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+
17+import QtQuick 2.3
18+
19+QtObject {
20+ id: root
21+
22+ // to be set from outside
23+ property Item spreadDelegate
24+ property Item background
25+ property Item window
26+ property Item screenshot
27+
28+ function start() {
29+ if (window.orientationAngle === 0) {
30+ if (spreadDelegate.shellOrientationAngle === 90) {
31+ chosenAnimation = simple90Animation;
32+ } else if (spreadDelegate.shellOrientationAngle === 180) {
33+ chosenAnimation = halfLoopAnimation;
34+ } else if (spreadDelegate.shellOrientationAngle === 270) {
35+ chosenAnimation = moving90Animation;
36+ } else {
37+ chosenAnimation = null;
38+ }
39+ } else if (window.orientationAngle === 90) {
40+ if (spreadDelegate.shellOrientationAngle === 0) {
41+ chosenAnimation = simple90Animation;
42+ } else if (spreadDelegate.shellOrientationAngle === 180) {
43+ chosenAnimation = moving90Animation;
44+ } else if (spreadDelegate.shellOrientationAngle === 270) {
45+ chosenAnimation = halfLoopAnimation;
46+ } else {
47+ chosenAnimation = null;
48+ }
49+ } else if (window.orientationAngle === 180) {
50+ if (spreadDelegate.shellOrientationAngle === 0) {
51+ chosenAnimation = halfLoopAnimation;
52+ } else if (spreadDelegate.shellOrientationAngle === 90) {
53+ chosenAnimation = moving90Animation;
54+ } else if (spreadDelegate.shellOrientationAngle === 270) {
55+ chosenAnimation = simple90Animation;
56+ } else {
57+ chosenAnimation = null;
58+ }
59+ } else if (window.orientationAngle === 270) {
60+ if (spreadDelegate.shellOrientationAngle === 0) {
61+ chosenAnimation = moving90Animation;
62+ } else if (spreadDelegate.shellOrientationAngle === 90) {
63+ chosenAnimation = halfLoopAnimation;
64+ } else if (spreadDelegate.shellOrientationAngle === 180) {
65+ chosenAnimation = simple90Animation;
66+ } else {
67+ chosenAnimation = null;
68+ }
69+ }
70+
71+ if (chosenAnimation)
72+ chosenAnimation.start();
73+ }
74+
75+ // to be read from outside
76+ property bool running: chosenAnimation ? chosenAnimation.running : false
77+
78+ property int duration: 450
79+ property int easingType: Easing.InOutCubic
80+
81+ property int shortestDimension: spreadDelegate.width < spreadDelegate.height
82+ ? spreadDelegate.width : spreadDelegate.height
83+ property int longestDimension: spreadDelegate.width > spreadDelegate.height
84+ ? spreadDelegate.width : spreadDelegate.height
85+ property string longestAxis: spreadDelegate.width > spreadDelegate.height ? "x" : "y"
86+
87+ property QtObject chosenAnimation
88+
89+ function setup90Animation() {
90+ background.visible = true;
91+
92+ screenshot.width = window.width;
93+ screenshot.height = window.height;
94+ screenshot.window.anchors.topMargin = window.window.anchors.topMargin;
95+ screenshot.take();
96+ screenshot.transformOriginX = root.shortestDimension / 2;
97+ screenshot.transformOriginY = root.shortestDimension / 2;
98+ screenshot.visible = true;
99+
100+ window.rotation = 0;
101+ window.width = spreadDelegate.width;
102+ window.height = spreadDelegate.height;
103+ window.transformOriginX = root.shortestDimension / 2;
104+ window.transformOriginY = root.shortestDimension / 2;
105+ }
106+
107+ function tearDown90Animation() {
108+ window.orientationAngle = spreadDelegate.shellOrientationAngle;
109+ screenshot.discard();
110+ screenshot.visible = false;
111+ background.visible = false;
112+ }
113+
114+ property QtObject simple90Animation: SequentialAnimation {
115+ id: simple90Animation
116+
117+ ScriptAction { script: setup90Animation() }
118+ ParallelAnimation {
119+ RotationAnimation {
120+ target: root.window
121+ duration: root.duration
122+ easing.type: root.easingType
123+ from: window.orientationAngle - spreadDelegate.shellOrientationAngle
124+ to: 0
125+ property: "transformRotationAngle"
126+ }
127+ RotationAnimation {
128+ target: root.screenshot
129+ duration: root.duration
130+ easing.type: root.easingType
131+ from: window.orientationAngle - spreadDelegate.shellOrientationAngle
132+ to: 0
133+ property: "transformRotationAngle"
134+ }
135+ NumberAnimation {
136+ target: root.screenshot
137+ duration: root.duration
138+ easing.type: root.easingType
139+ property: "opacity"
140+ from: 1.0
141+ to: 0.0
142+ }
143+ NumberAnimation {
144+ target: root.window
145+ duration: root.duration
146+ easing.type: root.easingType
147+ property: "opacity"
148+ from: 0.0
149+ to: 1.0
150+ }
151+ }
152+ ScriptAction { script: tearDown90Animation() }
153+ }
154+
155+ property QtObject moving90Animation: SequentialAnimation {
156+ id: moving90Animation
157+
158+ ScriptAction { script: setup90Animation() }
159+ ParallelAnimation {
160+ RotationAnimation {
161+ target: root.window
162+ duration: root.duration
163+ easing.type: root.easingType
164+ direction: RotationAnimation.Shortest
165+ from: window.orientationAngle - spreadDelegate.shellOrientationAngle
166+ to: 0
167+ property: "transformRotationAngle"
168+ }
169+ RotationAnimation {
170+ target: root.screenshot
171+ duration: root.duration
172+ easing.type: root.easingType
173+ direction: RotationAnimation.Shortest
174+ from: window.orientationAngle - spreadDelegate.shellOrientationAngle
175+ to: 0
176+ property: "transformRotationAngle"
177+ }
178+ NumberAnimation {
179+ target: root.screenshot
180+ duration: root.duration
181+ easing.type: root.easingType
182+ property: "opacity"
183+ from: 1.0
184+ to: 0.0
185+ }
186+ NumberAnimation {
187+ target: root.window
188+ duration: root.duration
189+ easing.type: root.easingType
190+ property: "opacity"
191+ from: 0.0
192+ to: 1.0
193+ }
194+ NumberAnimation {
195+ target: root.window
196+ duration: root.duration
197+ easing.type: root.easingType
198+ property: root.longestAxis
199+ from: root.longestDimension - root.shortestDimension
200+ to: 0
201+ }
202+ NumberAnimation {
203+ target: root.screenshot
204+ duration: root.duration
205+ easing.type: root.easingType
206+ property: root.longestAxis
207+ from: root.longestDimension - root.shortestDimension
208+ to: 0
209+ }
210+ }
211+ ScriptAction { script: tearDown90Animation() }
212+ }
213+
214+ property QtObject halfLoopAnimation: SequentialAnimation {
215+ id: halfLoopAnimation
216+
217+ ScriptAction { script: {
218+ background.visible = true;
219+
220+ window.rotation = 0;
221+ window.width = spreadDelegate.width;
222+ window.height = spreadDelegate.height;
223+ window.transformOriginX = window.width / 2
224+ window.transformOriginY = window.height / 2
225+ } }
226+ ParallelAnimation {
227+ RotationAnimation {
228+ target: root.window
229+ duration: root.duration
230+ easing.type: root.easingType
231+ from: window.orientationAngle - spreadDelegate.shellOrientationAngle
232+ to: 0
233+ property: "transformRotationAngle"
234+ }
235+ }
236+ ScriptAction { script: {
237+ window.orientationAngle = spreadDelegate.shellOrientationAngle;
238+ background.visible = false;
239+ } }
240+ }
241+}
qml/Stages/PhoneStage.qml+143−37
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -31,12 +31,64 @@ Rectangle {
3131 property bool interactive
3232 property bool spreadEnabled: true // If false, animations and right edge will be disabled
3333 property real inverseProgress: 0 // This is the progress for left edge drags, in pixels.
34− property int orientation: Qt.PortraitOrientation
3534 property QtObject applicationManager: ApplicationManager
3635 property bool focusFirstApp: true // If false, focused app will appear on right edge like other apps
3736 property bool altTabEnabled: true
3837 property real startScale: 1.1
3938 property real endScale: 0.7
39+ property int shellOrientationAngle: 0
40+ property int shellOrientation
41+ property int shellPrimaryOrientation
42+ property int nativeOrientation
43+ property real nativeWidth
44+ property real nativeHeight
45+ property bool beingResized: false
46+ onBeingResizedChanged: {
47+ if (beingResized) {
48+ // Brace yourselves for impact!
49+ spreadView.selectedIndex = -1;
50+ spreadView.phase = 0;
51+ spreadView.contentX = -spreadView.shift;
52+ }
53+ }
54+ function updateFocusedAppOrientation() {
55+ if (spreadRepeater.count > 0) {
56+ spreadRepeater.itemAt(0).matchShellOrientation();
57+ }
58+
59+ for (var i = 1; i < spreadRepeater.count; ++i) {
60+
61+ var spreadDelegate = spreadRepeater.itemAt(i);
62+
63+ var delta = spreadDelegate.appWindowOrientationAngle - root.shellOrientationAngle;
64+ if (delta < 0) { delta += 360; }
65+ delta = delta % 360;
66+
67+ var supportedOrientations = spreadDelegate.application.supportedOrientations;
68+ if (supportedOrientations === Qt.PrimaryOrientation) {
69+ supportedOrientations = spreadDelegate.shellPrimaryOrientation;
70+ }
71+
72+ if (delta === 180 && (supportedOrientations & spreadDelegate.shellOrientation)) {
73+ spreadDelegate.matchShellOrientation();
74+ }
75+ }
76+ }
77+ function updateFocusedAppOrientationAnimated() {
78+ if (spreadRepeater.count > 0) {
79+ spreadRepeater.itemAt(0).animateToShellOrientation();
80+ }
81+ }
82+
83+ // To be read from outside
84+ readonly property var mainApp: applicationManager.focusedApplicationId
85+ ? applicationManager.findApplication(applicationManager.focusedApplicationId)
86+ : null
87+ property int mainAppWindowOrientationAngle: 0
88+ readonly property bool orientationChangesEnabled: priv.focusedAppOrientationChangesEnabled
89+ && !priv.focusedAppDelegateIsDislocated
90+ && !(priv.focusedAppDelegate && priv.focusedAppDelegate.xBehavior.running)
91+ && spreadView.phase === 0
4092
4193 // How far left the stage has been dragged
4294 readonly property real dragProgress: spreadRepeater.count > 0 ? -spreadRepeater.itemAt(0).xTranslate : 0
@@ -54,12 +106,6 @@ Rectangle {
54106 spreadView.snapTo(priv.indexOf(appId));
55107 }
56108
57− onWidthChanged: {
58− spreadView.selectedIndex = -1;
59− spreadView.phase = 0;
60− spreadView.contentX = -spreadView.shift;
61− }
62−
63109 onInverseProgressChanged: {
64110 // This can't be a simple binding because that would be triggered after this handler
65111 // while we need it active before doing the anition left/right
@@ -73,6 +119,20 @@ Rectangle {
73119 priv.oldInverseProgress = inverseProgress;
74120 }
75121
122+ // <FIXME-contentX> See rationale in the next comment with this tag
123+ onWidthChanged: {
124+ if (!root.beingResized) {
125+ // we're being resized without a warning (ie, the corresponding property wasn't set
126+ root.beingResized = true;
127+ beingResizedTimer.start();
128+ }
129+ }
130+ Timer {
131+ id: beingResizedTimer
132+ interval: 100
133+ onTriggered: { root.beingResized = false; }
134+ }
135+
76136 Connections {
77137 target: applicationManager
78138
@@ -115,8 +175,9 @@ Rectangle {
115175 QtObject {
116176 id: priv
117177
178+ property string focusedAppId: root.applicationManager.focusedApplicationId
179+ property bool focusedAppOrientationChangesEnabled: false
118180 readonly property int firstSpreadIndex: root.focusFirstApp ? 1 : 0
119− property string focusedAppId: applicationManager.focusedApplicationId
120181 readonly property var focusedAppDelegate: {
121182 var index = indexOf(focusedAppId);
122183 return index >= 0 && index < spreadRepeater.count ? spreadRepeater.itemAt(index) : null
@@ -131,15 +192,27 @@ Rectangle {
131192 }
132193 }
133194
195+ property bool focusedAppDelegateIsDislocated: focusedAppDelegate && focusedAppDelegate.x !== 0
196+
134197 function indexOf(appId) {
135− for (var i = 0; i < applicationManager.count; i++) {
136− if (applicationManager.get(i).appId == appId) {
198+ for (var i = 0; i < root.applicationManager.count; i++) {
199+ if (root.applicationManager.get(i).appId == appId) {
137200 return i;
138201 }
139202 }
140203 return -1;
141204 }
142205
206+ // Is more stable than "spreadView.shiftedContentX === 0" as it filters out noise caused by
207+ // Flickable.contentX changing due to resizes.
208+ property bool fullyShowingFocusedApp: true
209+ }
210+ Timer {
211+ id: fullyShowingFocusedAppUpdateTimer
212+ interval: 100
213+ onTriggered: {
214+ priv.fullyShowingFocusedApp = spreadView.shiftedContentX === 0;
215+ }
143216 }
144217
145218 Flickable {
@@ -187,9 +260,25 @@ Rectangle {
187260 property int draggedDelegateCount: 0
188261 property int closingIndex: -1
189262
190− property bool focusChanging: false
263+ // <FIXME-contentX> Workaround Flickable's behavior of bringing contentX back between valid boundaries
264+ // when resized. The proper way to fix this is refactoring PhoneStage so that it doesn't
265+ // rely on having Flickable.contentX keeping an out-of-bounds value when it's set programatically
266+ // (as opposed to having contentX reaching an out-of-bounds value through dragging, which will trigger
267+ // the Flickable.boundsBehavior upon release).
268+ onContentXChanged: { forceItToRemainStillIfBeingResized(); }
269+ onShiftChanged: { forceItToRemainStillIfBeingResized(); }
270+ function forceItToRemainStillIfBeingResized() {
271+ if (root.beingResized && contentX != -spreadView.shift) {
272+ contentX = -spreadView.shift;
273+ }
274+ }
191275
192276 onShiftedContentXChanged: {
277+ if (root.beingResized) {
278+ // Flickabe.contentX wiggles during resizes. Don't react to it.
279+ return;
280+ }
281+
193282 switch (phase) {
194283 case 0:
195284 if (shiftedContentX > width * positionMarker2) {
@@ -204,6 +293,7 @@ Rectangle {
204293 }
205294 break;
206295 }
296+ fullyShowingFocusedAppUpdateTimer.restart();
207297 }
208298
209299 function snap() {
@@ -228,7 +318,7 @@ Rectangle {
228318 snapAnimation.start();
229319 return;
230320 }
231− if (applicationManager.count <= index) {
321+ if (root.applicationManager.count <= index) {
232322 // In case we're trying to snap to some non existing app, lets snap back to the first one
233323 index = 0;
234324 }
@@ -265,7 +355,7 @@ Rectangle {
265355 ScriptAction {
266356 script: {
267357 if (spreadView.selectedIndex >= 0) {
268− applicationManager.focusApplication(applicationManager.get(spreadView.selectedIndex).appId);
358+ root.applicationManager.focusApplication(root.applicationManager.get(spreadView.selectedIndex).appId);
269359
270360 spreadView.selectedIndex = -1;
271361 spreadView.phase = 0;
@@ -280,7 +370,7 @@ Rectangle {
280370 // This width controls how much the spread can be flicked left/right. It's composed of:
281371 // tileDistance * app count (with a minimum of 3 apps, in order to also allow moving 1 and 2 apps a bit)
282372 // + some constant value (still scales with the screen width) which looks good and somewhat fills the screen
283− width: Math.max(3, applicationManager.count) * spreadView.tileDistance + (spreadView.width - spreadView.tileDistance) * 1.5
373+ width: Math.max(3, root.applicationManager.count) * spreadView.tileDistance + (spreadView.width - spreadView.tileDistance) * 1.5
284374 height: parent.height
285375 Behavior on width {
286376 enabled: spreadView.closingIndex >= 0
@@ -303,7 +393,7 @@ Rectangle {
303393 Repeater {
304394 id: spreadRepeater
305395 objectName: "spreadRepeater"
306− model: applicationManager
396+ model: root.applicationManager
307397 delegate: TransformedSpreadDelegate {
308398 id: appDelegate
309399 objectName: "appDelegate" + index
@@ -318,11 +408,10 @@ Rectangle {
318408 selected: spreadView.selectedIndex == index
319409 otherSelected: spreadView.selectedIndex >= 0 && !selected
320410 interactive: !spreadView.interactive && spreadView.phase === 0
321− && spreadView.shiftedContentX === 0 && root.interactive && isFocused
411+ && priv.fullyShowingFocusedApp && root.interactive && isFocused
322412 swipeToCloseEnabled: spreadView.interactive && root.interactive && !snapAnimation.running
323413 maximizedAppTopMargin: root.maximizedAppTopMargin
324− dropShadow: spreadView.active ||
325− (priv.focusedAppDelegate && priv.focusedAppDelegate.x !== 0)
414+ dropShadow: spreadView.active || priv.focusedAppDelegateIsDislocated
326415 focusFirstApp: root.focusFirstApp
327416
328417 readonly property bool isDash: model.appId == "unity8-dash"
@@ -345,7 +434,7 @@ Rectangle {
345434 return spreadView.width + spreadIndex * spreadView.tileDistance;
346435 }
347436
348− application: applicationManager.get(index)
437+ application: root.applicationManager.get(index)
349438 closeable: !isDash
350439
351440 property real behavioredIndex: index
@@ -361,18 +450,16 @@ Rectangle {
361450 }
362451 }
363452
453+ property var xBehavior: xBehavior
364454 Behavior on x {
455+ id: xBehavior
365456 enabled: root.spreadEnabled &&
366457 !spreadView.active &&
367458 !snapAnimation.running &&
368− priv.animateX
459+ priv.animateX &&
460+ !root.beingResized
369461 UbuntuNumberAnimation {
370462 duration: UbuntuAnimation.BriskDuration
371− onRunningChanged: {
372− if (!running && root.inverseProgress == 0) {
373− spreadView.focusChanging = false;
374− }
375− }
376463 }
377464 }
378465
@@ -412,22 +499,21 @@ Rectangle {
412499 }
413500
414501 // Hiding tiles when their progress is negative or reached the maximum
415− visible: (progress >= 0 && progress < 1.7) ||
416− (isDash && priv.focusedAppDelegate.x !== 0)
502+ visible: (progress >= 0 && progress < 1.7)
503+ || (isDash && priv.focusedAppDelegateIsDislocated)
417504
418− Binding {
419− target: appDelegate
420− property: "orientation"
421− when: appDelegate.interactive
422− value: root.orientation
423− }
505+
506+ shellOrientationAngle: root.shellOrientationAngle
507+ shellOrientation: root.shellOrientation
508+ shellPrimaryOrientation: root.shellPrimaryOrientation
509+ nativeOrientation: root.nativeOrientation
424510
425511 onClicked: {
426512 if (root.altTabEnabled && spreadView.phase == 2) {
427− if (applicationManager.focusedApplicationId == applicationManager.get(index).appId) {
513+ if (root.applicationManager.focusedApplicationId == root.applicationManager.get(index).appId) {
428514 spreadView.snapTo(index);
429515 } else {
430− applicationManager.requestFocusApplication(applicationManager.get(index).appId);
516+ root.applicationManager.requestFocusApplication(root.applicationManager.get(index).appId);
431517 }
432518 }
433519 }
@@ -442,13 +528,33 @@ Rectangle {
442528
443529 onClosed: {
444530 spreadView.closingIndex = index;
445− applicationManager.stopApplication(applicationManager.get(index).appId);
531+ root.applicationManager.stopApplication(root.applicationManager.get(index).appId);
532+ }
533+
534+ Binding {
535+ target: root
536+ when: index == 0
537+ property: "mainAppWindowOrientationAngle"
538+ value: appWindowOrientationAngle
539+ }
540+ Binding {
541+ target: priv
542+ when: index == 0
543+ property: "focusedAppOrientationChangesEnabled"
544+ value: orientationChangesEnabled
446545 }
447546 }
448547 }
449548 }
450549 }
451550
551+ //eat touch events during the right edge gesture
552+ MouseArea {
553+ objectName: "eventEaterArea"
554+ anchors.fill: parent
555+ enabled: spreadDragArea.dragging
556+ }
557+
452558 DirectionalDragArea {
453559 id: spreadDragArea
454560 objectName: "spreadDragArea"
qml/Stages/SessionContainer.qml+2−8
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2014 Canonical Ltd.
2+ * Copyright 2014-2015 Canonical Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU Lesser General Public License as published by
@@ -24,14 +24,13 @@ FocusScope {
2424 readonly property var childSessions: session ? session.childSessions : null
2525 readonly property alias surface: _surfaceContainer.surface
2626 property alias interactive: _surfaceContainer.interactive
27− property int orientation
27+ property alias surfaceOrientationAngle: _surfaceContainer.surfaceOrientationAngle
2828
2929 readonly property alias surfaceContainer: _surfaceContainer
3030 SurfaceContainer {
3131 id: _surfaceContainer
3232 anchors.fill: parent
3333 surface: session ? session.surface : null
34− orientation: root.orientation
3534 }
3635
3736 Repeater {
@@ -72,11 +71,6 @@ FocusScope {
7271 target: item; when: item
7372 property: "height"; value: root.height
7473 }
75−
76− Binding {
77− target: item; when: item
78− property: "orientation"; value: root.orientation
79− }
8074 }
8175 }
8276
qml/Stages/ShimStage.qmlnew file+44
@@ -0,0 +1,44 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.3
18+import Ubuntu.Components 0.1
19+
20+Rectangle {
21+ id: shimStage
22+
23+ anchors.fill: parent
24+ color: UbuntuColors.lightAubergine
25+
26+ Text {
27+ id: greeterModeText
28+
29+ anchors.horizontalCenter: parent.horizontalCenter
30+ anchors.top: parent.top
31+ anchors.topMargin: units.gu(10)
32+ font.bold: true
33+ horizontalAlignment: Text.AlignHCenter
34+ text: "Shell is in \"greeter\" mode"
35+ }
36+
37+ Text {
38+ anchors.centerIn: parent
39+ color: UbuntuColors.orange
40+ font.pointSize: units.gu(8)
41+ horizontalAlignment: Text.AlignHCenter
42+ text: "Shim \nStage"
43+ }
44+}
qml/Stages/SpreadDelegate.qml+261−20
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2014 Canonical Ltd.
2+ * Copyright 2014-2015 Canonical Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU Lesser General Public License as published by
@@ -18,6 +18,7 @@
1818 */
1919
2020import QtQuick 2.0
21+import QtQuick.Window 2.0
2122import Ubuntu.Components 1.1
2223import "../Components"
2324
@@ -28,6 +29,9 @@ FocusScope {
2829 readonly property bool dragged: dragArea.moving
2930 signal clicked()
3031 signal closed()
32+ readonly property alias appWindowOrientationAngle: appWindowWithShadow.orientationAngle
33+ readonly property alias appWindowRotation: appWindowWithShadow.rotation
34+ readonly property alias orientationChangesEnabled: appWindow.orientationChangesEnabled
3135
3236 // to be set from outside
3337 property bool interactive: true
@@ -36,10 +40,54 @@ FocusScope {
3640 property alias swipeToCloseEnabled: dragArea.enabled
3741 property bool closeable
3842 property alias application: appWindow.application
39− property int orientation
43+ property int shellOrientationAngle
44+ property int shellOrientation
45+ property int shellPrimaryOrientation
46+ property int nativeOrientation
47+
48+ function matchShellOrientation() {
49+ if (!root.application)
50+ return;
51+ appWindowWithShadow.orientationAngle = root.shellOrientationAngle;
52+ }
53+
54+ function animateToShellOrientation() {
55+ if (!root.application)
56+ return;
57+
58+ if (root.application.rotatesWindowContents) {
59+ appWindowWithShadow.orientationAngle = root.shellOrientationAngle;
60+ } else {
61+ orientationChangeAnimation.start();
62+ }
63+ }
64+
65+ OrientationChangeAnimation {
66+ id: orientationChangeAnimation
67+ objectName: "orientationChangeAnimation"
68+ spreadDelegate: root
69+ background: background
70+ window: appWindowWithShadow
71+ screenshot: appWindowScreenshotWithShadow
72+ }
73+
74+ QtObject {
75+ id: priv
76+ property bool startingUp: true
77+ }
78+
79+ Component.onCompleted: { finishStartUpTimer.start(); }
80+ Timer { id: finishStartUpTimer; interval: 400; onTriggered: priv.startingUp = false }
81+
82+ Rectangle {
83+ id: background
84+ color: "black"
85+ anchors.fill: parent
86+ visible: false
87+ }
4088
4189 Item {
42− objectName: "appWindowWithShadow"
90+ objectName: "displacedAppWindowWithShadow"
4391
4492 readonly property real limit: root.height / 4
4593
@@ -52,27 +100,220 @@ FocusScope {
52100 return k * (1 - Math.pow((k - 1) / k, distance))
53101 }
54102
55− BorderImage {
56− anchors {
57− fill: appWindow
58− margins: -units.gu(2)
103+ Item {
104+ id: appWindowWithShadow
105+ objectName: "appWindowWithShadow"
106+
107+ property int orientationAngle
108+
109+ property real transformRotationAngle: 0
110+ property real transformOriginX
111+ property real transformOriginY
112+
113+ property var window: appWindow
114+
115+ transform: Rotation {
116+ origin.x: appWindowWithShadow.transformOriginX
117+ origin.y: appWindowWithShadow.transformOriginY
118+ axis { x: 0; y: 0; z: 1 }
119+ angle: appWindowWithShadow.transformRotationAngle
120+ }
121+
122+ state: {
123+ if (priv.startingUp) {
124+ return "startingUp";
125+ } else if (root.application && root.application.rotatesWindowContents) {
126+ return "counterRotate";
127+ } else if (orientationChangeAnimation.running) {
128+ return "animatingRotation";
129+ } else {
130+ return "keepSceneRotation";
131+ }
59132 }
60− source: "graphics/dropshadow2gu.sci"
61− opacity: root.dropShadow ? .3 : 0
62− Behavior on opacity { UbuntuNumberAnimation {} }
63− }
64133
65− ApplicationWindow {
66− id: appWindow
67− objectName: application ? "appWindow_" + application.appId : "appWindow_null"
68− focus: true
69− anchors {
70− fill: parent
71− topMargin: appWindow.fullscreen ? 0 : maximizedAppTopMargin
134+ // Ensures the given angle is in the form (0,90,180,270)
135+ function normalizeAngle(angle) {
136+ while (angle < 0) {
137+ angle += 360;
138+ }
139+ return angle % 360;
72140 }
73141
74− interactive: root.interactive
75− orientation: root.orientation
142+ states: [
143+ // Sets the initial orientationAngle of the window, when it first slides into view
144+ // (with the splash screen likely being displayed). At that point we just try to
145+ // match shell's current orientation. We need a bit of time in this state as the
146+ // information we need to decide orientationAngle may take a few cycles to
147+ // be set.
148+ State {
149+ name: "startingUp"
150+ PropertyChanges {
151+ target: appWindowWithShadow
152+ restoreEntryValues: false
153+ orientationAngle: {
154+ if (!root.application || root.application.rotatesWindowContents) {
155+ return 0;
156+ }
157+ var supportedOrientations = root.application.supportedOrientations;
158+
159+ if (supportedOrientations === Qt.PrimaryOrientation) {
160+ supportedOrientations = root.shellPrimaryOrientation;
161+ }
162+
163+ // If it doesn't support shell's current orientation
164+ // then simply pick some arbitraty one that it does support
165+ var chosenOrientation = 0;
166+ if (supportedOrientations & root.shellOrientation) {
167+ chosenOrientation = root.shellOrientation;
168+ } else if (supportedOrientations & Qt.PortraitOrientation) {
169+ chosenOrientation = Qt.PortraitOrientation;
170+ } else if (supportedOrientations & Qt.LandscapeOrientation) {
171+ chosenOrientation = Qt.LandscapeOrientation;
172+ } else if (supportedOrientations & Qt.InvertedPortraitOrientation) {
173+ chosenOrientation = Qt.InvertedPortraitOrientation;
174+ } else if (supportedOrientations & Qt.InvertedLandscapeOrientation) {
175+ chosenOrientation = Qt.InvertedLandscapeOrientation;
176+ } else {
177+ chosenOrientation = root.shellPrimaryOrientation;
178+ }
179+
180+ return Screen.angleBetween(root.nativeOrientation, chosenOrientation);
181+ }
182+
183+ rotation: normalizeAngle(appWindowWithShadow.orientationAngle - root.shellOrientationAngle)
184+ width: {
185+ if (rotation == 0 || rotation == 180) {
186+ return root.width;
187+ } else {
188+ return root.height;
189+ }
190+ }
191+ height: {
192+ if (rotation == 0 || rotation == 180)
193+ return root.height;
194+ else
195+ return root.width;
196+ }
197+ }
198+ },
199+ // In this state we stick to our currently set orientationAngle, which may change only due
200+ // to calls made to matchShellOrientation() or animateToShellOrientation()
201+ State {
202+ id: keepSceneRotationState
203+ name: "keepSceneRotation"
204+
205+ StateChangeScript { script: {
206+ // break binding
207+ appWindowWithShadow.orientationAngle = appWindowWithShadow.orientationAngle;
208+ } }
209+ PropertyChanges {
210+ target: appWindowWithShadow
211+ restoreEntryValues: false
212+ rotation: normalizeAngle(appWindowWithShadow.orientationAngle - root.shellOrientationAngle)
213+ width: {
214+ if (rotation == 0 || rotation == 180) {
215+ return root.width;
216+ } else {
217+ return root.height;
218+ }
219+ }
220+ height: {
221+ if (rotation == 0 || rotation == 180)
222+ return root.height;
223+ else
224+ return root.width;
225+ }
226+ }
227+ },
228+ // In this state we counteract any shell rotation so that the window, in scene coordinates,
229+ // remains unrotated.
230+ State {
231+ name: "counterRotate"
232+ StateChangeScript { script: {
233+ // break binding
234+ appWindowWithShadow.orientationAngle = appWindowWithShadow.orientationAngle;
235+ } }
236+ PropertyChanges {
237+ target: appWindowWithShadow
238+ width: root.shellOrientationAngle == 0 || root.shellOrientationAngle == 180 ? root.width : root.height
239+ height: root.shellOrientationAngle == 0 || root.shellOrientationAngle == 180 ? root.height : root.width
240+ rotation: normalizeAngle(-root.shellOrientationAngle)
241+ }
242+ PropertyChanges {
243+ target: appWindow
244+ surfaceOrientationAngle: orientationAngle
245+ }
246+ },
247+ State {
248+ name: "animatingRotation"
249+ }
250+ ]
251+
252+ x: (parent.width - width) / 2
253+ y: (parent.height - height) / 2
254+
255+ BorderImage {
256+ anchors {
257+ fill: appWindow
258+ margins: -units.gu(2)
259+ }
260+ source: "graphics/dropshadow2gu.sci"
261+ opacity: root.dropShadow ? .3 : 0
262+ Behavior on opacity { UbuntuNumberAnimation {} }
263+ }
264+
265+ ApplicationWindow {
266+ id: appWindow
267+ objectName: application ? "appWindow_" + application.appId : "appWindow_null"
268+ focus: true
269+ anchors {
270+ fill: parent
271+ topMargin: appWindow.fullscreen || application.rotatesWindowContents
272+ ? 0 : maximizedAppTopMargin
273+ }
274+
275+ interactive: root.interactive
276+ }
277+ }
278+ }
279+
280+ Item {
281+ // mimics appWindowWithShadow. Do the positioning of screenshots of non-fullscreen
282+ // app windows
283+ id: appWindowScreenshotWithShadow
284+ visible: false
285+
286+ property real transformRotationAngle: 0
287+ property real transformOriginX
288+ property real transformOriginY
289+
290+ transform: Rotation {
291+ origin.x: appWindowScreenshotWithShadow.transformOriginX
292+ origin.y: appWindowScreenshotWithShadow.transformOriginY
293+ axis { x: 0; y: 0; z: 1 }
294+ angle: appWindowScreenshotWithShadow.transformRotationAngle
295+ }
296+
297+ property var window: appWindowScreenshot
298+
299+ function take() {
300+ // Format: "image://application/$APP_ID/$CURRENT_TIME_MS"
301+ // eg: "image://application/calculator-app/123456"
302+ var timeMs = new Date().getTime();
303+ appWindowScreenshot.source = "image://application/" + root.application.appId + "/" + timeMs;
304+ }
305+ function discard() {
306+ appWindowScreenshot.source = "";
307+ }
308+
309+ Image {
310+ id: appWindowScreenshot
311+ source: ""
312+
313+ anchors.fill: parent
314+
315+ sourceSize.width: width
316+ sourceSize.height: height
76317 }
77318 }
78319
qml/Stages/SurfaceContainer.qml+2−2
@@ -24,8 +24,8 @@ FocusScope {
2424 objectName: "surfaceContainer"
2525 property Item surface: null
2626 property bool hadSurface: false
27− property int orientation
2827 property bool interactive
28+ property int surfaceOrientationAngle: 0
2929
3030 onSurfaceChanged: {
3131 if (surface) {
@@ -39,10 +39,10 @@ FocusScope {
3939 }
4040 }
4141 Binding { target: surface; property: "anchors.fill"; value: root }
42− Binding { target: surface; property: "orientation"; value: root.orientation }
4342 Binding { target: surface; property: "z"; value: 1 }
4443 Binding { target: surface; property: "enabled"; value: root.interactive; when: surface }
4544 Binding { target: surface; property: "antialiasing"; value: !root.interactive; when: surface }
45+ Binding { target: surface; property: "orientationAngle"; value: root.surfaceOrientationAngle; when: surface }
4646
4747 InputWatcher {
4848 target: root.surface
qml/Stages/TabletStage.qml+155−19
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -28,13 +28,79 @@ Rectangle {
2828 color: "#111111"
2929
3030 // Controls to be set from outside
31− property bool shown: false
32− property bool moving: false
3331 property int dragAreaWidth
3432 property real maximizedAppTopMargin
3533 property bool interactive
34+ property alias beingResized: spreadView.beingResized
35+
36+ property bool spreadEnabled: true // If false, animations and right edge will be disabled
37+
3638 property real inverseProgress: 0 // This is the progress for left edge drags, in pixels.
37− property int orientation: Qt.PortraitOrientation
39+ property int shellOrientationAngle: 0
40+ property int shellOrientation
41+ property int shellPrimaryOrientation
42+ property int nativeOrientation
43+ property real nativeWidth
44+ property real nativeHeight
45+ function updateFocusedAppOrientation() {
46+ var mainStageAppIndex = priv.indexOf(priv.mainStageAppId);
47+ if (mainStageAppIndex >= 0 && mainStageAppIndex < spreadRepeater.count) {
48+ spreadRepeater.itemAt(mainStageAppIndex).matchShellOrientation();
49+ }
50+
51+ for (var i = 0; i < spreadRepeater.count; ++i) {
52+
53+ if (i === mainStageAppIndex) {
54+ continue;
55+ }
56+
57+ var spreadDelegate = spreadRepeater.itemAt(i);
58+
59+ var delta = spreadDelegate.appWindowOrientationAngle - root.shellOrientationAngle;
60+ if (delta < 0) { delta += 360; }
61+ delta = delta % 360;
62+
63+ var supportedOrientations = spreadDelegate.application.supportedOrientations;
64+ if (supportedOrientations === Qt.PrimaryOrientation) {
65+ supportedOrientations = spreadDelegate.shellPrimaryOrientation;
66+ }
67+
68+ if (delta === 180 && (supportedOrientations & spreadDelegate.shellOrientation)) {
69+ spreadDelegate.matchShellOrientation();
70+ }
71+ }
72+ }
73+ function updateFocusedAppOrientationAnimated() {
74+ var mainStageAppIndex = priv.indexOf(priv.mainStageAppId);
75+ if (mainStageAppIndex >= 0 && mainStageAppIndex < spreadRepeater.count) {
76+ spreadRepeater.itemAt(mainStageAppIndex).animateToShellOrientation();
77+ }
78+
79+ if (priv.sideStageAppId) {
80+ var sideStageAppIndex = priv.indexOf(priv.sideStageAppId);
81+ if (sideStageAppIndex >= 0 && sideStageAppIndex < spreadRepeater.count) {
82+ spreadRepeater.itemAt(sideStageAppIndex).matchShellOrientation();
83+ }
84+ }
85+ }
86+
87+ // To be read from outside
88+ property var mainApp: null
89+ property int mainAppWindowOrientationAngle: 0
90+ readonly property bool orientationChangesEnabled: priv.mainAppOrientationChangesEnabled
91+
92+ onWidthChanged: {
93+ spreadView.selectedIndex = -1;
94+ spreadView.phase = 0;
95+ spreadView.contentX = -spreadView.shift;
96+ }
97+
98+ onShellOrientationChanged: {
99+ if (shellOrientation == Qt.PortraitOrientation || shellOrientation == Qt.InvertedPortraitOrientation) {
100+ ApplicationManager.focusApplication(priv.mainStageAppId);
101+ priv.sideStageAppId = "";
102+ }
103+ }
38104
39105 onInverseProgressChanged: {
40106 // This can't be a simple binding because that would be triggered after this handler
@@ -59,6 +125,13 @@ Rectangle {
59125 }
60126
61127 property string oldFocusedAppId: ""
128+ property bool mainAppOrientationChangesEnabled: false
129+
130+ property real landscapeHeight: root.nativeOrientation == Qt.LandscapeOrientation ?
131+ root.nativeHeight : root.nativeWidth
132+
133+ property bool shellIsLandscape: root.shellOrientation === Qt.LandscapeOrientation
134+ || root.shellOrientation === Qt.InvertedLandscapeOrientation
62135
63136 property string mainStageAppId
64137 property string sideStageAppId
@@ -76,6 +149,7 @@ Rectangle {
76149 priv.sideStageAppId = focusedAppId;
77150 } else {
78151 priv.mainStageAppId = focusedAppId;
152+ root.mainApp = focusedApp;
79153 }
80154 }
81155
@@ -166,6 +240,7 @@ Rectangle {
166240
167241 Flickable {
168242 id: spreadView
243+ objectName: "spreadView"
169244 anchors.fill: parent
170245 interactive: (spreadDragArea.dragging || phase > 1) && draggedDelegateCount === 0
171246 contentWidth: spreadRow.width - shift
@@ -214,7 +289,25 @@ Rectangle {
214289 property int draggedDelegateCount: 0
215290 property int closingIndex: -1
216291
292+ // FIXME: Workaround Flickable's not keepping its contentX still when resized
293+ onContentXChanged: { forceItToRemainStillIfBeingResized(); }
294+ onShiftChanged: { forceItToRemainStillIfBeingResized(); }
295+ function forceItToRemainStillIfBeingResized() {
296+ if (root.beingResized && contentX != -shift) {
297+ contentX = -shift;
298+ }
299+ }
300+
217301 property bool animateX: true
302+ property bool beingResized: false
303+ onBeingResizedChanged: {
304+ if (beingResized) {
305+ // Brace yourselves for impact!
306+ selectedIndex = -1;
307+ phase = 0;
308+ contentX = -shift;
309+ }
310+ }
218311
219312 property bool sideStageDragging: sideStageDragHandle.dragging
220313 property real sideStageDragProgress: sideStageDragHandle.progress
@@ -253,7 +346,6 @@ Rectangle {
253346 case "overlay":
254347 return 1;
255348 }
256− print("Unhandled nextInStack case! This shouldn't happen any more when the Dash is an app!");
257349 return -1;
258350 }
259351 property int nextZInStack: indexToZIndex(nextInStack)
@@ -289,6 +381,10 @@ Rectangle {
289381 }
290382
291383 onShiftedContentXChanged: {
384+ if (root.beingResized) {
385+ // Flickabe.contentX wiggles during resizes. Don't react to it.
386+ return;
387+ }
292388 if (spreadView.phase == 0 && spreadView.shiftedContentX > spreadView.width * spreadView.positionMarker2) {
293389 spreadView.phase = 1;
294390 } else if (spreadView.phase == 1 && spreadView.shiftedContentX > spreadView.width * spreadView.positionMarker4) {
@@ -398,8 +494,8 @@ Rectangle {
398494 MouseArea {
399495 id: spreadRow
400496 x: spreadView.contentX
401− height: root.height
402497 width: spreadView.width + Math.max(spreadView.width, ApplicationManager.count * spreadView.tileDistance)
498+ height: root.height
403499
404500 onClicked: {
405501 spreadView.snapTo(0);
@@ -408,8 +504,9 @@ Rectangle {
408504 Rectangle {
409505 id: sideStageBackground
410506 color: "black"
411− anchors.fill: parent
412− anchors.leftMargin: spreadView.width - (1 - sideStageDragHandle.progress) * spreadView.sideStageWidth
507+ width: spreadView.sideStageWidth * (1 - sideStageDragHandle.progress)
508+ height: priv.landscapeHeight
509+ x: spreadView.width - width
413510 z: spreadView.indexToZIndex(priv.indexOf(priv.sideStageAppId))
414511 opacity: spreadView.phase == 0 ? 1 : 0
415512 Behavior on opacity { UbuntuNumberAnimation {} }
@@ -417,8 +514,10 @@ Rectangle {
417514
418515 Item {
419516 id: sideStageDragHandle
420− anchors { top: parent.top; bottom: parent.bottom; left: parent.left; leftMargin: spreadView.width - spreadView.sideStageWidth - width }
517+ anchors.right: sideStageBackground.left
518+ anchors.top: sideStageBackground.top
421519 width: units.gu(2)
520+ height: priv.landscapeHeight
422521 z: sideStageBackground.z
423522 opacity: spreadView.phase <= 0 && spreadView.sideStageVisible ? 1 : 0
424523 property real progress: 0
@@ -437,7 +536,6 @@ Rectangle {
437536
438537 Image {
439538 anchors.centerIn: parent
440− anchors.horizontalCenterOffset: parent.progress * spreadView.sideStageWidth - (width - parent.width) / 2
441539 width: sideStageDragHandleMouseArea.pressed ? parent.width * 2 : parent.width
442540 height: parent.height
443541 source: "graphics/sidestage_handle@20.png"
@@ -450,16 +548,19 @@ Rectangle {
450548 enabled: spreadView.shiftedContentX == 0
451549 property int startX
452550 property var gesturePoints: new Array()
551+ property real totalDiff
453552
454553 onPressed: {
455554 gesturePoints = [];
456555 startX = mouseX;
556+ totalDiff = 0.0;
457557 sideStageDragHandle.progress = 0;
458558 sideStageDragHandle.dragging = true;
459559 }
460560 onMouseXChanged: {
561+ totalDiff += mouseX - startX;
461562 if (priv.mainStageAppId) {
462− sideStageDragHandle.progress = Math.max(0, (-startX + mouseX) / spreadView.sideStageWidth);
563+ sideStageDragHandle.progress = Math.max(0, totalDiff / spreadView.sideStageWidth);
463564 }
464565 gesturePoints.push(mouseX);
465566 }
@@ -488,14 +589,27 @@ Rectangle {
488589
489590 Repeater {
490591 id: spreadRepeater
592+ objectName: "spreadRepeater"
491593 model: ApplicationManager
492594
493595 delegate: TransformedTabletSpreadDelegate {
494596 id: spreadTile
495597 objectName: model.appId ? "tabletSpreadDelegate_" + model.appId
496598 : "tabletSpreadDelegate_null";
497− height: spreadView.height
498− width: model.stage == ApplicationInfoInterface.MainStage ? spreadView.width : spreadView.sideStageWidth
599+ width: {
600+ if (wantsMainStage) {
601+ return spreadView.width;
602+ } else {
603+ return spreadView.sideStageWidth;
604+ }
605+ }
606+ height: {
607+ if (wantsMainStage) {
608+ return spreadView.height;
609+ } else {
610+ return priv.landscapeHeight;
611+ }
612+ }
499613 active: model.appId == priv.mainStageAppId || model.appId == priv.sideStageAppId
500614 zIndex: spreadView.indexToZIndex(index)
501615 selected: spreadView.selectedIndex == index
@@ -508,6 +622,8 @@ Rectangle {
508622 application: ApplicationManager.get(index)
509623 closeable: !isDash
510624
625+ readonly property bool wantsMainStage: model.stage == ApplicationInfoInterface.MainStage
626+
511627 readonly property bool isDash: model.appId == "unity8-dash"
512628
513629 // FIXME: A regular binding doesn't update any more after closing an app.
@@ -559,12 +675,11 @@ Rectangle {
559675 return progress;
560676 }
561677
562− Binding {
563− target: spreadTile
564− property: "orientation"
565− when: spreadTile.interactive
566− value: root.orientation
567− }
678+ shellOrientationAngle: wantsMainStage ? root.shellOrientationAngle : 0
679+ shellOrientation: wantsMainStage ? root.shellOrientation : Qt.PortraitOrientation
680+ shellPrimaryOrientation: wantsMainStage ? root.shellPrimaryOrientation : Qt.PortraitOrientation
681+ nativeOrientation: wantsMainStage ? root.nativeOrientation : Qt.PortraitOrientation
682+
568683
569684 onClicked: {
570685 if (spreadView.phase == 2) {
@@ -585,6 +700,19 @@ Rectangle {
585700 ApplicationManager.stopApplication(ApplicationManager.get(index).appId);
586701 }
587702
703+ Binding {
704+ target: root
705+ when: model.appId == priv.mainStageAppId
706+ property: "mainAppWindowOrientationAngle"
707+ value: appWindowOrientationAngle
708+ }
709+ Binding {
710+ target: priv
711+ when: model.appId == priv.mainStageAppId
712+ property: "mainAppOrientationChangesEnabled"
713+ value: orientationChangesEnabled
714+ }
715+
588716 EasingCurve {
589717 id: snappingCurve
590718 type: EasingCurve.Linear
@@ -596,11 +724,19 @@ Rectangle {
596724 }
597725 }
598726
727+ //eat touch events during the right edge gesture
728+ MouseArea {
729+ anchors.fill: parent
730+ enabled: spreadDragArea.dragging
731+ }
732+
599733 DirectionalDragArea {
600734 id: spreadDragArea
735+ objectName: "spreadDragArea"
601736 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
602737 width: root.dragAreaWidth
603738 direction: Direction.Leftwards
739+ enabled: (spreadView.phase != 2 && root.spreadEnabled) || dragging
604740
605741 property var gesturePoints: new Array()
606742
qml/Stages/TransformedSpreadDelegate.qml+50−6
@@ -298,7 +298,6 @@ SpreadDelegate {
298298
299299 return easingCurve.value;
300300 }
301−
302301 }
303302
304303 transform: [
@@ -307,16 +306,61 @@ SpreadDelegate {
307306 axis { x: 0; y: 1; z: 0 }
308307 angle: priv.angle
309308 },
309+
310+ // The next two transformations are to ensure that fullscreen and rotated
311+ // windows all have the same size and position as an unrotated (0 degrees)
312+ // non-fullscreen window when they're stacked on top of each other on the
313+ // far left of the spread.
314+ Translate {
315+ y: !fullscreen && appWindowRotation === 180
316+ ? priv.topMarginProgress * maximizedAppTopMargin
317+ : 0
318+ },
319+ Scale {
320+ origin {
321+ x: appWindowRotation === 270 ? spreadView.width : 0
322+ y: spreadView.height
323+ }
324+
325+ xScale: {
326+ switch (appWindowRotation) {
327+ case 90:
328+ case 270:
329+ if (fullscreen) {
330+ return 1;
331+ } else {
332+ return 1 + priv.topMarginProgress * maximizedAppTopMargin / spreadView.width;
333+ }
334+ break;
335+ default:
336+ return 1;
337+ }
338+ }
339+
340+ yScale: {
341+ switch (appWindowRotation) {
342+ case 0:
343+ if (fullscreen) {
344+ return 1 - priv.topMarginProgress * maximizedAppTopMargin / spreadView.height;
345+ } else {
346+ return 1;
347+ }
348+ break;
349+ case 90:
350+ case 270:
351+ return 1 - priv.topMarginProgress * maximizedAppTopMargin / spreadView.height;
352+ break;
353+ default:
354+ return 1;
355+ }
356+ }
357+ },
358+
310359 Scale {
311360 origin { x: 0; y: spreadView.height / 2 }
312361 xScale: priv.scale
313362 yScale: xScale
314363 },
315− Scale {
316− origin { x: 0; y: (spreadView.height * priv.scale) + maximizedAppTopMargin * 3 }
317− xScale: 1
318− yScale: fullscreen ? 1 - priv.topMarginProgress * maximizedAppTopMargin / spreadView.height : 1
319− },
320364 Translate {
321365 x: priv.xTranslate
322366 }
qml/Stages/TransformedTabletSpreadDelegate.qml+5−2
@@ -50,7 +50,9 @@ SpreadDelegate {
5050 property int dragOffset: 0
5151
5252 dropShadow: spreadView.active ||
53− (active && model.stage == ApplicationInfoInterface.MainStage && priv.xTranslate != 0)
53+ (active
54+ && (model.stage == ApplicationInfoInterface.MainStage || !priv.shellIsLandscape)
55+ && priv.xTranslate != 0)
5456
5557 onSelectedChanged: {
5658 if (selected) {
@@ -139,7 +141,8 @@ SpreadDelegate {
139141 !snapAnimation.running &&
140142 model.appId !== "unity8-dash" &&
141143 !spreadView.sideStageDragging &&
142− spreadView.animateX
144+ spreadView.animateX &&
145+ !spreadView.beingResized
143146 UbuntuNumberAnimation {
144147 duration: UbuntuAnimation.FastDuration
145148 }
run.sh+1−1
@@ -44,7 +44,7 @@ else
4444 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/builddir/plugins/LightDM/liblightdm
4545fi
4646
47−QML_PHONE_SHELL_ARGS=""
47+QML_PHONE_SHELL_ARGS="-windowgeometry=40gux68gu -devicename=mako"
4848if $MOUSE_TOUCH; then
4949 QML_PHONE_SHELL_ARGS="$QML_PHONE_SHELL_ARGS -mousetouch"
5050fi
runtests.sh+1−1
@@ -41,7 +41,7 @@ sleep 1
4141
4242cd -
4343
44−make -k qmltests
44+make -k xvfballtests
4545if [ $? -ne 0 ]; then
4646 echo '<?xml version="1.0" encoding="UTF-8" ?><testsuite errors="0" failures="1" tests="1" name="makeExitStatusTest"><properties/><testcase result="fail" name="makeExitStatus"><failure message="Make test did not suceed" result="fail"/></testcase><system-err/></testsuite>' > testMakeExitStatus.xml
4747fi
src/ApplicationArguments.cppnew file+22
@@ -0,0 +1,22 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "ApplicationArguments.h"
18+
19+ApplicationArguments::ApplicationArguments(QObject *parent)
20+ : QObject(parent)
21+{
22+}
src/ApplicationArguments.h+7−11
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013,2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -13,7 +13,6 @@
1313 * You should have received a copy of the GNU General Public License
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1515 *
16− * Authored by: Nick Dedekind <nick.dedekind@canonical.com>
1716 */
1817
1918
@@ -22,23 +21,20 @@
2221
2322#include <QObject>
2423#include <QSize>
25−#include <QStringList>
24+#include <QString>
2625
2726class ApplicationArguments : public QObject
2827{
2928 Q_OBJECT
29+ Q_PROPERTY(QString deviceName READ deviceName CONSTANT)
3030public:
31− // Not exposed to the app as setSize isn't invokable
32− void setSize(const QSize &size) {
33− m_size = size;
34− }
31+ ApplicationArguments(QObject *parent = nullptr);
3532
36− Q_INVOKABLE bool hasGeometry() const { return m_size.isValid(); }
37− Q_INVOKABLE int width() const { return m_size.width(); }
38− Q_INVOKABLE int height() const { return m_size.height(); }
33+ void setDeviceName(QString deviceName) { m_deviceName = deviceName; }
34+ QString deviceName() const { return m_deviceName; }
3935
4036private:
41− QSize m_size;
37+ QString m_deviceName;
4238};
4339
4440#endif // APPLICATION_ARGUMENTS_H
src/CMakeLists.txt+5−6
@@ -17,7 +17,7 @@ file(GLOB_RECURSE QML_FILES
1717)
1818
1919add_executable(${SHELL_APP}
20− ApplicationArguments.h
20+ ApplicationArguments.cpp
2121 main.cpp
2222 MouseTouchAdaptor.cpp
2323 CachingNetworkManagerFactory.cpp
@@ -26,13 +26,12 @@ add_executable(${SHELL_APP}
2626)
2727
2828qt5_use_modules(${SHELL_APP} Gui Qml Quick Test)
29−pkg_check_modules(XCB REQUIRED xcb)
3029
31−if (NOT "${XCB_INCLUDE_DIRS}" STREQUAL "")
32− set_target_properties(${SHELL_APP} PROPERTIES INCLUDE_DIRECTORIES ${XCB_INCLUDE_DIRS})
33−
34− target_link_libraries(${SHELL_APP} ${XCB_LDFLAGS})
30+pkg_check_modules(NEEDED_LIBS REQUIRED xcb libandroid-properties)
31+if (NOT "${NEEDED_LIBS_INCLUDE_DIRS}" STREQUAL "")
32+ set_target_properties(${SHELL_APP} PROPERTIES INCLUDE_DIRECTORIES ${NEEDED_LIBS_INCLUDE_DIRS})
3533endif()
34+target_link_libraries(${SHELL_APP} ${NEEDED_LIBS_LDFLAGS})
3635
3736target_link_libraries(${SHELL_APP} UbuntuGestures connectivity-qt1)
3837
src/Dash/CMakeLists.txt+7−1
@@ -1,6 +1,6 @@
11set(DASH_SRCS
22 main.cpp
3− ../ApplicationArguments.h
3+ ../ApplicationArguments.cpp
44 ../MouseTouchAdaptor.cpp
55 ../CachingNetworkManagerFactory.cpp
66)
@@ -9,6 +9,12 @@ add_executable(unity8-dash ${DASH_SRCS})
99
1010qt5_use_modules(unity8-dash Gui Qml Quick Test)
1111
12+pkg_check_modules(NEEDED_LIBS REQUIRED xcb libandroid-properties)
13+if (NOT "${NEEDED_LIBS_INCLUDE_DIRS}" STREQUAL "")
14+ set_target_properties(unity8-dash PROPERTIES INCLUDE_DIRECTORIES ${NEEDED_LIBS_INCLUDE_DIRS})
15+endif()
16+target_link_libraries(unity8-dash ${NEEDED_LIBS_LDFLAGS})
17+
1218# For it to find libUbuntuGestures.so, needed by Ubuntu.Gestures QML module.
1319set_target_properties(unity8-dash PROPERTIES INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${SHELL_PRIVATE_LIBDIR}")
1420
src/Dash/main.cpp+12−6
@@ -58,12 +58,6 @@ int main(int argc, const char *argv[])
5858 parser.process(*application);
5959
6060 ApplicationArguments qmlArgs;
61− if (parser.isSet(windowGeometryOption) &&
62− parser.value(windowGeometryOption).split('x').size() == 2)
63− {
64− QStringList geom = parser.value(windowGeometryOption).split('x');
65− qmlArgs.setSize(QSize(geom.at(0).toInt(), geom.at(1).toInt()));
66− }
6761
6862 if (getenv("QT_LOAD_TESTABILITY")) {
6963 QLibrary testLib(QLatin1String("qttestability"));
@@ -85,6 +79,18 @@ int main(int argc, const char *argv[])
8579
8680 QQuickView* view = new QQuickView();
8781 view->setResizeMode(QQuickView::SizeRootObjectToView);
82+
83+ if (parser.isSet(windowGeometryOption) &&
84+ parser.value(windowGeometryOption).split('x').size() == 2)
85+ {
86+ QStringList geom = parser.value(windowGeometryOption).split('x');
87+ QSize windowSize(geom.at(0).toInt(), geom.at(1).toInt());
88+ if (windowSize.isValid()) {
89+ view->setWidth(windowSize.width());
90+ view->setHeight(windowSize.height());
91+ }
92+ }
93+
8894 view->setTitle("Scopes");
8995 view->rootContext()->setContextProperty("applicationArguments", &qmlArgs);
9096
src/UnityCommandLineParser.cpp+5
@@ -49,6 +49,10 @@ UnityCommandLineParser::UnityCommandLineParser(const QCoreApplication &app)
4949 "DISCOURAGED: Please set QT_LOAD_TESTABILITY instead.\nLoad the testability driver");
5050 parser.addOption(testabilityOption);
5151
52+ QCommandLineOption devicenameOption(QStringList() << "devicename",
53+ "Specify the device name instead of letting Unity 8 find it out", "devicename", "");
54+ parser.addOption(devicenameOption);
55+
5256 QCommandLineOption modeOption("mode",
5357 "Whether to run greeter and/or shell [full-greeter, full-shell, greeter, shell]",
5458 "mode", "full-greeter");
@@ -73,6 +77,7 @@ UnityCommandLineParser::UnityCommandLineParser(const QCoreApplication &app)
7377 m_hasFrameless = parser.isSet(framelessOption);
7478 m_hasMouseToTouch = parser.isSet(mousetouchOption);
7579 m_hasFullscreen = parser.isSet(fullscreenOption);
80+ m_deviceName = parser.value(devicenameOption);
7681 resolveMode(parser, modeOption);
7782}
7883
src/UnityCommandLineParser.h+2
@@ -30,6 +30,7 @@ public:
3030 bool hasFrameless() const { return m_hasFrameless; }
3131 bool hasMouseToTouch() const { return m_hasMouseToTouch; }
3232 bool hasFullscreen() const { return m_hasFullscreen; }
33+ QString deviceName() const { return m_deviceName; }
3334 QString mode() const { return m_mode; }
3435private:
3536
@@ -44,6 +45,7 @@ private:
4445 bool m_hasFrameless;
4546 bool m_hasMouseToTouch;
4647 bool m_hasFullscreen;
48+ QString m_deviceName;
4749 QString m_mode;
4850};
4951
src/main.cpp+18−8
@@ -25,6 +25,9 @@
2525#include <csignal>
2626#include <libintl.h>
2727
28+// libandroid-properties
29+#include <hybris/properties/properties.h>
30+
2831// local
2932#include <paths.h>
3033#include "MouseTouchAdaptor.h"
@@ -47,13 +50,15 @@ int main(int argc, const char *argv[])
4750
4851 UnityCommandLineParser parser(*application);
4952
50− QString indicatorProfile = qgetenv("UNITY_INDICATOR_PROFILE");
51− if (indicatorProfile.isEmpty()) {
52− indicatorProfile = "phone";
53− }
54−
5553 ApplicationArguments qmlArgs;
56− qmlArgs.setSize(parser.windowGeometry());
54+
55+ if (!parser.deviceName().isEmpty()) {
56+ qmlArgs.setDeviceName(parser.deviceName());
57+ } else {
58+ char buffer[200];
59+ property_get("ro.product.device", buffer /* value */, "desktop" /* default_value*/);
60+ qmlArgs.setDeviceName(QString(buffer));
61+ }
5762
5863 // The testability driver is only loaded by QApplication but not by QGuiApplication.
5964 // However, QApplication depends on QWidget which would add some unneeded overhead => Let's load the testability driver on our own.
@@ -79,9 +84,14 @@ int main(int argc, const char *argv[])
7984 view->setResizeMode(QQuickView::SizeRootObjectToView);
8085 view->setColor("black");
8186 view->setTitle("Unity8 Shell");
87+
88+ if (parser.windowGeometry().isValid()) {
89+ view->setWidth(parser.windowGeometry().width());
90+ view->setHeight(parser.windowGeometry().height());
91+ }
92+
8293 view->engine()->setBaseUrl(QUrl::fromLocalFile(::qmlDirectory()));
8394 view->rootContext()->setContextProperty("applicationArguments", &qmlArgs);
84− view->rootContext()->setContextProperty("indicatorProfile", indicatorProfile);
8595 view->rootContext()->setContextProperty("shellMode", parser.mode());
8696 if (parser.hasFrameless()) {
8797 view->setFlags(Qt::FramelessWindowHint);
@@ -94,7 +104,7 @@ int main(int argc, const char *argv[])
94104 mouseTouchAdaptor = MouseTouchAdaptor::instance();
95105 }
96106
97− QUrl source(::qmlDirectory()+"Shell.qml");
107+ QUrl source(::qmlDirectory()+"OrientedShell.qml");
98108 prependImportPaths(view->engine(), ::overrideImportPaths());
99109 if (!isMirServer) {
100110 prependImportPaths(view->engine(), ::nonMirImportPaths());
tests/CMakeLists.txt+120−8
@@ -1,16 +1,128 @@
1+include(QmlTest)
2+
13# QML tests that do not require graphical capabilities.
2−add_custom_target(qmlunittests)
4+add_custom_target(unittests)
35
46# QML tests that require graphical capabilities.
5−add_custom_target(qmluitests)
7+add_custom_target(uitests)
8+add_custom_target(xvfbuitests)
69
7−add_custom_target(qmltests)
8−add_dependencies(qmltests qmlunittests qmluitests)
10+add_custom_target(alltests)
11+add_dependencies(alltests unittests uitests)
912
10−add_subdirectory(libs)
11−add_subdirectory(qmltests)
13+add_custom_target(xvfballtests)
14+add_dependencies(xvfballtests unittests xvfbuitests)
15+
16+# Support libraries and plugins
1217add_subdirectory(mocks)
13−add_subdirectory(whitespace)
14−add_subdirectory(plugins)
1518add_subdirectory(utils)
1619add_subdirectory(uqmlscene)
20+
21+# Use our custom implementation of qmlscene and import dbus-test-runner
22+add_executable(qmlscene ALIAS uqmlscene)
23+import_executables(dbus-test-runner)
24+
25+set(UNITY_IMPORT_PATHS
26+ ${CMAKE_BINARY_DIR}/tests/mocks
27+ ${CMAKE_BINARY_DIR}/tests/utils/modules
28+ ${CMAKE_BINARY_DIR}/plugins
29+)
30+set(UNITY_IMPORT_PATHS ${UNITY_IMPORT_PATHS} CACHE INTERNAL "")
31+
32+macro(unity8_parse_arguments)
33+ cmake_parse_arguments(U8TEST
34+ "${QMLTEST_OPTIONS};LIGHTDM"
35+ "${QMLTEST_SINGLE}"
36+ "${QMLTEST_MULTI}"
37+ ${ARGN}
38+ )
39+
40+ set(ld_paths)
41+ if(U8TEST_LIGHTDM)
42+ list(APPEND ld_paths
43+ ${CMAKE_BINARY_DIR}/tests/mocks/libusermetrics
44+ ${CMAKE_BINARY_DIR}/tests/mocks/LightDM/liblightdm
45+ )
46+ endif()
47+
48+ if(ld_paths)
49+ string(REPLACE ";" ":" ld_library_path "${ld_paths}")
50+ set(ld_library_path LD_LIBRARY_PATH=${ld_library_path})
51+ endif()
52+
53+ set(U8TEST_ARGN "${ARGN}")
54+ list(REMOVE_ITEM U8TEST_ARGN LIGHTDM)
55+
56+ set(environment
57+ UNITY_TESTING=1
58+ LANGUAGE=C
59+ LC_ALL=C.UTF-8
60+ ${ld_library_path}
61+ )
62+endmacro()
63+
64+
65+# add a non-graphical unit test
66+# see QmlTest.cmake for additional options
67+function(add_unity8_unittest COMPONENT_NAME TARGET)
68+ unity8_parse_arguments(${ARGN})
69+ add_executable_test(${COMPONENT_NAME} ${TARGET}
70+ ADD_TEST
71+ IMPORT_PATHS ${UNITY_IMPORT_PATHS}
72+ TARGETS unittests
73+ ${U8TEST_ARGN}
74+ ENVIRONMENT ${environment}
75+ QT_QPA_PLATFORM=minimal
76+ ${U8TEST_ENVIRONMENT}
77+ )
78+endfunction()
79+
80+# add a graphical unit test
81+function(add_unity8_uitest COMPONENT_NAME TARGET)
82+ unity8_parse_arguments(${ARGN})
83+ add_executable_test(${COMPONENT_NAME} ${TARGET}
84+ IMPORT_PATHS ${UNITY_IMPORT_PATHS}
85+ TARGETS uitests
86+ ${U8TEST_ARGN}
87+ ENVIRONMENT ${environment}
88+ ${U8TEST_ENVIRONMENT}
89+ )
90+endfunction()
91+
92+# add a non-graphical qml unit test
93+function(add_unity8_qmlunittest PATH COMPONENT_NAME)
94+ unity8_parse_arguments(${ARGN})
95+ add_qml_unittest(${PATH} ${COMPONENT_NAME}
96+ ADD_TEST
97+ IMPORT_PATHS ${UNITY_IMPORT_PATHS}
98+ TARGET unittests
99+ ${U8TEST_ARGN}
100+ ENVIRONMENT ${environment}
101+ QT_QPA_PLATFORM=minimal
102+ ${U8TEST_ENVIRONMENT}
103+ )
104+endfunction()
105+
106+# add a graphical qml test
107+function(add_unity8_qmltest PATH COMPONENT_NAME)
108+ unity8_parse_arguments(${ARGN})
109+ add_qml_test(${PATH} ${COMPONENT_NAME}
110+ IMPORT_PATHS ${UNITY_IMPORT_PATHS}
111+ TARGETS uitests
112+ ${U8TEST_ARGN}
113+ ENVIRONMENT ${environment}
114+ ${U8TEST_ENVIRONMENT}
115+ )
116+endfunction()
117+
118+# add a graphical qml benchmark
119+function(add_unity8_qmlbenchmark PATH COMPONENT_NAME ITERATIONS)
120+ add_unity8_qmltest(${PATH} ${COMPONENT_NAME} ITERATIONS ${ITERATIONS} ${ARGN})
121+endfunction()
122+
123+
124+# Actual test definitions
125+add_subdirectory(libs)
126+add_subdirectory(plugins)
127+add_subdirectory(qmltests)
128+add_subdirectory(whitespace)
tests/autopilot/unity8/application_lifecycle/tests/__init__.py+2−2
@@ -31,8 +31,8 @@ class ApplicationLifeCycleTestCase(tests.UnityTestCase):
3131 super().setUp()
3232 self._qml_mock_enabled = False
3333 self._data_dirs_mock_enabled = False
34− unity_proxy = self.launch_unity()
35− process_helpers.unlock_unity(unity_proxy)
34+ self.launch_unity()
35+ process_helpers.unlock_unity()
3636
3737 def create_test_application(self):
3838 desktop_file_dict = fixture_setup.DEFAULT_DESKTOP_FILE_DICT
tests/autopilot/unity8/fixture_setup.py+131−3
@@ -17,19 +17,147 @@
1717# along with this program. If not, see <http://www.gnu.org/licenses/>.
1818#
1919
20−import fixtures
21−import logging
2220import os
2321import subprocess
22+import threading
23+import fixtures
24+import logging
2425
2526import ubuntuuitoolkit
2627from autopilot import introspection
28+from autopilot.matchers import Eventually
29+from testtools.matchers import Equals
30+from ubuntuuitoolkit import fixture_setup
2731
2832from unity8 import (
2933 get_binary_path,
34+ sensors,
3035 process_helpers
3136)
3237
38+from unity8.shell import emulators
39+
40+from unity8.shell.emulators import (
41+ main_window as main_window_emulator
42+)
43+
44+from unity8 import (
45+ get_mocks_library_path,
46+ get_default_extra_mock_libraries,
47+ get_data_dirs
48+)
49+
50+logger = logging.getLogger(__name__)
51+
52+
53+class LaunchUnityWithFakeSensors(fixtures.Fixture):
54+
55+ """Fixture to launch Unity8 with an injectable sensors backend.
56+
57+ :ivar unity_proxy: The Autopilot proxy object for the Unity shell.
58+
59+ """
60+
61+ unity_proxy = None
62+ main_win = None
63+
64+ def setUp(self):
65+ """Restart Unity8 with testability and create sensors."""
66+ super().setUp()
67+ self.useFixture(
68+ fixture_setup.InitctlEnvironmentVariable(
69+ UBUNTU_PLATFORM_API_TEST_OVERRIDE='sensors'))
70+
71+ self.addCleanup(process_helpers.stop_job, 'unity8')
72+ restart_thread = threading.Thread(
73+ target=self._restart_unity_with_testability)
74+ restart_thread.start()
75+
76+ self._create_sensors()
77+
78+ restart_thread.join()
79+ self.fake_sensors = sensors.FakePlatformSensors()
80+
81+ def _get_lightdm_mock_path(self):
82+ lib_path = get_mocks_library_path()
83+ lightdm_mock_path = os.path.abspath(
84+ os.path.join(lib_path, "LightDM", "liblightdm")
85+ )
86+
87+ if not os.path.exists(lightdm_mock_path):
88+ raise RuntimeError(
89+ "LightDM mock does not exist at path {}.".
90+ format(lightdm_mock_path)
91+ )
92+ return lightdm_mock_path
93+
94+ def _get_qml_import_path_with_mock(self):
95+ """Return the QML2_IMPORT_PATH value with the mock path prepended."""
96+ qml_import_path = [get_mocks_library_path()]
97+ if os.getenv('QML2_IMPORT_PATH') is not None:
98+ qml_import_path.append(os.getenv('QML2_IMPORT_PATH'))
99+
100+ qml_import_path = ':'.join(qml_import_path)
101+ return qml_import_path
102+
103+ def _restart_unity_with_testability(self):
104+ _environment = {}
105+
106+ data_dirs = get_data_dirs(True)
107+ if data_dirs is not None:
108+ _environment['XDG_DATA_DIRS'] = data_dirs
109+
110+ _environment['QML2_IMPORT_PATH'] = (
111+ self._get_qml_import_path_with_mock()
112+ )
113+
114+ new_ld_library_path = [
115+ get_default_extra_mock_libraries(),
116+ self._get_lightdm_mock_path()
117+ ]
118+ if os.getenv('LD_LIBRARY_PATH') is not None:
119+ new_ld_library_path.append(os.getenv('LD_LIBRARY_PATH'))
120+ new_ld_library_path = ':'.join(new_ld_library_path)
121+ _environment['LD_LIBRARY_PATH'] = new_ld_library_path
122+
123+ # FIXME: we shouldn't be doing this
124+ # $MIR_SOCKET, fallback to $XDG_RUNTIME_DIR/mir_socket and
125+ # /tmp/mir_socket as last resort
126+ try:
127+ os.unlink(
128+ os.getenv('MIR_SOCKET',
129+ os.path.join(os.getenv('XDG_RUNTIME_DIR', "/tmp"),
130+ "mir_socket")))
131+ except OSError:
132+ pass
133+ try:
134+ os.unlink("/tmp/mir_socket")
135+ except OSError:
136+ pass
137+
138+ binary_arg = "BINARY=%s" % get_binary_path()
139+ env_args = ["%s=%s" % (k, v) for k, v in _environment.items()]
140+ args = [binary_arg] + env_args
141+ self.unity_proxy = process_helpers.restart_unity_with_testability(
142+ *args)
143+ self.main_win = self.unity_proxy.select_single(
144+ main_window_emulator.QQuickView)
145+
146+ def _create_sensors(self):
147+ # Wait for unity to start running.
148+ Eventually(Equals(True)).match(
149+ lambda: process_helpers.is_job_running('unity8'))
150+
151+ # Wait for the sensors fifo file to be created.
152+ fifo_path = '/tmp/sensor-fifo-{0}'.format(
153+ process_helpers.get_unity_pid())
154+ Eventually(Equals(True)).match(
155+ lambda: os.path.exists(fifo_path))
156+
157+ with open(fifo_path, 'w') as fifo:
158+ fifo.write('create accel 0 1000 0.1\n')
159+ fifo.write('create light 0 10 1\n')
160+ fifo.write('create proximity\n')
33161
34162logger = logging.getLogger(__name__)
35163
@@ -188,7 +316,7 @@ class LaunchMockIndicatorService(fixtures.Fixture):
188316 self.ensure_not_running = ensure_not_running
189317
190318 def setUp(self):
191− super(LaunchMockIndicatorService, self).setUp()
319+ super().setUp()
192320 if self.ensure_not_running:
193321 self.ensure_service_not_running()
194322 self.addCleanup(self.stop_service)
tests/autopilot/unity8/greeter/__init__.py+52
@@ -0,0 +1,52 @@
1+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2+#
3+# Unity Autopilot Test Suite
4+# Copyright (C) 2015 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+import dbus
21+
22+from autopilot.matchers import Eventually
23+from testtools.matchers import Equals
24+
25+
26+def hide_greeter_with_dbus():
27+ dbus_proxy = _get_greeter_dbus_proxy()
28+ if _is_greeter_active():
29+ dbus_proxy.HideGreeter()
30+
31+
32+def show_greeter_with_dbus():
33+ dbus_proxy = _get_greeter_dbus_proxy()
34+ if not _is_greeter_active():
35+ dbus_proxy.ShowGreeter()
36+
37+
38+def wait_for_greeter():
39+ Eventually(Equals(True), timeout=300).match(_is_greeter_active)
40+
41+
42+def _get_greeter_dbus_proxy():
43+ bus = dbus.SessionBus()
44+ return bus.get_object('com.canonical.UnityGreeter', '/')
45+
46+
47+def _is_greeter_active():
48+ try:
49+ dbus_proxy = _get_greeter_dbus_proxy()
50+ return dbus_proxy.Get('com.canonical.UnityGreeter', 'IsActive')
51+ except:
52+ return False
tests/autopilot/unity8/indicators/tests/__init__.py+1−1
@@ -31,7 +31,7 @@ class IndicatorTestCase(tests.UnityTestCase):
3131 def setUp(self):
3232 super().setUp()
3333 self.unity_proxy = self.launch_unity()
34− process_helpers.unlock_unity(self.unity_proxy)
34+ process_helpers.unlock_unity()
3535
3636
3737class DeviceIndicatorTestCase(IndicatorTestCase):
tests/autopilot/unity8/indicators/tests/test_display_indicator.py+1−1
@@ -18,7 +18,7 @@
1818
1919from unity8 import (
2020 fixture_setup,
21− indicators,
21+ indicators
2222)
2323from unity8.indicators import tests
2424
tests/autopilot/unity8/process_helpers.py+8−95
@@ -19,14 +19,11 @@
1919
2020import logging
2121import subprocess
22−import dbus
2322
2423import ubuntuuitoolkit
25−from autopilot.exceptions import ProcessSearchError
2624from autopilot.introspection import get_proxy_object_for_existing_process
2725
28−from unity8.shell import emulators
29−from unity8.shell.emulators import main_window as main_window_emulator
26+from unity8 import greeter
3027
3128logger = logging.getLogger(__name__)
3229
@@ -39,104 +36,20 @@ class CannotAccessUnity(Exception):
3936 pass
4037
4138
42−def unlock_unity(unity_proxy_obj=None):
39+def unlock_unity():
4340 """Helper function that attempts to unlock the unity greeter.
4441
45− If unity_proxy_object is None create a proxy object by querying for the
46− running unity process.
47− Otherwise re-use the passed proxy object.
48−
49− :raises RuntimeError: if the greeter attempts and fails to be unlocked.
50−
51− :raises RuntimeWarning: when the greeter cannot be found because it is
52− already unlocked.
53− :raises CannotAccessUnity: if unity is not introspectable or cannot be
54− found on dbus.
55− :raises CannotAccessUnity: if unity's upstart status is not "start" or the
56− upstart job cannot be found at all.
57−
5842 """
59− if unity_proxy_obj is None:
60− try:
61− pid = _get_unity_pid()
62− unity = _get_unity_proxy_object(pid)
63− main_window = unity.select_single(main_window_emulator.QQuickView)
64− except ProcessSearchError as e:
65− raise CannotAccessUnity(
66− "Cannot introspect unity, make sure that it has been started "
67− "with testability. Perhaps use the function "
68− "'restart_unity_with_testability' this module provides."
69− "(%s)"
70− % str(e)
71− )
72− else:
73− main_window = unity_proxy_obj.select_single(
74− main_window_emulator.QQuickView)
75−
76− greeter = main_window.get_greeter()
77− if greeter.created is False:
78− raise RuntimeWarning("Greeter appears to be already unlocked.")
79−
80− bus = dbus.SessionBus()
81− dbus_proxy = bus.get_object("com.canonical.UnityGreeter", "/")
82− try:
83− dbus_proxy.HideGreeter()
84− except dbus.DBusException:
85− logger.info("Failed to unlock greeter")
86− raise
87− else:
88− greeter.created.wait_for(False)
89− logger.info("Greeter unlocked, continuing.")
43+ greeter.wait_for_greeter()
44+ greeter.hide_greeter_with_dbus()
9045
9146
92−def lock_unity(unity_proxy_obj=None):
47+def lock_unity():
9348 """Helper function that attempts to lock unity greeter.
9449
95− If unity_proxy_object is None create a proxy object by querying for the
96− running unity process.
97− Otherwise re-use the passed proxy object.
98−
99− :raises RuntimeError: if the greeter attempts and fails to be locked.
100−
101− :raises RuntimeWarning: when the greeter is found because it is
102− already locked.
103− :raises CannotAccessUnity: if unity is not introspectable or cannot be
104− found on dbus.
105− :raises CannotAccessUnity: if unity's upstart status is not "start" or the
106− upstart job cannot be found at all.
107−
10850 """
109− if unity_proxy_obj is None:
110− try:
111− pid = _get_unity_pid()
112− unity = _get_unity_proxy_object(pid)
113− main_window = unity.select_single(main_window_emulator.QQuickView)
114− except ProcessSearchError as e:
115− raise CannotAccessUnity(
116− "Cannot introspect unity, make sure that it has been started "
117− "with testability. Perhaps use the function "
118− "'restart_unity_with_testability' this module provides."
119− "(%s)"
120− % str(e)
121− )
122− else:
123− main_window = unity_proxy_obj.select_single(
124− main_window_emulator.QQuickView)
125−
126− greeter = main_window.get_greeter()
127− if greeter.created is True:
128− raise RuntimeWarning("Greeter appears to be already locked.")
129−
130− bus = dbus.SessionBus()
131− dbus_proxy = bus.get_object("com.canonical.UnityGreeter", "/")
132− try:
133− dbus_proxy.ShowGreeter()
134− except dbus.DBusException:
135− logger.info("Failed to lock greeter")
136− raise
137− else:
138− greeter.created.wait_for(True)
139− logger.info("Greeter locked, continuing.")
51+ greeter.show_greeter_with_dbus()
52+ greeter.wait_for_greeter()
14053
14154
14255def restart_unity_with_testability(*args):
@@ -262,7 +175,7 @@ def _get_unity_status():
262175 raise CannotAccessUnity(str(error))
263176
264177
265−def _get_unity_pid():
178+def get_unity_pid():
266179 try:
267180 return get_job_pid('unity8')
268181 except JobError as error:
tests/autopilot/unity8/sensors.pynew file+93
@@ -0,0 +1,93 @@
1+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2+#
3+# Unity Autopilot Test Suite
4+# Copyright (C) 2015 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+"""Control module for event injection to the fake/test platform sensors."""
21+
22+from unity8 import process_helpers
23+
24+
25+class FakePlatformSensors:
26+
27+ def __init__(self, pid=None):
28+ self.pid = pid or process_helpers.get_unity_pid()
29+
30+ def set_orientation(self, action):
31+ if action == 'top_up':
32+ with open("/tmp/sensor-fifo-{0}".format(self.pid), "w") as fifo:
33+ fifo.write("70 accel -10.050858 -0.598550 0.756568\n")
34+ fifo.write("70 accel -9.797073 -0.555455 1.019930\n")
35+ fifo.write("70 accel -10.141838 -0.770933 0.632069\n")
36+ fifo.write("70 accel -12.057199 -1.259350 1.690306\n")
37+ fifo.write("70 accel -19.282900 -3.926491 3.098097\n")
38+ fifo.write("70 accel -14.480132 -14.269443 1.216254\n")
39+ fifo.write("70 accel 16.419436 4.242526 -7.714118\n")
40+ fifo.write("70 accel 5.583278 8.279149 -1.848324\n")
41+ fifo.write("70 accel 1.422156 8.547300 0.416591\n")
42+ fifo.write("70 accel 4.357447 9.988609 -0.110133\n")
43+ fifo.write("70 accel 0.699107 9.840169 0.756568\n")
44+ fifo.write("70 accel 1.364695 9.844957 -0.287304\n")
45+ fifo.flush()
46+ elif action == 'top_down':
47+ with open("/tmp/sensor-fifo-{0}".format(self.pid), "w") as fifo:
48+ fifo.write("70 accel -10.050858 -0.598550 0.756568\n")
49+ fifo.write("70 accel 9.538500 -0.603339 1.292869\n")
50+ fifo.write("70 accel 9.485827 -0.636858 1.422156\n")
51+ fifo.write("70 accel 9.677363 -0.402226 1.374272\n")
52+ fifo.write("70 accel 9.303867 -0.507571 1.283292\n")
53+ fifo.write("70 accel 8.604761 -1.015141 1.436521\n")
54+ fifo.write("70 accel 7.580042 -2.001553 0.521936\n")
55+ fifo.write("70 accel 7.503428 -4.247314 0.502782\n")
56+ fifo.write("70 accel 7.067683 -7.240066 0.842759\n")
57+ fifo.write("70 accel 6.488286 -9.873688 -0.541090\n")
58+ fifo.write("70 accel 6.229713 -9.241618 -1.048660\n")
59+ fifo.write("70 accel 4.046201 -9.198523 -0.057461\n")
60+ fifo.write("70 accel 2.398990 -9.629479 0.957681\n")
61+ fifo.write("70 accel 1.632846 -9.361329 -0.311246\n")
62+ fifo.write("70 accel -0.181959 -9.696517 -0.301669\n")
63+ fifo.flush()
64+ elif action == 'left_up':
65+ with open("/tmp/sensor-fifo-{0}".format(self.pid), "w") as fifo:
66+ fifo.write("70 accel -10.050858 -0.598550 0.756568\n")
67+ fifo.write("70 accel 0.196325 9.878476 0.948104\n")
68+ fifo.write("70 accel 0.258574 9.955091 1.091756\n")
69+ fifo.write("70 accel 0.287304 10.041282 1.134852\n")
70+ fifo.write("70 accel 1.537078 10.553641 1.561020\n")
71+ fifo.write("70 accel 8.130709 10.093954 2.561796\n")
72+ fifo.write("70 accel -0.229843 5.348647 1.723825\n")
73+ fifo.write("70 accel -9.916783 0.488417 -3.418920\n")
74+ fifo.write("70 accel -13.417107 -0.416591 -2.360683\n")
75+ fifo.write("70 accel -13.872005 -2.049437 -0.574608\n")
76+ fifo.flush()
77+ elif action == 'right_up':
78+ with open("/tmp/sensor-fifo-{0}".format(self.pid), "w") as fifo:
79+ fifo.write("70 accel -10.050858 -0.598550 0.756568\n")
80+ fifo.write("70 accel -4.550858 -0.598550 0.856568\n")
81+ fifo.write("70 accel -0.799663 9.988609 1.197101\n")
82+ fifo.write("70 accel -0.861913 9.864111 1.066701\n")
83+ fifo.write("70 accel -0.861913 9.864111 0.866701\n")
84+ fifo.write("70 accel -1.776498 9.830592 1.273715\n")
85+ fifo.write("70 accel -2.376498 9.830592 1.273715\n")
86+ fifo.write("70 accel -13.158532 2.217031 1.091756\n")
87+ fifo.write("70 accel 5.056554 1.067814 0.799663\n")
88+ fifo.write("70 accel 5.056554 1.767814 0.799663\n")
89+ fifo.write("70 accel 14.882358 2.896984 1.221043\n")
90+ fifo.write("70 accel 9.466674 -0.363919 -1.029507\n")
91+ fifo.write("70 accel 11.253524 -0.186748 -0.311246\n")
92+ fifo.write("70 accel 12.253524 -0.186748 -0.311246\n")
93+ fifo.flush()
tests/autopilot/unity8/shell/emulators/dash.py+17−10
@@ -130,7 +130,7 @@ class Dash(ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
130130 x, y, width, height = dash_content.globalRect
131131 # Make the drag range be a multiple of the drag "rate" value.
132132 # Workarounds https://bugs.launchpad.net/mir/+bug/1399690
133− rate = 10
133+ rate = 5
134134 divisions = 5
135135 jump = (width / divisions) // rate * rate
136136 start_x = x + jump
@@ -146,7 +146,7 @@ class Dash(ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
146146 x, y, width, height = dash_content.globalRect
147147 # Make the drag range be a multiple of the drag "rate" value.
148148 # Workarounds https://bugs.launchpad.net/mir/+bug/1399690
149− rate = 10
149+ rate = 5
150150 divisions = 5
151151 jump = (width / divisions) // rate * rate
152152 start_x = x + jump * (divisions - 1)
@@ -185,14 +185,15 @@ class Dash(ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
185185
186186
187187class ListViewWithPageHeader(ubuntuuitoolkit.QQuickFlickable):
188− pass
188+
189+ margin_to_swipe_from_bottom = ubuntuuitoolkit.units.gu(4)
189190
190191
191192class GenericScopeView(ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
192193 """Autopilot emulator for generic scopes."""
193194
194195 @autopilot_logging.log_action(logger.info)
195− def open_preview(self, category, app_name):
196+ def open_preview(self, category, app_name, press_duration=0.10):
196197 """Open the preview of an application.
197198
198199 :parameter category: The name of the category where the application is.
@@ -203,16 +204,19 @@ class GenericScopeView(ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
203204 # FIXME some categories need a long press in order to see the preview.
204205 # Some categories do not show previews, like recent apps.
205206 # --elopio - 2014-1-14
206− self.click_scope_item(category, app_name)
207+ self.click_scope_item(category, app_name, press_duration)
207208 preview_list = self.wait_select_single(
208209 'QQuickLoader', objectName='subPageLoader')
209210 preview_list.subPageShown.wait_for(True)
210211 preview_list.x.wait_for(0)
212+ self.get_root_instance().select_single(
213+ objectName='processingIndicator').visible.wait_for(False)
211214 return preview_list.select_single(
212− Preview, objectName='preview{}'.format(preview_list.currentIndex))
215+ Preview, objectName='preview{}'.format(
216+ preview_list.initialIndex))
213217
214218 @autopilot_logging.log_action(logger.debug)
215− def click_scope_item(self, category, title):
219+ def click_scope_item(self, category, title, press_duration=0.10):
216220 """Click an item from the scope.
217221
218222 :parameter category: The name of the category where the item is.
@@ -220,9 +224,12 @@ class GenericScopeView(ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
220224
221225 """
222226 category_element = self._get_category_element(category)
223− icon = category_element.wait_select_single('AbstractButton',
224− title=title)
225− self.pointing_device.click_object(icon)
227+ icon = category_element.wait_select_single(
228+ 'AbstractButton', title=title)
229+ list_view = self.select_single(
230+ ListViewWithPageHeader, objectName='categoryListView')
231+ list_view.swipe_child_into_view(icon)
232+ self.pointing_device.click_object(icon, press_duration=press_duration)
226233
227234 def _get_category_element(self, category):
228235 try:
tests/autopilot/unity8/shell/emulators/main_window.py+12
@@ -169,6 +169,18 @@ class QQuickView(ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
169169 objectName='pinPadButton{}'.format(button_id)
170170 )
171171
172+ def get_shell_orientation_angle(self):
173+ return self._get_shell().orientationAngle
174+
175+ def get_shell_orientation(self):
176+ return self._get_shell().orientation
177+
178+ def get_shell_primary_orientation(self):
179+ return self._get_shell().primaryOrientation
180+
181+ def get_shell_native_orientation(self):
182+ return self._get_shell().nativeOrientation
183+
172184 @autopilot_logging.log_action(logger.info)
173185 def wait_for_notification(self):
174186 """Wait for a notification dialog to appear.
tests/autopilot/unity8/shell/tests/test_emulators.py+8−2
@@ -41,8 +41,8 @@ class MainWindowTestCase(tests.UnityTestCase):
4141
4242 def setUp(self):
4343 super().setUp()
44− unity_proxy = self.launch_unity()
45− process_helpers.unlock_unity(unity_proxy)
44+ self.launch_unity()
45+ process_helpers.unlock_unity()
4646
4747
4848class DashEmulatorTestCase(tests.DashBaseTestCase):
@@ -120,6 +120,12 @@ class GenericScopeViewEmulatorTestCase(tests.DashBaseTestCase):
120120 self.assertIsInstance(preview, dash_emulators.Preview)
121121 self.assertTrue(preview.isCurrent)
122122
123+ def test_open_preview_of_non_visible_item(self):
124+ """Open an item that requires swiping to make it visible."""
125+ preview = self.generic_scope.open_preview('2', 'Title.2.0')
126+ self.assertIsInstance(preview, dash_emulators.Preview)
127+ self.assertTrue(preview.isCurrent)
128+
123129
124130class DashAppsEmulatorTestCase(tests.DashBaseTestCase):
125131
tests/autopilot/unity8/shell/tests/test_notifications.py+22−22
@@ -119,8 +119,8 @@ class InteractiveNotificationBase(NotificationsBase):
119119
120120 def test_interactive(self):
121121 """Interactive notification must react upon click on itself."""
122− unity_proxy = self.launch_unity()
123− unlock_unity(unity_proxy)
122+ self.launch_unity()
123+ unlock_unity()
124124
125125 notify_list = self._get_notifications_list()
126126
@@ -156,8 +156,8 @@ class InteractiveNotificationBase(NotificationsBase):
156156 """Snap-decision with three actions should use
157157 one-over two button layout.
158158 """
159− unity_proxy = self.launch_unity()
160− unlock_unity(unity_proxy)
159+ self.launch_unity()
160+ unlock_unity()
161161
162162 summary = "Theatre at Ferria Stadium"
163163 body = "at Ferria Stadium in Bilbao, Spain\n07578545317"
@@ -197,8 +197,8 @@ class InteractiveNotificationBase(NotificationsBase):
197197 """Snap-decision should block input to shell
198198 without greeter/lockscreen.
199199 """
200− unity_proxy = self.launch_unity()
201− unlock_unity(unity_proxy)
200+ self.launch_unity()
201+ unlock_unity()
202202
203203 summary = "Incoming file"
204204 body = "Frank would like to send you the file: essay.pdf"
@@ -421,8 +421,8 @@ class EphemeralNotificationsTests(NotificationsBase):
421421
422422 def test_icon_summary_body(self):
423423 """Notification must display the expected summary and body text."""
424− unity_proxy = self.launch_unity()
425− unlock_unity(unity_proxy)
424+ self.launch_unity()
425+ unlock_unity()
426426
427427 notify_list = self._get_notifications_list()
428428
@@ -458,8 +458,8 @@ class EphemeralNotificationsTests(NotificationsBase):
458458 def test_icon_summary(self):
459459 """Notification must display the expected summary and secondary
460460 icon."""
461− unity_proxy = self.launch_unity()
462− unlock_unity(unity_proxy)
461+ self.launch_unity()
462+ unlock_unity()
463463
464464 notify_list = self._get_notifications_list()
465465
@@ -491,8 +491,8 @@ class EphemeralNotificationsTests(NotificationsBase):
491491 def test_urgency_order(self):
492492 """Notifications must be displayed in order according to their
493493 urgency."""
494− unity_proxy = self.launch_unity()
495− unlock_unity(unity_proxy)
494+ self.launch_unity()
495+ unlock_unity()
496496
497497 notify_list = self._get_notifications_list()
498498
@@ -578,8 +578,8 @@ class EphemeralNotificationsTests(NotificationsBase):
578578
579579 def test_summary_and_body(self):
580580 """Notification must display the expected summary- and body-text."""
581− unity_proxy = self.launch_unity()
582− unlock_unity(unity_proxy)
581+ self.launch_unity()
582+ unlock_unity()
583583
584584 notify_list = self._get_notifications_list()
585585
@@ -603,8 +603,8 @@ class EphemeralNotificationsTests(NotificationsBase):
603603
604604 def test_summary_only(self):
605605 """Notification must display only the expected summary-text."""
606− unity_proxy = self.launch_unity()
607− unlock_unity(unity_proxy)
606+ self.launch_unity()
607+ unlock_unity()
608608
609609 notify_list = self._get_notifications_list()
610610
@@ -621,8 +621,8 @@ class EphemeralNotificationsTests(NotificationsBase):
621621 def test_update_notification_same_layout(self):
622622 """Notification must allow updating its contents while being
623623 displayed."""
624− unity_proxy = self.launch_unity()
625− unlock_unity(unity_proxy)
624+ self.launch_unity()
625+ unlock_unity()
626626
627627 notify_list = self._get_notifications_list()
628628
@@ -660,8 +660,8 @@ class EphemeralNotificationsTests(NotificationsBase):
660660 def test_update_notification_layout_change(self):
661661 """Notification must allow updating its contents and layout while
662662 being displayed."""
663− unity_proxy = self.launch_unity()
664− unlock_unity(unity_proxy)
663+ self.launch_unity()
664+ unlock_unity()
665665
666666 notify_list = self._get_notifications_list()
667667
@@ -709,8 +709,8 @@ class EphemeralNotificationsTests(NotificationsBase):
709709 """ use the create notification script to get a notification dialog.
710710 Check that the arguments passed to the script match the fields. """
711711
712− unity_proxy = self.launch_unity()
713− unlock_unity(unity_proxy)
712+ self.launch_unity()
713+ unlock_unity()
714714
715715 summary = 'Helper summary'
716716 body = 'Helper body'
tests/autopilot/unity8/shell/tests/test_rotation.pynew file+113
@@ -0,0 +1,113 @@
1+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2+#
3+# Unity Autopilot Test Suite
4+# Copyright (C) 2015 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+"""Tests for shell-rotation"""
21+
22+from autopilot.platform import model
23+from unity8 import (
24+ fixture_setup,
25+ process_helpers
26+)
27+from unity8.shell import tests
28+import logging
29+from testtools.matchers import Equals
30+from autopilot.matchers import Eventually
31+
32+logger = logging.getLogger(__name__)
33+
34+
35+class RotationBase(tests.UnityTestCase):
36+ """Base class for all shell-rotation tests that provides helper methods."""
37+
38+ def setUp(self):
39+ if model() == 'Desktop':
40+ self.skipTest('Test cannot be run on the desktop.')
41+ super().setUp()
42+ self._qml_mock_enabled = False
43+ self._data_dirs_mock_enabled = False
44+
45+ def _assert_change_of_orientation_and_angle(self):
46+ self.assertThat(self.shell_proxy.orientation,
47+ Eventually(Equals(self.orientation)))
48+ self.assertThat(self.shell_proxy.orientationAngle,
49+ Eventually(Equals(self.angle)))
50+
51+
52+class TestFakeSensor(RotationBase):
53+ scenarios = [('top up', {'action': 'top_up', 'orientation': 1}),
54+ ('right up', {'action': 'right_up', 'orientation': 8}),
55+ ('top down', {'action': 'top_down', 'orientation': 4}),
56+ ('left up', {'action': 'left_up', 'orientation': 2})]
57+
58+ def test_fake_sensor(self):
59+ unity_with_sensors = fixture_setup.LaunchUnityWithFakeSensors()
60+ self.useFixture(unity_with_sensors)
61+ process_helpers.unlock_unity(unity_with_sensors.unity_proxy)
62+ fake_sensors = unity_with_sensors.fake_sensors
63+ o_proxy = unity_with_sensors.main_win.select_single('OrientedShell')
64+
65+ fake_sensors.set_orientation(self.action)
66+ self.assertThat(o_proxy.physicalOrientation,
67+ Eventually(Equals(self.orientation), timeout=15))
68+
69+
70+class TestRotationWithApp(RotationBase):
71+ scenarios = [
72+ ('top up, angle 0',
73+ {'action': 'top_up', 'orientation': 1, 'angle': 0}),
74+ ('right up, angle 90',
75+ {'action': 'right_up', 'orientation': 8, 'angle': 90}),
76+ ('top down, angle 180',
77+ {'action': 'top_down', 'orientation': 4, 'angle': 180}),
78+ ('left up, angle 270',
79+ {'action': 'left_up', 'orientation': 2, 'angle': 270})]
80+
81+ def test_rotation_with_webbrowser_app(self):
82+ """Do an orientation-change and verify that an app and the shell
83+ adapted correctly"""
84+
85+ unity_with_sensors = fixture_setup.LaunchUnityWithFakeSensors()
86+ self.useFixture(unity_with_sensors)
87+ process_helpers.unlock_unity(unity_with_sensors.unity_proxy)
88+ fake_sensors = unity_with_sensors.fake_sensors
89+ o_proxy = unity_with_sensors.main_win.select_single('OrientedShell')
90+ self.shell_proxy = unity_with_sensors.main_win.select_single('Shell')
91+
92+ # launch an application
93+ self.launch_upstart_application('webbrowser-app')
94+ unity_with_sensors.main_win.show_dash_from_launcher()
95+ unity_with_sensors.main_win.launch_application('webbrowser-app')
96+
97+ # skip test early, if device doesn't support a certain orientation
98+ if not (self.shell_proxy.orientation & o_proxy.supportedOrientations):
99+ self.skipTest('unsupported orientation ' + self.action)
100+
101+ self.assertThat(
102+ unity_with_sensors.main_win.get_current_focused_app_id(),
103+ Eventually(Equals('webbrowser-app')))
104+
105+ # get default orientation and angle
106+ self.orientation = self.shell_proxy.orientation
107+ self.angle = self.shell_proxy.orientationAngle
108+
109+ # check if fake sensors affect orientation and angle
110+ fake_sensors.set_orientation(self.action)
111+ self.assertThat(o_proxy.physicalOrientation,
112+ Eventually(Equals(self.orientation), timeout=15))
113+ self._assert_change_of_orientation_and_angle()
tests/copyright/CMakeLists.txt+1−1
@@ -2,5 +2,5 @@
22# Test that all source files contain a copyright header.
33#
44
5−add_test(copyright ${CMAKE_CURRENT_SOURCE_DIR}/check_copyright.sh
5+add_test(Copyright ${CMAKE_CURRENT_SOURCE_DIR}/check_copyright.sh
66 ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR})
tests/libs/UbuntuGestures/CMakeLists.txt+4−8
@@ -4,17 +4,13 @@ include_directories(
44 )
55
66macro(add_gesture_test CLASSNAME)
7− set(testCommand
8− ${CLASSNAME}TestExec -o ${CMAKE_BINARY_DIR}/test${CLASSNAME}.xml,xunitxml -o -,txt)
9−
10− add_test(NAME test${CLASSNAME} COMMAND ${testCommand})
11−
12− set_tests_properties(test${CLASSNAME} PROPERTIES
13− ENVIRONMENT LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/libs/UbuntuGestures)
14−
157 add_executable(${CLASSNAME}TestExec tst_${CLASSNAME}.cpp)
168 qt5_use_modules(${CLASSNAME}TestExec Test Core Gui Quick)
179 target_link_libraries(${CLASSNAME}TestExec UbuntuGestures)
10+
11+ add_unity8_unittest(${CLASSNAME} ${CLASSNAME}TestExec
12+ ENVIRONMENT LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/libs/UbuntuGestures
13+ )
1814endmacro(add_gesture_test)
1915
2016add_gesture_test(TouchRegistry)
tests/mocks/Unity/Application/ApplicationInfo.cpp+43−2
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013-2014 Canonical, Ltd.
2+ * Copyright (C) 2013-2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -34,6 +34,11 @@ ApplicationInfo::ApplicationInfo(const QString &appId, QObject *parent)
3434 , m_focused(false)
3535 , m_fullscreen(false)
3636 , m_session(0)
37+ , m_supportedOrientations(Qt::PortraitOrientation |
38+ Qt::LandscapeOrientation |
39+ Qt::InvertedPortraitOrientation |
40+ Qt::InvertedLandscapeOrientation)
41+ , m_rotatesWindowContents(false)
3742 , m_manualSurfaceCreation(false)
3843{
3944}
@@ -45,6 +50,11 @@ ApplicationInfo::ApplicationInfo(QObject *parent)
4550 , m_focused(false)
4651 , m_fullscreen(false)
4752 , m_session(0)
53+ , m_supportedOrientations(Qt::PortraitOrientation |
54+ Qt::LandscapeOrientation |
55+ Qt::InvertedPortraitOrientation |
56+ Qt::InvertedLandscapeOrientation)
57+ , m_rotatesWindowContents(false)
4858 , m_manualSurfaceCreation(false)
4959{
5060}
@@ -99,9 +109,17 @@ void ApplicationInfo::setIconId(const QString &iconId)
99109
100110void ApplicationInfo::setScreenshotId(const QString &screenshotId)
101111{
102− QString screenshotFileName = QString("%1/Dash/graphics/phone/screenshots/%2@12.png")
112+ QString screenshotFileName;
113+
114+ if (screenshotId.endsWith(".svg")) {
115+ screenshotFileName = QString("%1/Dash/graphics/phone/screenshots/%2")
116+ .arg(qmlDirectory())
117+ .arg(screenshotId);
118+ } else {
119+ screenshotFileName = QString("%1/Dash/graphics/phone/screenshots/%2@12.png")
103120 .arg(qmlDirectory())
104121 .arg(screenshotId);
122+ }
105123
106124 if (screenshotFileName != m_screenshotFileName) {
107125 m_screenshotFileName = screenshotFileName;
@@ -145,6 +163,9 @@ void ApplicationInfo::setState(State value)
145163
146164 if (!m_manualSurfaceCreation && m_state == ApplicationInfo::Running) {
147165 QTimer::singleShot(500, this, SLOT(createSession()));
166+ } else if (m_state == ApplicationInfo::Stopped) {
167+ delete m_session;
168+ m_session = nullptr;
148169 }
149170 }
150171}
@@ -172,3 +193,23 @@ void ApplicationInfo::setManualSurfaceCreation(bool value)
172193 Q_EMIT manualSurfaceCreationChanged(value);
173194 }
174195}
196+
197+Qt::ScreenOrientations ApplicationInfo::supportedOrientations() const
198+{
199+ return m_supportedOrientations;
200+}
201+
202+void ApplicationInfo::setSupportedOrientations(Qt::ScreenOrientations orientations)
203+{
204+ m_supportedOrientations = orientations;
205+}
206+
207+bool ApplicationInfo::rotatesWindowContents() const
208+{
209+ return m_rotatesWindowContents;
210+}
211+
212+void ApplicationInfo::setRotatesWindowContents(bool value)
213+{
214+ m_rotatesWindowContents = value;
215+}
tests/mocks/Unity/Application/ApplicationInfo.h+9−1
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013-2014 Canonical, Ltd.
2+ * Copyright (C) 2013-2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -80,6 +80,12 @@ public:
8080 void setFullscreen(bool value);
8181 bool fullscreen() const { return m_fullscreen; }
8282
83+ Qt::ScreenOrientations supportedOrientations() const override;
84+ void setSupportedOrientations(Qt::ScreenOrientations orientations);
85+
86+ bool rotatesWindowContents() const override;
87+ void setRotatesWindowContents(bool value);
88+
8389 bool manualSurfaceCreation() const { return m_manualSurfaceCreation; }
8490 void setManualSurfaceCreation(bool value);
8591
@@ -108,6 +114,8 @@ private:
108114 bool m_focused;
109115 bool m_fullscreen;
110116 Session* m_session;
117+ Qt::ScreenOrientations m_supportedOrientations;
118+ bool m_rotatesWindowContents;
111119
112120 bool m_manualSurfaceCreation;
113121};
tests/mocks/Unity/Application/ApplicationManager.cpp+39−2
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013-2014 Canonical, Ltd.
2+ * Copyright (C) 2013-2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -348,6 +348,13 @@ void ApplicationManager::unfocusCurrentApplication()
348348
349349void ApplicationManager::buildListOfAvailableApplications()
350350{
351+ /*
352+ ATTENTION!
353+ Be careful when changing application properties here as some qmltests
354+ rely on them being the way it's specified here (e.g. that camera-app
355+ is fullscreen, that twitter-webapp can rotate in all directions, etc)
356+ */
357+
351358 ApplicationInfo *application;
352359
353360 application = new ApplicationInfo(this);
@@ -355,6 +362,7 @@ void ApplicationManager::buildListOfAvailableApplications()
355362 application->setName("Unity 8 Mock Dash");
356363 application->setScreenshotId("unity8-dash");
357364 application->setStage(ApplicationInfo::MainStage);
365+ application->setSupportedOrientations(Qt::PrimaryOrientation);
358366 m_availableApplications.append(application);
359367
360368 application = new ApplicationInfo(this);
@@ -363,6 +371,8 @@ void ApplicationManager::buildListOfAvailableApplications()
363371 application->setScreenshotId("dialer");
364372 application->setIconId("dialer-app");
365373 application->setStage(ApplicationInfo::SideStage);
374+ application->setSupportedOrientations(Qt::PortraitOrientation
375+ | Qt::InvertedPortraitOrientation);
366376 m_availableApplications.append(application);
367377
368378 application = new ApplicationInfo(this);
@@ -371,6 +381,11 @@ void ApplicationManager::buildListOfAvailableApplications()
371381 application->setScreenshotId("camera");
372382 application->setIconId("camera");
373383 application->setFullscreen(true);
384+ application->setSupportedOrientations(Qt::PortraitOrientation
385+ | Qt::LandscapeOrientation
386+ | Qt::InvertedPortraitOrientation
387+ | Qt::InvertedLandscapeOrientation);
388+ application->setRotatesWindowContents(true);
374389 m_availableApplications.append(application);
375390
376391 application = new ApplicationInfo(this);
@@ -416,13 +431,35 @@ void ApplicationManager::buildListOfAvailableApplications()
416431 application->setAppId("gmail-webapp");
417432 application->setName("GMail");
418433 application->setIconId("gmail");
434+ application->setScreenshotId("gmail-webapp.svg");
435+ application->setFullscreen(false);
436+ application->setStage(ApplicationInfo::MainStage);
437+ application->setSupportedOrientations(Qt::PortraitOrientation
438+ | Qt::LandscapeOrientation
439+ | Qt::InvertedPortraitOrientation
440+ | Qt::InvertedLandscapeOrientation);
441+ m_availableApplications.append(application);
442+
443+ application = new ApplicationInfo(this);
444+ application->setAppId("music-app");
445+ application->setName("Music");
446+ application->setIconId("soundcloud");
447+ application->setScreenshotId("music");
448+ application->setFullscreen(false);
449+ application->setStage(ApplicationInfo::MainStage);
450+ application->setSupportedOrientations(Qt::PortraitOrientation
451+ | Qt::LandscapeOrientation
452+ | Qt::InvertedPortraitOrientation
453+ | Qt::InvertedLandscapeOrientation);
419454 m_availableApplications.append(application);
420455
421456 application = new ApplicationInfo(this);
422457 application->setAppId("ubuntu-weather-app");
423458 application->setName("Weather");
424459 application->setIconId("weather");
425− application->setStage(ApplicationInfo::SideStage);
460+ application->setScreenshotId("ubuntu-weather-app.svg");
461+ application->setSupportedOrientations(Qt::LandscapeOrientation
462+ | Qt::InvertedLandscapeOrientation);
426463 m_availableApplications.append(application);
427464
428465 application = new ApplicationInfo(this);
tests/mocks/Unity/Application/CMakeLists.txt+1−2
@@ -1,5 +1,3 @@
1−pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=5)
2−
31set(FakeUnityApplicationQml_SOURCES
42 plugin.cpp
53 ApplicationInfo.cpp
@@ -14,6 +12,7 @@ set(FakeUnityApplicationQml_SOURCES
1412 SurfaceManager.cpp
1513 SessionModel.h
1614 UbuntuKeyboardInfo.cpp
15+ VirtualKeyboard.cpp
1716 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
1817 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
1918)
tests/mocks/Unity/Application/MirSurfaceItem.cpp+10−27
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -39,14 +39,12 @@ MirSurfaceItem::MirSurfaceItem(const QString& name,
3939 , m_type(type)
4040 , m_state(state)
4141 , m_live(true)
42− , m_orientation(Qt::PortraitOrientation)
43− , m_touchPressCount(0)
44− , m_touchReleaseCount(0)
42+ , m_orientationAngle(Angle0)
4543 , m_qmlItem(nullptr)
4644 , m_screenshotUrl(screenshot)
45+ , m_touchPressCount(0)
46+ , m_touchReleaseCount(0)
4747{
48− qDebug() << "MirSurfaceItem::MirSurfaceItem() " << this->name();
49−
5048 setAcceptedMouseButtons(Qt::LeftButton | Qt::MiddleButton | Qt::RightButton |
5149 Qt::ExtraButton1 | Qt::ExtraButton2 | Qt::ExtraButton3 | Qt::ExtraButton4 |
5250 Qt::ExtraButton5 | Qt::ExtraButton6 | Qt::ExtraButton7 | Qt::ExtraButton8 |
@@ -55,9 +53,6 @@ MirSurfaceItem::MirSurfaceItem(const QString& name,
5553
5654 QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
5755
58− connect(this, &QQuickItem::focusChanged,
59− this, &MirSurfaceItem::onFocusChanged);
60−
6156 // The assumptions I make here really should hold.
6257 QQuickView *quickView =
6358 qobject_cast<QQuickView*>(QGuiApplication::topLevelWindows()[0]);
@@ -117,17 +112,17 @@ void MirSurfaceItem::release()
117112 }
118113}
119114
120−void MirSurfaceItem::setOrientation(const Qt::ScreenOrientation orientation)
115+void MirSurfaceItem::setOrientationAngle(OrientationAngle angle)
121116{
122− if (m_orientation == orientation)
117+ if (m_orientationAngle == angle)
123118 return;
124119
125− m_orientation = orientation;
120+ m_orientationAngle = angle;
126121
127− QQmlProperty orientationProp(m_qmlItem, "orientation");
128− orientationProp.write(QVariant::fromValue(orientation));
122+ QQmlProperty orientationProp(m_qmlItem, "orientationAngle");
123+ orientationProp.write(QVariant::fromValue(m_orientationAngle));
129124
130− Q_EMIT orientationChanged();
125+ Q_EMIT orientationAngleChanged(m_orientationAngle);
131126}
132127
133128void MirSurfaceItem::setSession(Session* session)
@@ -152,15 +147,6 @@ void MirSurfaceItem::setLive(bool live)
152147 }
153148}
154149
155−void MirSurfaceItem::onFocusChanged()
156−{
157− if (!hasFocus()) {
158− // Causes a crash in tst_Shell.qml, inside the mock Unity.Application itself.
159− // Didn't have time to debug yet.
160− //Q_EMIT inputMethodDismissed();
161− }
162−}
163−
164150void MirSurfaceItem::setState(MirSurfaceItem::State newState)
165151{
166152 if (newState != m_state) {
@@ -197,9 +183,6 @@ void MirSurfaceItem::touchEvent(QTouchEvent * event)
197183 if (event->touchPointStates() & Qt::TouchPointPressed) {
198184 ++m_touchPressCount;
199185 Q_EMIT touchPressCountChanged(m_touchPressCount);
200− // Causes a crash in tst_Shell.qml, inside the mock Unity.Application itself.
201− // Didn't have time to debug yet.
202− // Q_EMIT inputMethodRequested();
203186 } else if (event->touchPointStates() & Qt::TouchPointReleased) {
204187 ++m_touchReleaseCount;
205188 Q_EMIT touchReleaseCountChanged(m_touchReleaseCount);
tests/mocks/Unity/Application/MirSurfaceItem.h+29−29
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -29,17 +29,18 @@ class MirSurfaceItem : public QQuickItem
2929 Q_OBJECT
3030 Q_ENUMS(Type)
3131 Q_ENUMS(State)
32+ Q_ENUMS(OrientationAngle)
3233
3334 Q_PROPERTY(Type type READ type NOTIFY typeChanged)
3435 Q_PROPERTY(State state READ state NOTIFY stateChanged)
3536 Q_PROPERTY(QString name READ name CONSTANT)
3637 Q_PROPERTY(bool live READ live NOTIFY liveChanged)
37− Q_PROPERTY(Qt::ScreenOrientation orientation READ orientation WRITE setOrientation NOTIFY orientationChanged DESIGNABLE false)
38−
39− Q_PROPERTY(int touchPressCount READ touchPressCount WRITE setTouchPressCount NOTIFY touchPressCountChanged
40− DESIGNABLE false)
41− Q_PROPERTY(int touchReleaseCount READ touchReleaseCount WRITE setTouchReleaseCount NOTIFY touchReleaseCountChanged
42− DESIGNABLE false)
38+ Q_PROPERTY(OrientationAngle orientationAngle READ orientationAngle WRITE setOrientationAngle
39+ NOTIFY orientationAngleChanged DESIGNABLE false)
40+ Q_PROPERTY(int touchPressCount READ touchPressCount WRITE setTouchPressCount
41+ NOTIFY touchPressCountChanged DESIGNABLE false)
42+ Q_PROPERTY(int touchReleaseCount READ touchReleaseCount WRITE setTouchReleaseCount
43+ NOTIFY touchReleaseCountChanged DESIGNABLE false)
4344
4445public:
4546 enum Type {
@@ -62,6 +63,13 @@ public:
6263 Fullscreen,
6364 };
6465
66+ enum OrientationAngle {
67+ Angle0 = 0,
68+ Angle90 = 90,
69+ Angle180 = 180,
70+ Angle270 = 270
71+ };
72+
6573 ~MirSurfaceItem();
6674
6775 //getters
@@ -70,9 +78,9 @@ public:
7078 State state() const { return m_state; }
7179 QString name() const { return m_name; }
7280 bool live() const { return m_live; }
73− Qt::ScreenOrientation orientation() const { return m_orientation; }
81+ OrientationAngle orientationAngle() const { return m_orientationAngle; }
7482
75− void setOrientation(const Qt::ScreenOrientation orientation);
83+ void setOrientationAngle(OrientationAngle angle);
7684
7785 void setSession(Session* item);
7886 void setScreenshot(const QUrl& screenshot);
@@ -91,29 +99,14 @@ Q_SIGNALS:
9199 void typeChanged(Type);
92100 void stateChanged(State);
93101 void liveChanged(bool live);
94− void orientationChanged();
102+ void orientationAngleChanged(OrientationAngle angle);
95103 void touchPressCountChanged(int count);
96104 void touchReleaseCountChanged(int count);
97105
98− void inputMethodRequested();
99− void inputMethodDismissed();
100−
101106 // internal mock use
102107 void deregister();
103108
104109protected:
105− void touchEvent(QTouchEvent * event) override;
106−
107− // becaue the default implementation ignores the events
108− void mousePressEvent(QMouseEvent *) override {}
109− void mouseMoveEvent(QMouseEvent *) override {}
110− void mouseReleaseEvent(QMouseEvent *) override {}
111−
112−private Q_SLOTS:
113− void onFocusChanged();
114− void onComponentStatusChanged(QQmlComponent::Status status);
115−
116−private:
117110 explicit MirSurfaceItem(const QString& name,
118111 Type type,
119112 State state,
@@ -121,6 +114,12 @@ private:
121114 const QString &qmlFilePath = QString(),
122115 QQuickItem *parent = 0);
123116
117+ void touchEvent(QTouchEvent * event) override;
118+
119+private Q_SLOTS:
120+ void onComponentStatusChanged(QQmlComponent::Status status);
121+
122+private:
124123 void createQmlContentItem();
125124 void printComponentErrors();
126125
@@ -129,19 +128,20 @@ private:
129128 const Type m_type;
130129 State m_state;
131130 bool m_live;
132− Qt::ScreenOrientation m_orientation;
133− int m_touchPressCount;
134− int m_touchReleaseCount;
131+ OrientationAngle m_orientationAngle;
135132
136133 QQmlComponent *m_qmlContentComponent;
137134 QQuickItem *m_qmlItem;
138135 QUrl m_screenshotUrl;
139136
137+ int m_touchPressCount;
138+ int m_touchReleaseCount;
139+
140140 friend class SurfaceManager;
141141};
142142
143143Q_DECLARE_METATYPE(MirSurfaceItem*)
144144Q_DECLARE_METATYPE(QList<MirSurfaceItem*>)
145−Q_DECLARE_METATYPE(Qt::ScreenOrientation)
145+Q_DECLARE_METATYPE(MirSurfaceItem::OrientationAngle)
146146
147147#endif // MIRSURFACEITEM_H
tests/mocks/Unity/Application/MirSurfaceItem.qml+12−14
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2014 Canonical Ltd.
2+ * Copyright 2014-2015 Canonical Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU Lesser General Public License as published by
@@ -23,19 +23,11 @@ Rectangle {
2323 implicitWidth: units.gu(40)
2424 implicitHeight: units.gu(70)
2525
26− rotation: {
27− if (orientation == Qt.PortraitOrientation) return 0;
28− else if (orientation == Qt.LandscapeOrientation) return 90;
29− else if (orientation == Qt.InvertedPortraitOrientation) return 180;
30− else return 270;
31− }
32− x: parent ? (parent.width - width) / 2 : 0
33− y: parent ? (parent.height - height) / 2 : 0
34− width: parent ? (rotation == 0 || rotation == 180 ? parent.width : parent.height) : implicitWidth
35− height: parent ? (rotation == 0 || rotation == 180 ? parent.height : parent.width) : implicitHeight
26+ width: parent ? parent.width : implicitWidth
27+ height: parent ? parent.height : implicitHeight
3628
3729 property alias screenshotSource: screenshotImage.source
38− property int orientation: Qt.PortraitOrientation
30+ property int orientationAngle
3931
4032 Image {
4133 id: screenshotImage
@@ -50,6 +42,7 @@ Rectangle {
5042 fontSizeMode: Text.Fit
5143 minimumPixelSize: 10
5244 verticalAlignment: Text.AlignVCenter
45+ rotation: surfaceText.rotation
5346 x: surfaceText.x
5447 y: surfaceText.y
5548 width: surfaceText.width
@@ -59,12 +52,17 @@ Rectangle {
5952 }
6053 Text {
6154 id: surfaceText
62− anchors.fill: parent
63− text: "SURFACE"
55+ text: "SURFACE " + root.width + "," + root.height
6456 color: root.parent && root.parent.activeFocus ? "yellow" : "blue"
6557 font.bold: true
6658 fontSizeMode: Text.Fit
6759 minimumPixelSize: 10; font.pixelSize: 200
6860 verticalAlignment: Text.AlignVCenter
61+
62+ rotation: root.orientationAngle
63+ x: (parent.width - width) / 2
64+ y: (parent.height - height) / 2
65+ width: (rotation == 0 || rotation == 180 ? parent.width : parent.height)
66+ height:(rotation == 0 || rotation == 180 ? parent.height : parent.width)
6967 }
7068}
tests/mocks/Unity/Application/SurfaceManager.cpp+4−34
@@ -16,8 +16,9 @@
1616
1717#include "SurfaceManager.h"
1818
19+#include "VirtualKeyboard.h"
20+
1921#include <paths.h>
20−#include "MirSurfaceItem.h"
2122
2223SurfaceManager *SurfaceManager::the_surface_manager = nullptr;
2324
@@ -40,10 +41,7 @@ MirSurfaceItem *SurfaceManager::createSurface(const QString& name,
4041 MirSurfaceItem::State state,
4142 const QUrl& screenshot)
4243{
43− MirSurfaceItem* surface = new MirSurfaceItem(name,
44− type,
45− state,
46− screenshot);
44+ MirSurfaceItem* surface = new MirSurfaceItem(name, type, state, screenshot);
4745 Q_EMIT surfaceCreated(surface);
4846 return surface;
4947}
@@ -57,40 +55,12 @@ void SurfaceManager::registerSurface(MirSurfaceItem *surface)
5755 surface->setLive(false);
5856 Q_EMIT surfaceDestroyed(surface);
5957 });
60− connect(surface, &MirSurfaceItem::inputMethodRequested,
61− this, &SurfaceManager::showInputMethod);
62− connect(surface, &MirSurfaceItem::inputMethodDismissed,
63− this, &SurfaceManager::hideInputMethod);
64−}
65−
66−void SurfaceManager::showInputMethod()
67−{
68− inputMethodSurface()->setState(MirSurfaceItem::Restored);
69−}
70−
71−void SurfaceManager::hideInputMethod()
72−{
73− if (m_virtualKeyboard) {
74− m_virtualKeyboard->setState(MirSurfaceItem::Minimized);
75− }
7658}
7759
7860MirSurfaceItem *SurfaceManager::inputMethodSurface()
7961{
8062 if (!m_virtualKeyboard) {
81−
82− QString screenshotPath = QString("file://%1/Dash/graphics/phone/screenshots/vkb_portrait.png")
83− .arg(qmlDirectory());
84−
85− QString qmlFilePath = QString("%1/Unity/Application/VirtualKeyboard.qml")
86− .arg(mockPluginsDir());
87−
88− m_virtualKeyboard = new MirSurfaceItem(
89− "input-method",
90− MirSurfaceItem::InputMethod,
91− MirSurfaceItem::Minimized,
92− screenshotPath,
93− qmlFilePath);
63+ m_virtualKeyboard = new VirtualKeyboard;
9464 Q_EMIT surfaceCreated(m_virtualKeyboard);
9565 }
9666 return m_virtualKeyboard;
tests/mocks/Unity/Application/SurfaceManager.h+2−5
@@ -20,6 +20,7 @@
2020#include <QObject>
2121
2222#include "MirSurfaceItem.h"
23+#include "VirtualKeyboard.h"
2324
2425class SurfaceManager : public QObject
2526{
@@ -45,13 +46,9 @@ Q_SIGNALS:
4546 void surfaceCreated(MirSurfaceItem *surface);
4647 void surfaceDestroyed(MirSurfaceItem *surface);
4748
48−private Q_SLOTS:
49− void showInputMethod();
50− void hideInputMethod();
51−
5249private:
5350 static SurfaceManager *the_surface_manager;
54− MirSurfaceItem *m_virtualKeyboard;
51+ VirtualKeyboard *m_virtualKeyboard;
5552};
5653
5754#endif // SURFACEMANAGER_H
tests/mocks/Unity/Application/UbuntuKeyboardInfo.cpp+33−1
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
33 *
44 * This program is free software: you can redistribute it and/or modify it under
55 * the terms of the GNU Lesser General Public License version 3, as published by
@@ -26,3 +26,35 @@ UbuntuKeyboardInfo::UbuntuKeyboardInfo(QObject *parent)
2626 m_height(200)
2727{
2828}
29+
30+void UbuntuKeyboardInfo::setX(qreal value)
31+{
32+ if (value != m_x) {
33+ m_x = value;
34+ Q_EMIT xChanged(m_x);
35+ }
36+}
37+
38+void UbuntuKeyboardInfo::setY(qreal value)
39+{
40+ if (value != m_y) {
41+ m_y = value;
42+ Q_EMIT yChanged(m_y);
43+ }
44+}
45+
46+void UbuntuKeyboardInfo::setWidth(qreal value)
47+{
48+ if (value != m_width) {
49+ m_width = value;
50+ Q_EMIT widthChanged(m_width);
51+ }
52+}
53+
54+void UbuntuKeyboardInfo::setHeight(qreal value)
55+{
56+ if (value != m_height) {
57+ m_height = value;
58+ Q_EMIT heightChanged(m_height);
59+ }
60+}
tests/mocks/Unity/Application/UbuntuKeyboardInfo.h+11−5
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
33 *
44 * This program is free software: you can redistribute it and/or modify it under
55 * the terms of the GNU Lesser General Public License version 3, as published by
@@ -22,10 +22,10 @@
2222
2323class UbuntuKeyboardInfo : public QObject {
2424 Q_OBJECT
25− Q_PROPERTY(qreal x READ x NOTIFY xChanged)
26− Q_PROPERTY(qreal y READ y NOTIFY yChanged)
27− Q_PROPERTY(qreal width READ width NOTIFY widthChanged)
28− Q_PROPERTY(qreal height READ height NOTIFY heightChanged)
25+ Q_PROPERTY(qreal x READ x WRITE setX NOTIFY xChanged)
26+ Q_PROPERTY(qreal y READ y WRITE setY NOTIFY yChanged)
27+ Q_PROPERTY(qreal width READ width WRITE setWidth NOTIFY widthChanged)
28+ Q_PROPERTY(qreal height READ height WRITE setHeight NOTIFY heightChanged)
2929public:
3030 UbuntuKeyboardInfo(QObject *parent = 0);
3131 virtual ~UbuntuKeyboardInfo() {}
@@ -34,6 +34,12 @@ public:
3434 qreal width() const { return m_width; }
3535 qreal height() const { return m_height; }
3636
37+ // Just in the fake implementation
38+ void setX(qreal value);
39+ void setY(qreal value);
40+ void setWidth(qreal value);
41+ void setHeight(qreal value);
42+
3743 static UbuntuKeyboardInfo *singleton() {
3844 if (!m_instance) {
3945 m_instance = new UbuntuKeyboardInfo;
tests/mocks/Unity/Application/VirtualKeyboard.cppnew file+44
@@ -0,0 +1,44 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "VirtualKeyboard.h"
18+
19+#include <paths.h>
20+
21+#include <QString>
22+
23+#include <QDebug>
24+
25+VirtualKeyboard::VirtualKeyboard(QQuickItem *parent)
26+ : MirSurfaceItem("input-method",
27+ MirSurfaceItem::InputMethod,
28+ MirSurfaceItem::Minimized,
29+ QString("file://%1/Dash/graphics/phone/screenshots/vkb_portrait.png")
30+ .arg(qmlDirectory()),
31+ QString("%1/Unity/Application/VirtualKeyboard.qml")
32+ .arg(mockPluginsDir()),
33+ parent)
34+{
35+}
36+
37+VirtualKeyboard::~VirtualKeyboard()
38+{
39+}
40+
41+void VirtualKeyboard::touchEvent(QTouchEvent *event)
42+{
43+ event->setAccepted(false);
44+}
tests/mocks/Unity/Application/VirtualKeyboard.hnew file+42
@@ -0,0 +1,42 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef VIRTUALKEYBOARD_H
18+#define VIRTUALKEYBOARD_H
19+
20+#include "MirSurfaceItem.h"
21+#include "MirSurfaceItemModel.h"
22+
23+#include <QQuickItem>
24+#include <QUrl>
25+
26+class Session;
27+
28+class VirtualKeyboard : public MirSurfaceItem
29+{
30+ Q_OBJECT
31+public:
32+ explicit VirtualKeyboard(QQuickItem *parent = 0);
33+ ~VirtualKeyboard();
34+
35+protected:
36+ void touchEvent(QTouchEvent * event) override;
37+};
38+
39+Q_DECLARE_METATYPE(VirtualKeyboard*)
40+Q_DECLARE_METATYPE(QList<VirtualKeyboard*>)
41+
42+#endif // VIRTUALKEYBOARD_H
tests/mocks/Unity/Application/VirtualKeyboard.qml+7−1
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2014 Canonical Ltd.
2+ * Copyright 2014-2015 Canonical Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU Lesser General Public License as published by
@@ -15,6 +15,7 @@
1515 */
1616
1717import QtQuick 2.0
18+import Unity.Application 0.1
1819
1920Item {
2021 implicitWidth: units.gu(40)
@@ -37,4 +38,9 @@ Item {
3738 anchors.fill: parent
3839 }
3940 }
41+
42+ Binding { target: UbuntuKeyboardInfo; property: "x"; value: screenshotImage.x}
43+ Binding { target: UbuntuKeyboardInfo; property: "y"; value: screenshotImage.y}
44+ Binding { target: UbuntuKeyboardInfo; property: "width"; value: screenshotImage.width}
45+ Binding { target: UbuntuKeyboardInfo; property: "height"; value: screenshotImage.height}
4046}
tests/mocks/Unity/Launcher/MockLauncherModel.cpp+3−1
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2013 Canonical Ltd.
2+ * Copyright 2013,2015 Canonical Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU Lesser General Public License as published by
@@ -35,6 +35,8 @@ MockLauncherModel::MockLauncherModel(QObject* parent): LauncherModelInterface(pa
3535 item->setProgress(50);
3636 item->setCountVisible(true);
3737 m_list.append(item);
38+ item = new MockLauncherItem("music-app", "/usr/share/applications/music-app.desktop", "Music", "soundcloud", this);
39+ m_list.append(item);
3840 item = new MockLauncherItem("facebook-webapp", "/usr/share/applications/facebook-webapp.desktop", "Facebook", "facebook", this);
3941 item->setProgress(150);
4042 m_list.append(item);
tests/mocks/Utils/CMakeLists.txt+1
@@ -16,6 +16,7 @@ set(QMLPLUGIN_SRC
1616 ${CMAKE_SOURCE_DIR}/plugins/Utils/timeformatter.cpp
1717 ${CMAKE_SOURCE_DIR}/plugins/Utils/unitymenumodelpaths.cpp
1818 ${CMAKE_SOURCE_DIR}/plugins/Utils/windowkeysfilter.cpp
19+ ${CMAKE_SOURCE_DIR}/plugins/Utils/windowscreenshotprovider.cpp
1920 ${CMAKE_SOURCE_DIR}/plugins/Utils/easingcurve.cpp
2021 constants.cpp
2122 plugin.cpp
tests/mocks/Utils/plugin.cpp+3
@@ -34,6 +34,7 @@
3434#include <timeformatter.h>
3535#include <unitymenumodelpaths.h>
3636#include <windowkeysfilter.h>
37+#include <windowscreenshotprovider.h>
3738#include <easingcurve.h>
3839
3940static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
@@ -70,4 +71,6 @@ void FakeUtilsPlugin::registerTypes(const char *uri)
7071void FakeUtilsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
7172{
7273 QQmlExtensionPlugin::initializeEngine(engine, uri);
74+
75+ engine->addImageProvider(QLatin1String("window"), new WindowScreenshotProvider);
7376}
tests/plugins/AccountsService/CMakeLists.txt+5−4
@@ -43,12 +43,13 @@ add_executable(test-accountsservice
4343 )
4444qt5_use_modules(test-accountsservice Core DBus Test)
4545
46−add_test(NAME test-accountsservice
47− COMMAND dbus-test-runner
48− --task ${CMAKE_CURRENT_BINARY_DIR}/mock-server
46+add_unity8_unittest(AccountsService dbus-test-runner
47+ ARG_PREFIX "--parameter"
48+ ARGS
49+ --task $<TARGET_FILE:mock-server>
4950 --task-name server
5051 --ignore-return
51− --task ${CMAKE_CURRENT_BINARY_DIR}/test-accountsservice
52+ --task $<TARGET_FILE:test-accountsservice>
5253 --task-name client
5354 --wait-for org.freedesktop.Accounts
5455 )
tests/plugins/Dash/CMakeLists.txt+10−17
@@ -32,7 +32,7 @@ macro(add_lvwph_test FILENAME TESTNAME)
3232 qt5_use_modules(${TESTNAME}TestExec Test Core Qml)
3333 target_link_libraries(${TESTNAME}TestExec ${Qt5Gui_LIBRARIES} ${Qt5Quick_LIBRARIES})
3434
35− add_binary_qml_test(${TESTNAME} "" "Dash-qml" "")
35+ add_unity8_uitest(${TESTNAME} ${TESTNAME}TestExec DEPENDS Dash-qml)
3636endmacro()
3737
3838add_lvwph_test(listviewwithpageheader ListViewWithPageHeader)
@@ -48,19 +48,16 @@ macro(add_dashview_try_test FILENAME TESTNAME)
4848 qt5_use_modules(${TESTNAME}TestExec Test Core Qml)
4949 target_link_libraries(${TESTNAME}TestExec ${Qt5Gui_LIBRARIES} ${Qt5Quick_LIBRARIES})
5050
51− add_binary_qml_test(${TESTNAME} "" "Dash-qml" "")
51+ add_unity8_uitest(${TESTNAME} ${TESTNAME}TestExec DEPENDS Dash-qml)
5252
53− add_executable(${TESTNAME}tryExec
53+ add_executable(${TESTNAME}TryExec
5454 ${FILENAME}try.cpp
5555 ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/Dash/${FILENAME}.cpp
5656 ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/Dash/abstractdashview.cpp
5757 )
58− qt5_use_modules(${TESTNAME}tryExec Test Core Qml)
59− target_link_libraries(${TESTNAME}tryExec ${Qt5Gui_LIBRARIES} ${Qt5Quick_LIBRARIES})
60− add_custom_target(try${TESTNAME}
61− ${CMAKE_CURRENT_BINARY_DIR}/${TESTNAME}tryExec
62− DEPENDS ${TESTNAME}tryExec Dash-qml
63− )
58+ qt5_use_modules(${TESTNAME}TryExec Test Core Qml)
59+ target_link_libraries(${TESTNAME}TryExec ${Qt5Gui_LIBRARIES} ${Qt5Quick_LIBRARIES})
60+ add_manual_test(${TESTNAME} ${TESTNAME}TryExec DEPENDS Dash-qml)
6461endmacro()
6562
6663add_dashview_try_test(verticaljournal VerticalJournal)
@@ -71,13 +68,9 @@ add_dashview_try_test(organicgrid OrganicGrid)
7168add_executable(CardCreatorTestExec cardcreatortest.cpp)
7269qt5_use_modules(CardCreatorTestExec Test Core Qml)
7370target_link_libraries(CardCreatorTestExec ${Qt5Gui_LIBRARIES} ${Qt5Quick_LIBRARIES})
74−add_binary_qml_test(CardCreator "" "Dash-qml" "QML2_IMPORT_PATH=${CMAKE_BINARY_DIR}/tests/mocks/")
71+add_unity8_uitest(CardCreator CardCreatorTestExec DEPENDS Dash-qml)
7572
7673# plain qml test
77−set(qmltest_DEFAULT_TARGETS qmluitests)
78−set(qmltest_DEFAULT_NO_ADD_TEST TRUE)
79−set(qmltest_DEFAULT_PROPERTIES ENVIRONMENT "LC_ALL=C")
80−add_qml_test(. ScopeStyle IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
81−add_qml_test(. ListViewWithPageHeaderQML IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
82−add_qml_test(. CardAttributes IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
83−add_qml_test(. CroppedImageMinimumSourceSize IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
74+foreach(dash_test ScopeStyle ListViewWithPageHeaderQML CardAttributes CroppedImageMinimumSourceSize)
75+ add_unity8_qmltest(. ${dash_test})
76+endforeach()
tests/plugins/Dash/cardcreator/1.res+3−2
@@ -1,6 +1,5 @@
11AbstractButton {
22 id: root;
3− property var template;
43 property var components;
54 property var cardData;
65 property var artShapeBorderSource: undefined;
@@ -13,7 +12,7 @@ AbstractButton {
1312 property bool asynchronous: true;
1413 property bool showHeader: true;
1514 implicitWidth: childrenRect.width;
16− enabled: root.template == null ? true : (root.template["non-interactive"] !== undefined ? !root.template["non-interactive"] : true);
15+ enabled: true;
1716
1817onArtShapeBorderSourceChanged: { if (artShapeBorderSource !== undefined && artShapeLoader.item) artShapeLoader.item.borderSource = artShapeBorderSource; }
1918readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
@@ -34,6 +33,7 @@ Item {
3433 property bool doShapeItem: components["art"]["conciergeMode"] !== true;
3534 visible: image.status == Image.Ready;
3635 readonly property alias image: artImage.image;
36+ property alias borderSource: artShapeShape.borderSource;
3737 ShaderEffectSource {
3838 id: artShapeSource;
3939 sourceItem: artImage;
@@ -43,6 +43,7 @@ Item {
4343 hideSource: doShapeItem;
4444 }
4545 Shape {
46+ id: artShapeShape;
4647 image: artShapeSource;
4748 anchors.fill: parent;
4849 visible: doShapeItem;
tests/plugins/Dash/cardcreator/2.res+3−8
@@ -1,6 +1,5 @@
11AbstractButton {
22 id: root;
3− property var template;
43 property var components;
54 property var cardData;
65 property var artShapeBorderSource: undefined;
@@ -13,7 +12,7 @@ AbstractButton {
1312 property bool asynchronous: true;
1413 property bool showHeader: true;
1514 implicitWidth: childrenRect.width;
16− enabled: root.template == null ? true : (root.template["non-interactive"] !== undefined ? !root.template["non-interactive"] : true);
15+ enabled: true;
1716
1817Loader {
1918 id: backgroundLoader;
@@ -33,18 +32,14 @@ Loader {
3332 objectName: "backgroundImage";
3433 source: {
3534 if (cardData && typeof cardData["background"] === "string") return cardData["background"];
36− else if (template && typeof template["card-background"] === "string") return template["card-background"];
37− else return "";
35+ else return "";
3836 }
3937 }
4038 function getColor(index) {
4139 if (cardData && typeof cardData["background"] === "object"
4240 && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
4341 return cardData["background"]["elements"][index];
44− } else if (template && typeof template["card-background"] === "object"
45− && (template["card-background"]["type"] === "color" || template["card-background"]["type"] === "gradient")) {
46− return template["card-background"]["elements"][index];
47− } else return undefined;
42+ } else return index === 0 ? "#E9E9E9" : undefined;
4843 }
4944 }
5045 }
tests/plugins/Dash/cardcreator/3.res+3−2
@@ -1,6 +1,5 @@
11AbstractButton {
22 id: root;
3− property var template;
43 property var components;
54 property var cardData;
65 property var artShapeBorderSource: undefined;
@@ -13,7 +12,7 @@ AbstractButton {
1312 property bool asynchronous: true;
1413 property bool showHeader: true;
1514 implicitWidth: childrenRect.width;
16− enabled: root.template == null ? true : (root.template["non-interactive"] !== undefined ? !root.template["non-interactive"] : true);
15+ enabled: true;
1716
1817onArtShapeBorderSourceChanged: { if (artShapeBorderSource !== undefined && artShapeLoader.item) artShapeLoader.item.borderSource = artShapeBorderSource; }
1918readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
@@ -34,6 +33,7 @@ Item {
3433 property bool doShapeItem: components["art"]["conciergeMode"] !== true;
3534 visible: image.status == Image.Ready;
3635 readonly property alias image: artImage.image;
36+ property alias borderSource: artShapeShape.borderSource;
3737 ShaderEffectSource {
3838 id: artShapeSource;
3939 sourceItem: artImage;
@@ -43,6 +43,7 @@ Item {
4343 hideSource: doShapeItem;
4444 }
4545 Shape {
46+ id: artShapeShape;
4647 image: artShapeSource;
4748 anchors.fill: parent;
4849 visible: doShapeItem;
tests/plugins/Dash/cardcreator/4.res+1−2
@@ -1,6 +1,5 @@
11AbstractButton {
22 id: root;
3− property var template;
43 property var components;
54 property var cardData;
65 property var artShapeBorderSource: undefined;
@@ -13,7 +12,7 @@ AbstractButton {
1312 property bool asynchronous: true;
1413 property bool showHeader: true;
1514 implicitWidth: childrenRect.width;
16− enabled: root.template == null ? true : (root.template["non-interactive"] !== undefined ? !root.template["non-interactive"] : true);
15+ enabled: true;
1716
1817readonly property size artShapeSize: Qt.size(-1, -1);
1918readonly property int headerHeight: row.height;
tests/plugins/Dash/cardcreator/5.res+3−2
@@ -1,6 +1,5 @@
11AbstractButton {
22 id: root;
3− property var template;
43 property var components;
54 property var cardData;
65 property var artShapeBorderSource: undefined;
@@ -13,7 +12,7 @@ AbstractButton {
1312 property bool asynchronous: true;
1413 property bool showHeader: true;
1514 implicitWidth: childrenRect.width;
16− enabled: root.template == null ? true : (root.template["non-interactive"] !== undefined ? !root.template["non-interactive"] : true);
15+ enabled: false;
1716
1817onArtShapeBorderSourceChanged: { if (artShapeBorderSource !== undefined && artShapeLoader.item) artShapeLoader.item.borderSource = artShapeBorderSource; }
1918readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
@@ -34,6 +33,7 @@ Item {
3433 property bool doShapeItem: components["art"]["conciergeMode"] !== true;
3534 visible: image.status == Image.Ready;
3635 readonly property alias image: artImage.image;
36+ property alias borderSource: artShapeShape.borderSource;
3737 ShaderEffectSource {
3838 id: artShapeSource;
3939 sourceItem: artImage;
@@ -43,6 +43,7 @@ Item {
4343 hideSource: doShapeItem;
4444 }
4545 Shape {
46+ id: artShapeShape;
4647 image: artShapeSource;
4748 anchors.fill: parent;
4849 visible: doShapeItem;
tests/plugins/Dash/cardcreator/5.tst+1−1
@@ -1,3 +1,3 @@
1−template: {"card-layout":"vertical","card-size":"medium","category-layout":"carousel","collapsed-rows":2,"overlay":true}
1+template: {"non-interactive":true,"card-layout":"vertical","card-size":"medium","category-layout":"carousel","collapsed-rows":2,"overlay":true}
22components: {"art":{"aspect-ratio":1,"field":"art"},"subtitle":{"field":"artist"},"title":{"field":"title"},"attributes":{}}
33result: 5.res
tests/plugins/Dash/cardcreator/6.res+3−8
@@ -1,6 +1,5 @@
11AbstractButton {
22 id: root;
3− property var template;
43 property var components;
54 property var cardData;
65 property var artShapeBorderSource: undefined;
@@ -13,7 +12,7 @@ AbstractButton {
1312 property bool asynchronous: true;
1413 property bool showHeader: true;
1514 implicitWidth: childrenRect.width;
16− enabled: root.template == null ? true : (root.template["non-interactive"] !== undefined ? !root.template["non-interactive"] : true);
15+ enabled: true;
1716
1817Loader {
1918 id: backgroundLoader;
@@ -33,18 +32,14 @@ Loader {
3332 objectName: "backgroundImage";
3433 source: {
3534 if (cardData && typeof cardData["background"] === "string") return cardData["background"];
36− else if (template && typeof template["card-background"] === "string") return template["card-background"];
37− else return "";
35+ else return "http://assets.ubuntu.com/sites/ubuntu/latest/u/img/logos/logo-ubuntu-grey.png";
3836 }
3937 }
4038 function getColor(index) {
4139 if (cardData && typeof cardData["background"] === "object"
4240 && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
4341 return cardData["background"]["elements"][index];
44− } else if (template && typeof template["card-background"] === "object"
45− && (template["card-background"]["type"] === "color" || template["card-background"]["type"] === "gradient")) {
46− return template["card-background"]["elements"][index];
47− } else return undefined;
42+ } else return index === 0 ? undefined : undefined;
4843 }
4944 }
5045 }
tests/plugins/Dash/cardcreator/6.tst+1−1
@@ -1,3 +1,3 @@
1−template: {"card-background":{"elements":["#E9E9E9"],"type":"color"},"card-layout":"vertical","card-size":"medium","category-layout":"grid","collapsed-rows":2}
1+template: {"card-background":"http://assets.ubuntu.com/sites/ubuntu/latest/u/img/logos/logo-ubuntu-grey.png","card-layout":"vertical","card-size":"medium","category-layout":"grid","collapsed-rows":2}
22components: {"art":{"aspect-ratio":1},"background":{"field":"background"},"subtitle":{"field":"author"},"title":{"field":"title"},"emblem":{"field":"source"},"attributes":{}}
33result: 6.res
tests/plugins/Dash/cardcreator/7.res+3−8
@@ -1,6 +1,5 @@
11AbstractButton {
22 id: root;
3− property var template;
43 property var components;
54 property var cardData;
65 property var artShapeBorderSource: undefined;
@@ -13,7 +12,7 @@ AbstractButton {
1312 property bool asynchronous: true;
1413 property bool showHeader: true;
1514 implicitWidth: childrenRect.width;
16− enabled: root.template == null ? true : (root.template["non-interactive"] !== undefined ? !root.template["non-interactive"] : true);
15+ enabled: true;
1716
1817Loader {
1918 id: backgroundLoader;
@@ -33,18 +32,14 @@ Loader {
3332 objectName: "backgroundImage";
3433 source: {
3534 if (cardData && typeof cardData["background"] === "string") return cardData["background"];
36− else if (template && typeof template["card-background"] === "string") return template["card-background"];
37− else return "";
35+ else return "";
3836 }
3937 }
4038 function getColor(index) {
4139 if (cardData && typeof cardData["background"] === "object"
4240 && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
4341 return cardData["background"]["elements"][index];
44− } else if (template && typeof template["card-background"] === "object"
45− && (template["card-background"]["type"] === "color" || template["card-background"]["type"] === "gradient")) {
46− return template["card-background"]["elements"][index];
47− } else return undefined;
42+ } else return index === 0 ? "#E9E9E9" : "#E9AAE9";
4843 }
4944 }
5045 }
tests/plugins/Dash/cardcreator/7.tst+1−1
@@ -1,3 +1,3 @@
1−template: {"card-background":{"elements":["#E9E9E9"],"type":"color"},"card-layout":"vertical","card-size":"medium","category-layout":"grid","collapsed-rows":2}
1+template: {"card-background":{"elements":["#E9E9E9","#E9AAE9"],"type":"color"},"card-layout":"vertical","card-size":"medium","category-layout":"grid","collapsed-rows":2}
22components: {"art":{"aspect-ratio":1},"background":{"field":"background"},"mascot":{"field":"icon"},"subtitle":{"field":"author"},"title":{"field":"title"},"attributes":{"field":"attributes","max-count":2}}
33result: 7.res
tests/plugins/Greeter/Unity/Launcher/CMakeLists.txt+5−6
@@ -1,5 +1,5 @@
11pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=6)
2−pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=5)
2+pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=6)
33
44include_directories(
55 ${CMAKE_CURRENT_SOURCE_DIR}
@@ -13,11 +13,6 @@ add_definitions(-DSRCDIR="${CMAKE_CURRENT_SOURCE_DIR}")
1313add_definitions(-DLAUNCHER_TESTING)
1414
1515### LauncherModelASTest
16−set(testModelCommand dbus-test-runner --task ${CMAKE_CURRENT_BINARY_DIR}/launchermodelastestExec
17− --parameter -o --parameter ${CMAKE_BINARY_DIR}/launchermodeltest.xml,xunitxml
18− --parameter -o --parameter -,txt)
19−add_test(NAME launchermodelastest COMMAND ${testModelCommand})
20−add_custom_target(launchermodelastest ${testModelCommand})
2116add_executable(launchermodelastestExec
2217 launchermodelastest.cpp
2318 AccountsServiceDBusAdaptor.cpp
@@ -36,6 +31,10 @@ target_link_libraries(launchermodelastestExec
3631 ${GSETTINGS_QT_LDFLAGS}
3732 )
3833qt5_use_modules(launchermodelastestExec Test Core DBus Gui)
34+add_unity8_unittest(LauncherModelAS dbus-test-runner
35+ ARG_PREFIX "--parameter"
36+ ARGS --task $<TARGET_FILE:launchermodelastestExec>
37+)
3938
4039# copy .desktop files into build directory for shadow builds
4140file(GLOB DESKTOP_FILES *.desktop)
tests/plugins/LightDM/CMakeLists.txt+13−4
@@ -18,7 +18,13 @@ target_include_directories(GreeterDBusTestExec PUBLIC
1818 ${CMAKE_SOURCE_DIR}/plugins/LightDM
1919 ${CMAKE_SOURCE_DIR}/tests/mocks/LightDM
2020 )
21−add_binary_qml_test(GreeterDBus "${CMAKE_BINARY_DIR}/tests/mocks/LightDM/liblightdm" MockLightDM "QML2_IMPORT_PATH=${CMAKE_BINARY_DIR}/tests/mocks")
21+add_unity8_uitest(GreeterDBus dbus-test-runner
22+ ARG_PREFIX "--parameter"
23+ ARGS --task $<TARGET_FILE:GreeterDBusTestExec>
24+ DEPENDS MockLightDM
25+ IMPORT_PATHS ${CMAKE_BINARY_DIR}/tests/mocks
26+ LIGHTDM
27+ )
2228
2329add_executable(GreeterPamTestExec
2430 pam.cpp
@@ -31,8 +37,7 @@ target_link_libraries(GreeterPamTestExec
3137target_include_directories(GreeterPamTestExec PUBLIC
3238 ${CMAKE_SOURCE_DIR}/plugins/LightDM/liblightdm
3339 )
34−add_qmltest_target(testGreeterPam "${CMAKE_CURRENT_BINARY_DIR}/GreeterPamTestExec" TRUE FALSE)
35−add_dependencies(testGreeterPam GreeterPamTestExec)
40+add_qmltest_target(testGreeterPam GreeterPamTestExec COMMAND $<TARGET_FILE:GreeterPamTestExec>)
3641
3742add_executable(GreeterUsersModelTestExec
3843 usersmodel.cpp
@@ -49,4 +54,8 @@ target_include_directories(GreeterUsersModelTestExec PUBLIC
4954 ${CMAKE_SOURCE_DIR}/plugins/Utils
5055 ${CMAKE_SOURCE_DIR}/tests/mocks/LightDM
5156 )
52−add_binary_qml_test(GreeterUsersModel "${CMAKE_BINARY_DIR}/tests/mocks/LightDM/liblightdm" MockLightDM "LIBLIGHTDM_MOCK_MODE=full")
57+add_unity8_uitest(GreeterUsers GreeterUsersModelTestExec
58+ ENVIRONMENT LIBLIGHTDM_MOCK_MODE=full
59+ DEPENDS MockLightDM
60+ LIGHTDM
61+ )
tests/plugins/Ubuntu/Gestures/CMakeLists.txt+22−24
@@ -12,43 +12,41 @@ include_directories(
1212 ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
1313 )
1414
15−file(GLOB qmlFiles *.qml)
16−add_custom_target(UbuntuGesturesTestQmlFiles ALL
17− COMMAND cp ${qmlFiles} ${CMAKE_CURRENT_BINARY_DIR}
18− DEPENDS ${qmlFiles}
19−)
15+export_qmlfiles(UbuntuGesturesTest .)
2016
2117add_definitions(-DUBUNTU_GESTURES_PLUGIN_DIR="${CMAKE_BINARY_DIR}/plugins")
2218add_definitions(-DTESTS_UTILS_MODULES_DIR="${CMAKE_BINARY_DIR}/tests/utils/modules")
2319
24−macro(add_gesture_ui_test CLASSNAME)
20+macro(build_gesture_test CLASSNAME)
2521 add_executable(${CLASSNAME}TestExec tst_${CLASSNAME}.cpp GestureTest.cpp TestItem.cpp)
2622 qt5_use_modules(${CLASSNAME}TestExec Test Core Qml Gui Quick)
2723 target_link_libraries(${CLASSNAME}TestExec UbuntuGesturesQml UbuntuGestures)
24+endmacro()
25+
26+macro(add_gesture_unit_test CLASSNAME)
27+ build_gesture_test(${CLASSNAME})
28+ add_unity8_unittest(${CLASSNAME} ${CLASSNAME}TestExec
29+ ENVIRONMENT LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/plugins/Ubuntu/Gestures
30+ DEPENDS UbuntuGesturesTest-qmlfiles
31+ ${ARGN}
32+ )
33+endmacro()
2834
29− add_binary_qml_test(${CLASSNAME} ${CMAKE_BINARY_DIR}/plugins/Ubuntu/Gestures UbuntuGesturesTestQmlFiles "")
35+macro(add_gesture_ui_test CLASSNAME)
36+ build_gesture_test(${CLASSNAME})
37+ add_unity8_uitest(${CLASSNAME} ${CLASSNAME}TestExec
38+ ENVIRONMENT LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/plugins/Ubuntu/Gestures
39+ DEPENDS UbuntuGesturesTest-qmlfiles
40+ ${ARGN}
41+ )
3042 add_manual_qml_test(. ${CLASSNAME} IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins
3143 ${CMAKE_BINARY_DIR}/tests/utils/modules)
32−endmacro(add_gesture_ui_test)
33−
34−macro(add_gesture_test CLASSNAME)
35− set(testCommand
36− ${CLASSNAME}TestExec -o ${CMAKE_BINARY_DIR}/test${CLASSNAME}.xml,xunitxml -o -,txt)
37−
38− add_test(NAME test${CLASSNAME} COMMAND ${testCommand})
39−
40− set_tests_properties(test${CLASSNAME} PROPERTIES
41− ENVIRONMENT LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/plugins/Ubuntu/Gestures)
42−
43− add_executable(${CLASSNAME}TestExec tst_${CLASSNAME}.cpp)
44− qt5_use_modules(${CLASSNAME}TestExec Test Core)
45− target_link_libraries(${CLASSNAME}TestExec UbuntuGesturesQml)
46−endmacro(add_gesture_test)
44+endmacro()
4745
4846add_gesture_ui_test(DirectionalDragArea)
4947add_gesture_ui_test(FloatingFlickable)
5048add_gesture_ui_test(PressedOutsideNotifier)
5149add_gesture_ui_test(TouchDispatcher)
5250add_gesture_ui_test(TouchGate)
53−add_gesture_test(Damper)
54−add_gesture_test(AxisVelocityCalculator)
51+add_gesture_unit_test(Damper)
52+add_gesture_unit_test(AxisVelocityCalculator)
tests/plugins/Unity/Indicators/CMakeLists.txt+9−12
@@ -17,13 +17,8 @@ macro(indicator_test TEST)
1717 set(multi_value_keywords ADDITIONAL_CPPS ADDITIONAL_LIBS)
1818 cmake_parse_arguments(test "" "" "${multi_value_keywords}" ${ARGN})
1919
20− set(testCommand ${CMAKE_CURRENT_BINARY_DIR}/${TEST}Exec
21− -o ${CMAKE_BINARY_DIR}/${TEST}.xml,xunitxml
22− -o -,txt)
23− add_test(NAME ${TEST} COMMAND ${testCommand})
24− add_custom_target(${TEST} ${testCommand})
2520 add_executable(${TEST}Exec
26− ${TEST}.cpp
21+ ${TEST}Test.cpp
2722 ${test_ADDITIONAL_CPPS}
2823 )
2924 qt5_use_modules(${TEST}Exec Test Core Qml)
@@ -33,11 +28,13 @@ macro(indicator_test TEST)
3328 ${GIO_LIBRARIES}
3429 ${QMENUMODEL_LDFLAGS}
3530 )
31+
32+ add_unity8_unittest(${TEST} ${TEST}Exec)
3633endmacro(indicator_test)
3734
38−indicator_test(indicatorsmanagertest ADDITIONAL_LIBS IndicatorsQml)
39−indicator_test(indicatorsmodeltest ADDITIONAL_LIBS IndicatorsQml)
40−indicator_test(menucontentactivatortest ADDITIONAL_CPPS ${INDICATORS_DIR}/menucontentactivator.cpp)
41−indicator_test(unitymenumodelstacktest ADDITIONAL_CPPS ${TEST_DIR}/mocks/QMenuModel/unitymenumodel.cpp ${INDICATORS_DIR}/unitymenumodelstack.cpp)
42−indicator_test(rootactionstatetest ADDITIONAL_LIBS IndicatorsQml)
43−indicator_test(sharedunitymenumodeltest ADDITIONAL_LIBS IndicatorsQml)
35+indicator_test(IndicatorsManager ADDITIONAL_LIBS IndicatorsQml)
36+indicator_test(IndicatorsModel ADDITIONAL_LIBS IndicatorsQml)
37+indicator_test(MenuContentActivator ADDITIONAL_CPPS ${INDICATORS_DIR}/menucontentactivator.cpp)
38+indicator_test(UnityMenuModelStack ADDITIONAL_CPPS ${TEST_DIR}/mocks/QMenuModel/unitymenumodel.cpp ${INDICATORS_DIR}/unitymenumodelstack.cpp)
39+indicator_test(RootActionState ADDITIONAL_LIBS IndicatorsQml)
40+indicator_test(SharedUnityMenuModel ADDITIONAL_LIBS IndicatorsQml)
tests/plugins/Unity/Indicators/indicatorsmanagertest.cpp → tests/plugins/Unity/Indicators/IndicatorsManagerTest.cpprenamed+1−1
@@ -180,4 +180,4 @@ private Q_SLOTS:
180180};
181181
182182QTEST_GUILESS_MAIN(IndicatorsManagerTest)
183−#include "indicatorsmanagertest.moc"
183+#include "IndicatorsManagerTest.moc"
tests/plugins/Unity/Indicators/indicatorsmodeltest.cpp → tests/plugins/Unity/Indicators/IndicatorsModelTest.cpprenamed+1−1
@@ -138,4 +138,4 @@ private Q_SLOTS:
138138
139139QTEST_GUILESS_MAIN(IndicatorsModelTest)
140140
141−#include "indicatorsmodeltest.moc"
141+#include "IndicatorsModelTest.moc"
tests/plugins/Unity/Indicators/menucontentactivatortest.cpp → tests/plugins/Unity/Indicators/MenuContentActivatorTest.cpprenamed+1−1
@@ -299,4 +299,4 @@ private:
299299};
300300
301301QTEST_GUILESS_MAIN(MenuConentActivatorTest)
302−#include "menucontentactivatortest.moc"
302+#include "MenuContentActivatorTest.moc"
tests/plugins/Unity/Indicators/rootactionstatetest.cpp → tests/plugins/Unity/Indicators/RootActionStateTest.cpprenamed+1−1
@@ -92,4 +92,4 @@ private Q_SLOTS:
9292};
9393
9494QTEST_GUILESS_MAIN(RootActionStateTest)
95−#include "rootactionstatetest.moc"
95+#include "RootActionStateTest.moc"
tests/plugins/Unity/Indicators/sharedunitymenumodeltest.cpp → tests/plugins/Unity/Indicators/SharedUnityMenuModelTest.cpprenamed+1−1
@@ -99,4 +99,4 @@ private Q_SLOTS:
9999};
100100
101101QTEST_GUILESS_MAIN(SharedUnityMenuModelTest)
102−#include "sharedunitymenumodeltest.moc"
102+#include "SharedUnityMenuModelTest.moc"
tests/plugins/Unity/Indicators/unitymenumodelstacktest.cpp → tests/plugins/Unity/Indicators/UnityMenuModelStackTest.cpprenamed+1−1
@@ -205,4 +205,4 @@ public:
205205};
206206
207207QTEST_GUILESS_MAIN(UnityMenuModelStackTest)
208−#include "unitymenumodelstacktest.moc"
208+#include "UnityMenuModelStackTest.moc"
tests/plugins/Unity/Launcher/CMakeLists.txt+5−6
@@ -1,6 +1,5 @@
11pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
22pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=6)
3−pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=5)
43
54include_directories(
65 ${CMAKE_CURRENT_SOURCE_DIR}
@@ -15,11 +14,6 @@ add_definitions(-DSRCDIR="${CMAKE_CURRENT_SOURCE_DIR}")
1514add_definitions(-DLAUNCHER_TESTING)
1615
1716### LauncherModelTest
18−set(testModelCommand dbus-test-runner --task ${CMAKE_CURRENT_BINARY_DIR}/launchermodeltestExec
19− --parameter -o --parameter ${CMAKE_BINARY_DIR}/launchermodeltest.xml,xunitxml
20− --parameter -o --parameter -,txt)
21−add_test(NAME launchermodeltest COMMAND ${testModelCommand})
22−add_custom_target(launchermodeltest ${testModelCommand})
2317add_executable(launchermodeltestExec
2418 launchermodeltest.cpp
2519 gsettings.cpp
@@ -43,6 +37,11 @@ target_link_libraries(launchermodeltestExec
4337 )
4438qt5_use_modules(launchermodeltestExec Test Core DBus Xml Gui)
4539
40+add_unity8_unittest(LauncherModel dbus-test-runner
41+ ARG_PREFIX "--parameter"
42+ ARGS --task $<TARGET_FILE:launchermodeltestExec>
43+)
44+
4645# copy .desktop files into build directory for shadow builds
4746file(GLOB DESKTOP_FILES *.desktop)
4847
tests/plugins/Unity/Launcher/launchermodeltest.cpp+3−1
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2013-2014 Canonical Ltd.
2+ * Copyright 2013-2015 Canonical Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU Lesser General Public License as published by
@@ -52,6 +52,8 @@ public:
5252 QColor splashColor() const override { return QColor(0,0,0,0); }
5353 QColor splashColorHeader() const override { return QColor(0,0,0,0); }
5454 QColor splashColorFooter() const override { return QColor(0,0,0,0); }
55+ Qt::ScreenOrientations supportedOrientations() const override { return Qt::PortraitOrientation; }
56+ bool rotatesWindowContents() const override { return false; }
5557
5658 // Methods used for mocking (not in the interface)
5759 void setFocused(bool focused) { m_focused = focused; Q_EMIT focusedChanged(focused); }
tests/plugins/Unity/Session/CMakeLists.txt+5−5
@@ -9,11 +9,6 @@ add_definitions(-DSRCDIR="${CMAKE_CURRENT_SOURCE_DIR}")
99add_definitions(-DSESSION_TESTING)
1010
1111### SessionBackendTest
12−set(testBackendCommand dbus-test-runner --task ${CMAKE_CURRENT_BINARY_DIR}/sessionbackendtestExec
13− --parameter -o --parameter ${CMAKE_BINARY_DIR}/sessionbackendtest.xml,xunitxml
14− --parameter -o --parameter -,txt)
15−add_test(NAME sessionbackendtest COMMAND ${testBackendCommand})
16−add_custom_target(sessionbackendtest ${testBackendCommand})
1712add_executable(sessionbackendtestExec
1813 sessionbackendtest.cpp
1914 ${CMAKE_SOURCE_DIR}/plugins/Unity/Session/dbusunitysessionservice.cpp
@@ -22,3 +17,8 @@ target_link_libraries(sessionbackendtestExec
2217 unity8-private
2318 )
2419qt5_use_modules(sessionbackendtestExec Test Core Qml DBus)
20+
21+add_unity8_unittest(SessionBackend dbus-test-runner
22+ ARG_PREFIX "--parameter"
23+ ARGS --task $<TARGET_FILE:sessionbackendtestExec>
24+)
tests/plugins/Utils/CMakeLists.txt+12−26
@@ -4,31 +4,17 @@ include_directories(
44 ${CMAKE_CURRENT_BINARY_DIR}
55 )
66
7−macro(run_tests)
8− set(_test_list "")
9− foreach(_test ${ARGN})
10− set(testCommand env "LC_ALL=C.UTF-8" ${CMAKE_CURRENT_BINARY_DIR}/${_test}Exec
11− -o ${CMAKE_BINARY_DIR}/${_test}.xml,xunitxml
12− -o -,txt)
13− add_test(NAME ${_test} COMMAND ${testCommand})
14− add_custom_target(${_test} ${testCommand})
15− add_executable(${_test}Exec ${_test}.cpp modeltest.cpp)
16− qt5_use_modules(${_test}Exec Test Core Qml)
17− target_link_libraries(${_test}Exec
18− Utils-qml
19− )
20− set(_test_list "${_test_list};${_test}")
21− endforeach()
22−endmacro(run_tests)
23−
24−run_tests(
25− qlimitproxymodeltest
26− unitysortfilterproxymodeltest
27− timeformattertest
28− homekeywatchertest
29− )
7+foreach(util_test
8+ QLimitProxyModel
9+ UnitySortFilterProxyModel
10+ TimeFormatter
11+ HomeKeyWatcher
12+)
13+ add_executable(${util_test}TestExec ${util_test}Test.cpp ModelTest.cpp)
14+ qt5_use_modules(${util_test}TestExec Test Core Qml)
15+ target_link_libraries(${util_test}TestExec Utils-qml)
16+ add_unity8_unittest(${util_test} ${util_test}TestExec ADD_TEST)
17+endforeach()
3018
3119# plain qml test
32−set(qmltest_DEFAULT_TARGETS qmltests)
33−set(qmltest_DEFAULT_PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=minimal")
34−add_qml_test(. UtilsStyle IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
20+add_unity8_qmlunittest(. UtilsStyle)
tests/plugins/Utils/homekeywatchertest.cpp → tests/plugins/Utils/HomeKeyWatcherTest.cpprenamed+1−1
@@ -320,4 +320,4 @@ AbstractTimer *FakeTimerFactory::create(QObject *parent)
320320
321321QTEST_GUILESS_MAIN(HomeKeyWatcherTest)
322322
323−#include "homekeywatchertest.moc"
323+#include "HomeKeyWatcherTest.moc"
tests/plugins/Utils/modeltest.cpp → tests/plugins/Utils/ModelTest.cpprenamed+1−1
@@ -47,7 +47,7 @@
4747
4848#include <QtGui/QtGui>
4949
50−#include "modeltest.h"
50+#include "ModelTest.h"
5151
5252#include <QtTest/QtTest>
5353
tests/plugins/Utils/modeltest.h → tests/plugins/Utils/ModelTest.hrenamed

The content is the same.

tests/plugins/Utils/qlimitproxymodeltest.cpp → tests/plugins/Utils/QLimitProxyModelTest.cpprenamed+2−2
@@ -16,7 +16,7 @@
1616
1717// local
1818#include "qlimitproxymodelqml.h"
19−#include "modeltest.h"
19+#include "ModelTest.h"
2020
2121// Qt
2222#include <QTest>
@@ -608,4 +608,4 @@ private Q_SLOTS:
608608
609609QTEST_GUILESS_MAIN(QLimitProxyModelTest)
610610
611−#include "qlimitproxymodeltest.moc"
611+#include "QLimitProxyModelTest.moc"
tests/plugins/Utils/timeformattertest.cpp → tests/plugins/Utils/TimeFormatterTest.cpprenamed+1−11
@@ -45,16 +45,6 @@ private:
4545 }
4646
4747private Q_SLOTS:
48− void initTestCase()
49− {
50− setenv("UNITY_TEST_ENV", "1", 1);
51− }
52−
53− void cleanupTestCase()
54− {
55− unsetenv("UNITY_TEST_ENV");
56− }
57−
5848 void testTimeFormatter_Format_data() { addTestData(QList<int>() << 0); }
5949 void testTimeFormatter_Format()
6050 {
@@ -162,4 +152,4 @@ private Q_SLOTS:
162152
163153QTEST_GUILESS_MAIN(TimeFormatterTest)
164154
165−#include "timeformattertest.moc"
155+#include "TimeFormatterTest.moc"
tests/plugins/Utils/unitysortfilterproxymodeltest.cpp → tests/plugins/Utils/UnitySortFilterProxyModelTest.cpprenamed+2−2
@@ -19,7 +19,7 @@
1919
2020// local
2121#include "unitysortfilterproxymodelqml.h"
22−#include "modeltest.h"
22+#include "ModelTest.h"
2323
2424// Qt
2525#include <QTest>
@@ -287,4 +287,4 @@ private Q_SLOTS:
287287
288288QTEST_GUILESS_MAIN(UnitySortFilterProxyModelTest)
289289
290−#include "unitysortfilterproxymodeltest.moc"
290+#include "UnitySortFilterProxyModelTest.moc"
tests/plugins/Wizard/CMakeLists.txt+2−2
@@ -8,11 +8,11 @@ add_executable(tst-wizard-pagelist
88 ${CMAKE_SOURCE_DIR}/plugins/Wizard/PageList.cpp
99)
1010qt5_use_modules(tst-wizard-pagelist Core Qml Test)
11−add_test(NAME wizardpagelisttest COMMAND env UNITY_TEST_ENV=1 ${CMAKE_CURRENT_BINARY_DIR}/tst-wizard-pagelist)
11+add_unity8_unittest(WizardPageList tst-wizard-pagelist ENVIRONMENT WIZARD_TESTING=1)
1212
1313add_executable(tst-wizard-system
1414 tst_system.cpp
1515 ${CMAKE_SOURCE_DIR}/plugins/Wizard/System.cpp
1616)
1717qt5_use_modules(tst-wizard-system Core DBus Qml Test)
18−add_test(NAME wizardsystemtest COMMAND ${CMAKE_CURRENT_BINARY_DIR}/tst-wizard-system)
18+add_unity8_unittest(WizardSystem tst-wizard-system)
tests/qmltests/CMakeLists.txt+77−93
@@ -1,96 +1,80 @@
1−# add_qml_test macro
2−include(QmlTest)
31
42add_subdirectory(Components)
53
6−set(qmltest_DEFAULT_IMPORT_PATHS
7− ${CMAKE_BINARY_DIR}/plugins
8− ${CMAKE_BINARY_DIR}/tests/utils/modules
9− ${CMAKE_BINARY_DIR}/tests/mocks
10−)
11−
12−set(qmltest_DEFAULT_TARGETS qmlunittests)
13−set(qmltest_DEFAULT_NO_ADD_TEST FALSE)
14−set(qmltest_DEFAULT_PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=minimal")
15−add_qml_test(utils/Unity/Test UnityTest)
16−add_qml_test(Components PhysicalKeysMapper)
17−
18−set(qmltest_DEFAULT_TARGETS qmluitests)
19−set(qmltest_DEFAULT_NO_ADD_TEST TRUE)
20−set(qmltest_DEFAULT_PROPERTIES ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/libusermetrics:${CMAKE_BINARY_DIR}/tests/mocks/LightDM/liblightdm:${CMAKE_BINARY_DIR}/tests/mocks/QMenuModel")
21−
22−add_qml_test(. Shell)
23−add_qml_test(. ShellWithPin)
24−add_qml_test(Components Background)
25−add_qml_test(Components Carousel)
26−add_qml_test(Components Dialogs)
27−add_qml_test(Components DraggingArea)
28−add_qml_test(Components LazyImage)
29−add_qml_test(Components Lockscreen)
30−add_qml_test(Components Rating)
31−add_qml_test(Components ResponsiveGridView)
32−add_qml_test(Components ResponsiveVerticalJournal IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/plugins)
33−add_qml_test(Components Showable)
34−add_qml_test(Components ZoomableImage)
35−add_qml_test(Dash Dash)
36−add_qml_test(Dash DashContent)
37−add_qml_test(Dash Card)
38−add_qml_benchmark(Dash CardBenchmark 1000)
39−add_qml_test(Dash CardTool)
40−add_qml_test(Dash GenericScopeView)
41−add_qml_test(Dash PageHeader)
42−add_qml_test(Dash PreviewListView)
43−add_qml_test(Dash/Previews Preview)
44−add_qml_test(Dash/Previews PreviewActions)
45−add_qml_test(Dash/Previews PreviewAudioPlayback)
46−add_qml_test(Dash/Previews PreviewExpandable)
47−add_qml_test(Dash/Previews PreviewHeader)
48−add_qml_test(Dash/Previews PreviewImageGallery)
49−add_qml_test(Dash/Previews PreviewPayments)
50−add_qml_test(Dash/Previews PreviewProgress)
51−add_qml_test(Dash/Previews PreviewRatingDisplay)
52−add_qml_test(Dash/Previews PreviewRatingEdit)
53−add_qml_test(Dash/Previews PreviewRatingInput)
54−add_qml_test(Dash/Previews PreviewTable)
55−add_qml_test(Dash/Previews PreviewTextSummary)
56−add_qml_test(Dash/Previews PreviewVideoPlayback)
57−add_qml_test(Dash/Previews PreviewWidgetFactory)
58−add_qml_test(Dash/Previews PreviewZoomableImage)
59−add_qml_test(Dash/ScopeSettings ScopeSettingBoolean)
60−add_qml_test(Dash/ScopeSettings ScopeSettingList)
61−add_qml_test(Dash/ScopeSettings ScopeSettingNumber)
62−add_qml_test(Dash/ScopeSettings ScopeSettingString)
63−add_qml_test(Dash/ScopeSettings ScopeSettingsWidgetFactory)
64−add_qml_test(Greeter Clock)
65−add_qml_test(Greeter Greeter)
66−add_qml_test(Greeter Infographics)
67−add_qml_test(Greeter NarrowView)
68−add_qml_test(Greeter WideView)
69−add_qml_test(Launcher Launcher)
70−add_qml_test(Notifications Notifications)
71−add_qml_test(Notifications VisualSnapDecisionsQueue)
72−add_qml_test(Notifications OptionToggle)
73−add_qml_test(Notifications SwipeToAct)
74−add_qml_test(Panel ActiveCallHint)
75−add_qml_test(Panel IndicatorItem)
76−add_qml_test(Panel IndicatorItemRow)
77−add_qml_test(Panel IndicatorPage)
78−add_qml_test(Panel IndicatorsBar)
79−add_qml_test(Panel IndicatorsMenu)
80−add_qml_test(Panel MenuContent)
81−add_qml_test(Panel Panel)
82−add_qml_test(Panel/Indicators IndicatorsLight ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/QMenuModel")
83−# These MenuItemFactory tests need the test/mocks/ to come before plugins/
84−add_qml_test(Panel/Indicators MenuItemFactory IMPORT_PATHS ${CMAKE_BINARY_DIR}/tests/mocks ${qmltest_DEFAULT_IMPORT_PATHS})
85−add_qml_test(Panel/Indicators MessageMenuItemFactory IMPORT_PATHS ${CMAKE_BINARY_DIR}/tests/mocks ${qmltest_DEFAULT_IMPORT_PATHS})
86−add_qml_test(Stages ApplicationWindow)
87−add_qml_test(Stages DesktopStage)
88−add_qml_test(Stages PhoneStage ENVIRONMENT)
89−add_qml_test(Stages SpreadDelegate ENVIRONMENT)
90−add_qml_test(Stages SurfaceContainer ENVIRONMENT)
91−add_qml_test(Stages SessionContainer ENVIRONMENT)
92−add_qml_test(Stages TabletStage)
93−add_qml_test(Stages WindowMoveResizeArea)
94−add_qml_test(Stages Splash)
95−add_qml_test(Tutorial Tutorial)
96−add_qml_test(Wizard Wizard)
4+add_unity8_qmltest(. OrientedShell)
5+add_unity8_qmltest(. Shell LIGHTDM)
6+add_unity8_qmltest(. ShellWithPin LIGHTDM)
7+add_unity8_qmltest(Components Background)
8+add_unity8_qmltest(Components Carousel)
9+add_unity8_qmltest(Components Dialogs LIGHTDM)
10+add_unity8_qmltest(Components DraggingArea)
11+add_unity8_qmltest(Components LazyImage)
12+add_unity8_qmltest(Components Lockscreen)
13+add_unity8_qmlunittest(Components PhysicalKeysMapper)
14+add_unity8_qmltest(Components Rating)
15+add_unity8_qmltest(Components ResponsiveGridView)
16+add_unity8_qmltest(Components ResponsiveVerticalJournal)
17+add_unity8_qmltest(Components Showable)
18+add_unity8_qmltest(Components ZoomableImage)
19+add_unity8_qmltest(Dash Dash)
20+add_unity8_qmltest(Dash DashContent)
21+add_unity8_qmltest(Dash Card)
22+add_unity8_qmlbenchmark(Dash CardBenchmark 1000)
23+add_unity8_qmltest(Dash CardTool)
24+add_unity8_qmltest(Dash GenericScopeView)
25+add_unity8_qmltest(Dash PageHeader)
26+add_unity8_qmltest(Dash PreviewListView)
27+add_unity8_qmltest(Dash/Previews Preview)
28+add_unity8_qmltest(Dash/Previews PreviewActions)
29+add_unity8_qmltest(Dash/Previews PreviewAudioPlayback)
30+add_unity8_qmltest(Dash/Previews PreviewExpandable)
31+add_unity8_qmltest(Dash/Previews PreviewHeader)
32+add_unity8_qmltest(Dash/Previews PreviewImageGallery)
33+add_unity8_qmltest(Dash/Previews PreviewPayments)
34+add_unity8_qmltest(Dash/Previews PreviewProgress)
35+add_unity8_qmltest(Dash/Previews PreviewRatingDisplay)
36+add_unity8_qmltest(Dash/Previews PreviewRatingEdit)
37+add_unity8_qmltest(Dash/Previews PreviewRatingInput)
38+add_unity8_qmltest(Dash/Previews PreviewTable)
39+add_unity8_qmltest(Dash/Previews PreviewTextSummary)
40+add_unity8_qmltest(Dash/Previews PreviewVideoPlayback)
41+add_unity8_qmltest(Dash/Previews PreviewWidgetFactory)
42+add_unity8_qmltest(Dash/Previews PreviewZoomableImage)
43+add_unity8_qmltest(Dash/ScopeSettings ScopeSettingBoolean)
44+add_unity8_qmltest(Dash/ScopeSettings ScopeSettingList)
45+add_unity8_qmltest(Dash/ScopeSettings ScopeSettingNumber)
46+add_unity8_qmltest(Dash/ScopeSettings ScopeSettingString)
47+add_unity8_qmltest(Dash/ScopeSettings ScopeSettingsWidgetFactory)
48+add_unity8_qmltest(Greeter Clock)
49+add_unity8_qmltest(Greeter Greeter LIGHTDM)
50+add_unity8_qmltest(Greeter Infographics LIGHTDM)
51+add_unity8_qmltest(Greeter NarrowView LIGHTDM)
52+add_unity8_qmltest(Greeter WideView LIGHTDM)
53+add_unity8_qmltest(Launcher Launcher)
54+add_unity8_qmltest(Notifications Notifications)
55+add_unity8_qmltest(Notifications VisualSnapDecisionsQueue)
56+add_unity8_qmltest(Notifications OptionToggle)
57+add_unity8_qmltest(Notifications SwipeToAct)
58+add_unity8_qmltest(Panel ActiveCallHint)
59+add_unity8_qmltest(Panel IndicatorItem)
60+add_unity8_qmltest(Panel IndicatorItemRow)
61+add_unity8_qmltest(Panel IndicatorPage)
62+add_unity8_qmltest(Panel IndicatorsBar)
63+add_unity8_qmltest(Panel IndicatorsMenu)
64+add_unity8_qmltest(Panel MenuContent)
65+add_unity8_qmltest(Panel Panel)
66+add_unity8_qmltest(Panel/Indicators IndicatorsLight)
67+add_unity8_qmltest(Panel/Indicators MenuItemFactory)
68+add_unity8_qmltest(Panel/Indicators MessageMenuItemFactory)
69+add_unity8_qmltest(Stages ApplicationWindow)
70+add_unity8_qmltest(Stages DesktopStage)
71+add_unity8_qmltest(Stages PhoneStage)
72+add_unity8_qmltest(Stages SpreadDelegate)
73+add_unity8_qmltest(Stages SurfaceContainer)
74+add_unity8_qmltest(Stages SessionContainer)
75+add_unity8_qmltest(Stages TabletStage)
76+add_unity8_qmltest(Stages WindowMoveResizeArea)
77+add_unity8_qmltest(Stages Splash)
78+add_unity8_qmltest(Tutorial Tutorial LIGHTDM)
79+add_unity8_qmltest(Wizard Wizard ENVIRONMENT "OXIDE_NO_SANDBOX=1")
80+add_unity8_qmlunittest(utils/Unity/Test UnityTest)
tests/qmltests/Components/CMakeLists.txt+4−10
@@ -16,19 +16,13 @@ add_executable(DragHandleTestExec tst_DragHandle.cpp)
1616qt5_use_modules(DragHandleTestExec Test Core Qml Gui Quick)
1717target_link_libraries(DragHandleTestExec UbuntuGesturesQml UbuntuGestures)
1818
19−add_binary_qml_test(DragHandle ${CMAKE_BINARY_DIR}/plugins/Ubuntu/Gestures "" "")
20−add_manual_qml_test(. DragHandle IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${CMAKE_BINARY_DIR}/tests/utils/modules)
19+add_unity8_uitest(DragHandle DragHandleTestExec ENVIRONMENT LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/plugins/Ubuntu/Gestures)
20+add_manual_qml_test(. DragHandle IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${CMAKE_BINARY_DIR}/tests/utils/modules)
2121
22−########## tst_EdgeDragEvaluator
23−
24−include_directories(
25− ${CMAKE_SOURCE_DIR}/plugins/Ubuntu/Gestures
26− ${CMAKE_SOURCE_DIR}/libs/UbuntuGestures
27− ${CMAKE_CURRENT_BINARY_DIR}
28− )
2922
23+########## tst_EdgeDragEvaluator
3024add_executable(EdgeDragEvaluatorTestExec tst_EdgeDragEvaluator.cpp)
3125qt5_use_modules(EdgeDragEvaluatorTestExec Test Core Qml Gui Quick)
3226target_link_libraries(EdgeDragEvaluatorTestExec UbuntuGesturesQml UbuntuGestures)
3327
34−add_binary_qml_test(EdgeDragEvaluator ${CMAKE_BINARY_DIR}/plugins/Ubuntu/Gestures "" "")
28+add_unity8_uitest(EdgeDragEvaluator EdgeDragEvaluatorTestExec ENVIRONMENT LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/plugins/Ubuntu/Gestures)
tests/qmltests/Dash/Previews/tst_PreviewHeader.qml+30
@@ -38,6 +38,22 @@ Rectangle {
3838 "attributes": [{"value":"text1","icon":"image://theme/ok"},{"value":"text2","icon":"image://theme/cancel"}]
3939 }
4040
41+ property var brokenheaderjson: {
42+ "title": "THE TITLE",
43+ "subtitle": "Something catchy",
44+ "mascot": "bad_path",
45+ "fallback": "",
46+ "attributes": [{"value":"text1","icon":"image://theme/ok"},{"value":"text2","icon":"image://theme/cancel"}]
47+ }
48+
49+ property var fallbackheaderjson: {
50+ "title": "THE TITLE",
51+ "subtitle": "Something catchy",
52+ "mascot": "bad_path2",
53+ "fallback": "../graphics/play_button.png",
54+ "attributes": [{"value":"text1","icon":"image://theme/ok"},{"value":"text2","icon":"image://theme/cancel"}]
55+ }
56+
4157 PreviewHeader {
4258 id: previewHeader
4359 widgetData: headerjson
@@ -121,5 +137,19 @@ Rectangle {
121137 compare(innerPreviewHeader.subtitle, "Something catchy");
122138 compare(innerPreviewHeader.mascot.toString().slice(-24), "graphics/play_button.png");
123139 }
140+
141+ function test_fallback() {
142+ previewHeader.widgetData = brokenheaderjson;
143+ tryCompareFunction(function() { return findChild(previewHeader, "mascotShape") != null }, true);
144+ var mascot = findChild(previewHeader, "mascotShape");
145+ compare(mascot.visible, false);
146+
147+ previewHeader.widgetData = {};
148+ previewHeader.widgetData = fallbackheaderjson;
149+ tryCompareFunction(function() { return findChild(previewHeader, "mascotShape") != null }, true);
150+ var mascot = findChild(previewHeader, "mascotShape");
151+ tryCompare(mascot, "visible", true);
152+ tryCompare(mascot.image, "status", Image.Ready);
153+ }
124154 }
125155}
tests/qmltests/Dash/Previews/tst_PreviewImageGallery.qml+21−1
@@ -33,10 +33,21 @@ Rectangle {
3333 "sources": [
3434 "../../graphics/phone_background.jpg",
3535 "../../graphics/tablet_background.jpg",
36− "../../graphics/clock@18.png"
36+ "../../graphics/clock@18.png",
37+ "../../graphics/borked"
3738 ]
3839 }
3940
41+ property var sourcesModel1WithFallback: {
42+ "sources": [
43+ "../../graphics/phone_background.jpg",
44+ "../../graphics/tablet_background.jpg",
45+ "../../graphics/clock@18.png",
46+ "../../graphics/borked"
47+ ]
48+ , "fallback": "../../graphics/clock@18.png"
49+ }
50+
4051 PreviewImageGallery {
4152 id: imageGallery
4253 width: parent.width
@@ -103,5 +114,14 @@ Rectangle {
103114 tryCompare(overlayListView, "currentIndex", data.index);
104115 verify(image.source === overlayListView.currentItem.source);
105116 }
117+
118+ function test_fallback() {
119+ var image3 = findChild(imageGallery, "previewImage3");
120+ tryCompare(image3, "state", "error");
121+ imageGallery.widgetData = sourcesModel0;
122+ imageGallery.widgetData = sourcesModel1WithFallback;
123+ image3 = findChild(imageGallery, "previewImage3");
124+ tryCompare(image3, "state", "ready");
125+ }
106126 }
107127}
tests/qmltests/Dash/Previews/tst_PreviewZoomableImage.qml+27−6
@@ -26,7 +26,7 @@ Rectangle {
2626 color: "lightgrey"
2727
2828 property var widgetData0: {
29− "source": Qt.resolvedUrl("../artwork/checkers.png"),
29+ "source": "../../graphics/phone_background.jpg",
3030 "zoomable": false
3131 }
3232
@@ -34,21 +34,35 @@ Rectangle {
3434 "source": ""
3535 }
3636
37− PreviewZoomableImage {
38− id: zoomableImage
37+ property var widgetData2: {
38+ "source": "fadsasf",
39+ "fallback": "../../graphics/phone_background.jpg"
40+ }
41+
42+ Loader {
43+ id: loader
3944 anchors.centerIn: parent
40− widgetData: widgetData0
45+ sourceComponent: PreviewZoomableImage {
46+ widgetData: widgetData0
47+ }
4148 }
4249
50+ property alias zoomableImage: loader.item
51+
4352 UT.UnityTestCase {
4453 name: "PreviewZoomableImageTest"
4554 when: windowShown
4655
47− property Item lazyImage: findChild(zoomableImage, "lazyImage");
48− property Item overlay: findChild(zoomableImage.rootItem, "overlay");
56+ property Item lazyImage
57+ property Item overlay
4958
5059 function init() {
60+ // Use a loader so we start from scratch each time
61+ loader.active = false;
62+ loader.active = true;
63+ lazyImage = findChild(zoomableImage, "lazyImage");
5164 waitForRendering(zoomableImage);
65+ overlay = findChild(zoomableImage.rootItem, "overlay");
5266 waitForRendering(overlay);
5367 }
5468
@@ -79,5 +93,12 @@ Rectangle {
7993 mouseClick(overlayCloseButton);
8094 tryCompare(overlay, "visible", false);
8195 }
96+
97+ function test_fallback() {
98+ zoomableImage.widgetData = widgetData2;
99+ waitForRendering(zoomableImage);
100+ waitForRendering(lazyImage);
101+ tryCompare(lazyImage, "state", "ready");
102+ }
82103 }
83104}
tests/qmltests/Dash/tst_Card.qml+30−1
@@ -124,7 +124,6 @@ Rectangle {
124124 sourceComponent: cardTool.cardComponent
125125 clip: true
126126 onLoaded: {
127− item.template = Qt.binding(function() { return cardTool.template; });
128127 item.components = Qt.binding(function() { return cardTool.components; });
129128 item.cardData = Qt.binding(function() { return Helpers.mapData(dataArea.text, cardTool.components, dataError); });
130129 item.width = Qt.binding(function() { return cardTool.cardWidth || item.implicitWidth; });
@@ -224,6 +223,7 @@ Rectangle {
224223 property Item mascotImage: findChild(card, "mascotImage");
225224
226225 function init() {
226+ cardTool.components = Qt.binding(function() { return Helpers.update(JSON.parse(Helpers.defaultLayout), Helpers.tryParse(layoutArea.text, layoutError))['components']; });
227227 loader.visible = true;
228228 }
229229
@@ -424,6 +424,7 @@ Rectangle {
424424 ];
425425 }
426426
427+
427428 function test_background(data) {
428429 selector.selectedIndex = data.index;
429430 waitForRendering(selector);
@@ -454,6 +455,34 @@ Rectangle {
454455 }
455456 }
456457
458+ function test_fallback() {
459+ selector.selectedIndex = 0;
460+ waitForRendering(card);
461+
462+ card.cardData["art"] = "somethingbroken";
463+ card.cardDataChanged();
464+ waitForRendering(card);
465+ tryCompare(art, "visible", false);
466+
467+ cardTool.components["art"]["fallback"] = Qt.resolvedUrl("artwork/emblem.png");
468+ cardTool.componentsChanged();
469+ card.cardData["art"] = "somethingbroken";
470+ card.cardDataChanged();
471+ waitForRendering(card);
472+ tryCompare(art, "visible", true);
473+
474+ card.cardData["mascot"] = "somethingbroken2";
475+ card.cardDataChanged();
476+ compare(mascotImage.image.status, Image.Error);
477+
478+ cardTool.components["mascot"] = {"fallback": Qt.resolvedUrl("artwork/emblem.png")};
479+ cardTool.componentsChanged();
480+ card.cardData["mascot"] = "somethingbroken2";
481+ card.cardDataChanged();
482+ waitForRendering(card);
483+ tryCompare(mascotImage.image, "status", Image.Ready);
484+ }
485+
457486 function test_font_weights_data() {
458487 return [
459488 { tag: "Title only", index: 8, weight: Font.Normal },
tests/qmltests/Dash/tst_GenericScopeView.qml+30−7
@@ -302,6 +302,7 @@ Item {
302302 }
303303
304304 function closePreview() {
305+ tryCompare(testCase.subPageLoader, "x", 0);
305306 var closePreviewMouseArea = findChild(subPageLoader.item, "pageHeader");
306307 mouseClick(closePreviewMouseArea, units.gu(2), units.gu(2));
307308
@@ -616,27 +617,49 @@ Item {
616617 waitForRendering(category0);
617618
618619 var cardTool = findChild(category0, "cardTool");
619− var cardGrid = category0.item;
620−
621620 cardTool.template["non-interactive"] = true;
621+ cardTool.templateChanged();
622+
623+ var category0 = findChild(categoryListView, "dashCategory0");
624+ waitForRendering(category0);
625+
626+ var cardGrid = category0.item;
622627 compare(cardGrid.cardTool.template["non-interactive"], true);
623628
624629 var item0 = findChild(cardGrid, "delegate0");
625630 waitForRendering(item0);
626− item0.template = cardTool.template;
627− compare(item0.template["non-interactive"], true);
628631 compare(item0.enabled, false);
629632 var touchdown = findChild(item0, "touchdown");
630633
631634 compare(touchdown.visible, false);
632− mouseClick(item0);
635+ mousePress(item0);
633636 compare(touchdown.visible, false);
637+ mouseRelease(item0);
634638
635639 cardTool.template["non-interactive"] = false;
640+ cardTool.templateChanged();
636641 compare(cardGrid.cardTool.template["non-interactive"], false);
637− item0.template = cardTool.template;
638− compare(item0.template["non-interactive"], false);
642+
643+ waitForRendering(category0);
644+ item0 = findChild(cardGrid, "delegate0");
639645 compare(item0.enabled, true);
646+ var touchdown = findChild(item0, "touchdown");
647+
648+ compare(touchdown.visible, false);
649+ mousePress(item0);
650+ compare(touchdown.visible, true);
651+ mouseRelease(item0);
652+ compare(touchdown.visible, false);
653+ closePreview();
654+ }
655+
656+ function test_carousel_borderSource() {
657+ var category = scrollToCategory("dashCategory1");
658+ var tile = findChild(category, "carouselDelegate0");
659+ tryCompareFunction(function() { return findChild(tile, "artShapeLoader") !== null; }, true);
660+ var artShapeLoader = findChild(tile, "artShapeLoader");
661+ var shape = findChildsByType(artShapeLoader, "UCUbuntuShape");
662+ compare(shape.borderSource, undefined);
640663 }
641664 }
642665 }
tests/qmltests/Panel/tst_IndicatorsMenu.qml−2
@@ -28,8 +28,6 @@ IndicatorTest {
2828 height: units.gu(71)
2929 color: "white"
3030
31− property string indicatorProfile: "phone"
32−
3331 RowLayout {
3432 anchors.fill: parent
3533 anchors.margins: units.gu(1)
tests/qmltests/Panel/tst_Panel.qml−2
@@ -29,8 +29,6 @@ IndicatorTest {
2929 height: units.gu(71)
3030 color: "white"
3131
32− property string indicatorProfile: "phone"
33−
3432 RowLayout {
3533 anchors.fill: parent
3634 anchors.margins: units.gu(1)
tests/qmltests/Stages/tst_ApplicationWindow.qml+14−23
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2014 Canonical Ltd.
2+ * Copyright 2014-2015 Canonical Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -50,7 +50,7 @@ Rectangle {
5050 ApplicationWindow {
5151 anchors.fill: parent
5252 application: fakeApplication
53− orientation: Qt.PortraitOrientation
53+ surfaceOrientationAngle: 0
5454 interactive: true
5555 focus: true
5656 }
@@ -210,15 +210,6 @@ Rectangle {
210210 stateGroup = null;
211211 }
212212
213− // wait until any transition animation has finished
214− function waitUntilTransitionsEnd() {
215− var transitions = stateGroup.transitions;
216− for (var i = 0; i < transitions.length; ++i) {
217− var transition = transitions[i];
218− tryCompare(transition, "running", false, 2000);
219− }
220− }
221−
222213 function init() {
223214 findInterestingApplicationWindowChildren();
224215 }
@@ -280,12 +271,12 @@ Rectangle {
280271
281272 tryCompare(stateGroup, "state", "surface");
282273
283− waitUntilTransitionsEnd();
274+ waitUntilTransitionsEnd(stateGroup);
284275
285276 setApplicationState(appSuspended);
286277
287278 verify(stateGroup.state === "surface");
288− waitUntilTransitionsEnd();
279+ waitUntilTransitionsEnd(stateGroup);
289280 }
290281
291282 function test_killedAppShowsScreenshot() {
@@ -311,7 +302,7 @@ Rectangle {
311302 initSession();
312303 setApplicationState(appRunning);
313304 tryCompare(stateGroup, "state", "surface");
314− waitUntilTransitionsEnd();
305+ waitUntilTransitionsEnd(stateGroup);
315306
316307 setApplicationState(appSuspended);
317308
@@ -319,19 +310,19 @@ Rectangle {
319310 setApplicationState(appStopped);
320311
321312 tryCompare(stateGroup, "state", "screenshot");
322− waitUntilTransitionsEnd();
313+ waitUntilTransitionsEnd(stateGroup);
323314 tryCompare(fakeApplication, "session", null);
324315
325316 // and restart it
326317 setApplicationState(appStarting);
327318
328− waitUntilTransitionsEnd();
319+ waitUntilTransitionsEnd(stateGroup);
329320 verify(stateGroup.state === "screenshot");
330321 verify(fakeSession === null);
331322
332323 setApplicationState(appRunning);
333324
334− waitUntilTransitionsEnd();
325+ waitUntilTransitionsEnd(stateGroup);
335326 verify(stateGroup.state === "screenshot");
336327
337328 initSession();
@@ -344,7 +335,7 @@ Rectangle {
344335 initSession();
345336 setApplicationState(appRunning);
346337 tryCompare(stateGroup, "state", "surface");
347− waitUntilTransitionsEnd();
338+ waitUntilTransitionsEnd(stateGroup);
348339
349340 // oh, it crashed...
350341 setApplicationState(appStopped);
@@ -357,18 +348,18 @@ Rectangle {
357348 initSession();
358349 setApplicationState(appRunning);
359350 tryCompare(stateGroup, "state", "surface");
360− waitUntilTransitionsEnd();
351+ waitUntilTransitionsEnd(stateGroup);
361352 verify(fakeSession.surface !== null);
362353
363354 applicationWindowLoader.item.visible = false;
364355
365− waitUntilTransitionsEnd();
356+ waitUntilTransitionsEnd(stateGroup);
366357 verify(stateGroup.state === "surface");
367358 verify(fakeSession.surface !== null);
368359
369360 applicationWindowLoader.item.visible = true;
370361
371− waitUntilTransitionsEnd();
362+ waitUntilTransitionsEnd(stateGroup);
372363 verify(stateGroup.state === "surface");
373364 verify(fakeSession.surface !== null);
374365 }
@@ -379,7 +370,7 @@ Rectangle {
379370
380371 tryCompare(stateGroup, "state", "surface");
381372
382− waitUntilTransitionsEnd();
373+ waitUntilTransitionsEnd(stateGroup);
383374
384375 var fakeSurface = fakeSession.surface;
385376 fakeSurface.touchPressCount = 0;
@@ -396,7 +387,7 @@ Rectangle {
396387 initSession();
397388 setApplicationState(appRunning);
398389 tryCompare(stateGroup, "state", "surface");
399− waitUntilTransitionsEnd();
390+ waitUntilTransitionsEnd(stateGroup);
400391
401392 cleanupSession();
402393
tests/qmltests/Stages/tst_PhoneStage.qml+24−58
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2014 Canonical Ltd.
2+ * Copyright 2014-2015 Canonical Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -33,7 +33,9 @@ Item {
3333 dragAreaWidth: units.gu(2)
3434 maximizedAppTopMargin: units.gu(3) + units.dp(2)
3535 interactive: true
36− orientation: Qt.PortraitOrientation
36+ shellOrientation: Qt.PortraitOrientation
37+ shellPrimaryOrientation: Qt.PortraitOrientation
38+ nativeOrientation: Qt.PortraitOrientation
3739 }
3840
3941 Binding {
@@ -73,21 +75,6 @@ Item {
7375 ApplicationManager.get(appList.selectedAppIndex).setState(ApplicationInfoInterface.Stopped);
7476 }
7577 }
76− Button {
77− anchors { left: parent.left; right: parent.right }
78− text: "Rotate device \u27F3"
79− onClicked: {
80− if (phoneStage.orientation == Qt.PortraitOrientation) {
81− phoneStage.orientation = Qt.LandscapeOrientation;
82− } else if (phoneStage.orientation == Qt.LandscapeOrientation) {
83− phoneStage.orientation = Qt.InvertedPortraitOrientation;
84− } else if (phoneStage.orientation == Qt.InvertedPortraitOrientation) {
85− phoneStage.orientation = Qt.InvertedLandscapeOrientation;
86− } else {
87− phoneStage.orientation = Qt.PortraitOrientation;
88− }
89− }
90− }
9178 }
9279 ListView {
9380 id: appList
@@ -283,46 +270,6 @@ Item {
283270 compare(ApplicationManager.focusedApplicationId, selectedApp.appId);
284271 }
285272
286− function test_orientationChangeSentToFocusedApp() {
287− phoneStage.orientation = Qt.PortraitOrientation;
288− addApps(1);
289−
290− var spreadView = findChild(phoneStage, "spreadView");
291− var app = findChild(spreadView, "appDelegate0");
292− tryCompare(app, "orientation", Qt.PortraitOrientation);
293−
294− phoneStage.orientation = Qt.LandscapeOrientation;
295− tryCompare(app, "orientation", Qt.LandscapeOrientation);
296− }
297−
298− function test_orientationChangeNotSentToAppsWhileSpreadOpen() {
299− phoneStage.orientation = Qt.PortraitOrientation;
300− addApps(1);
301−
302− var spreadView = findChild(phoneStage, "spreadView");
303− var app = findChild(spreadView, "appDelegate0");
304− tryCompare(app, "orientation", Qt.PortraitOrientation);
305−
306− goToSpread();
307− phoneStage.orientation = Qt.LandscapeOrientation;
308− tryCompare(app, "orientation", Qt.PortraitOrientation);
309− }
310−
311− function test_orientationChangeNotSentToUnfocusedAppUntilItFocused() {
312− phoneStage.orientation = Qt.PortraitOrientation;
313− addApps(1);
314−
315− var spreadView = findChild(phoneStage, "spreadView");
316− var app = findChild(spreadView, "appDelegate0");
317−
318− goToSpread();
319− phoneStage.orientation = Qt.LandscapeOrientation;
320− tryCompare(app, "orientation", Qt.PortraitOrientation);
321−
322− phoneStage.select(app.application.appId);
323− tryCompare(app, "orientation", Qt.LandscapeOrientation);
324− }
325−
326273 function test_backgroundClickCancelsSpread() {
327274 addApps(3);
328275
@@ -347,7 +294,7 @@ Item {
347294 ApplicationManager.stopApplication(ApplicationManager.get(closingIndex).appId)
348295 tryCompare(ApplicationManager, "count", oldCount - 1)
349296 }
350− phoneStage.orientation = Qt.PortraitOrientation;
297+ phoneStage.shellOrientationAngle = 0;
351298 }
352299
353300 function test_focusNewTopMostAppAfterFocusedOneClosesItself() {
@@ -399,6 +346,25 @@ Item {
399346 tryCompare(spreadView, "contentX", -spreadView.shift)
400347 }
401348
349+ function test_cantAccessPhoneStageWhileRightEdgeGesture() {
350+ var spreadView = findChild(phoneStage, "spreadView");
351+ var eventEaterArea = findChild(phoneStage, "eventEaterArea")
352+
353+ var startX = phoneStage.width - 2;
354+ var startY = phoneStage.height / 2;
355+ var endY = startY;
356+ var endX = phoneStage.width / 2;
357+
358+ touchFlick(phoneStage, startX, startY, endX, endY,
359+ true /* beginTouch */, false /* endTouch */, units.gu(10), 50);
360+
361+ compare(eventEaterArea.enabled, true);
362+
363+ touchRelease(phoneStage, endX, endY);
364+
365+ compare(eventEaterArea.enabled, false);
366+ }
367+
402368 function test_leftEdge_data() {
403369 return [
404370 { tag: "normal", inSpread: false, leftEdgeDragWidth: units.gu(5), shouldMoveApp: true },
tests/qmltests/Stages/tst_SessionContainer.qml+1−64
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2014 Canonical Ltd.
2+ * Copyright 2014-2015 Canonical Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -41,7 +41,6 @@ Rectangle {
4141 SessionContainer {
4242 id: sessionContainer
4343 anchors.fill: parent
44− orientation: Qt.PortraitOrientation
4544 focus: true
4645 interactive: true
4746 }
@@ -117,24 +116,6 @@ Rectangle {
117116 session: sessionContainerLoader.item ? sessionContainerLoader.item.session : null
118117 }
119118 }
120−
121− Button {
122− anchors { left: parent.left; right: parent.right }
123− text: "Rotate device \u27F3"
124− onClicked: {
125− var orientation = sessionContainerLoader.item.orientation
126− if (orientation == Qt.PortraitOrientation) {
127− orientation = Qt.LandscapeOrientation;
128− } else if (orientation == Qt.LandscapeOrientation) {
129− orientation = Qt.InvertedPortraitOrientation;
130− } else if (orientation == Qt.InvertedPortraitOrientation) {
131− orientation = Qt.InvertedLandscapeOrientation;
132− } else {
133− orientation = Qt.PortraitOrientation;
134− }
135− sessionContainerLoader.item.orientation = orientation;
136− }
137− }
138119 }
139120 }
140121
@@ -324,49 +305,5 @@ Rectangle {
324305 // wait for animation to end
325306 tryCompareFunction(function() { return isContainerAnimating(childContainer); }, false);
326307 }
327−
328− function test_orientationPropagatedToChildren_data() {
329− return [ { tag: "count=1", count: 1 },
330− { tag: "count=4", count: 4 } ];
331− }
332−
333− /* Test orientation changes are propagated to all children immediately */
334− function test_orientationPropagatedToChildren(data) {
335− sessionCheckbox.checked = true;
336− var rootSessionContainer = sessionContainerLoader.item;
337− compare(rootSessionContainer.childSessions.count(), 0);
338−
339− var i;
340− var sessions = [];
341− for (i = 0; i < data.count; i++) {
342− var session = ApplicationTest.addChildSession(rootSessionContainer.session,
343− "gallery");
344− session.createSurface();
345− rootSessionContainer.session.addChildSession(session);
346−
347− // Check child SessionContainer has orientation matching the parent
348− var delegate = findChild(rootSessionContainer, "childDelegate" + i);
349− var childSessionContainer = findChild(delegate, "sessionContainer");
350−
351− tryCompare(rootSessionContainer, "orientation", childSessionContainer.orientation);
352−
353− sessions.push(session);
354− }
355−
356− // Change orientation and verify all children updated
357− rootSessionContainer.orientation = Qt.LandscapeOrientation;
358−
359− for (i = 0; i < data.count; i++) {
360− var delegate = findChild(rootSessionContainer, "childDelegate" + i);
361− var childSessionContainer = findChild(delegate, "sessionContainer");
362−
363− tryCompare(rootSessionContainer, "orientation", childSessionContainer.orientation);
364− }
365−
366− // Clean up
367− for (i = data.count-1; i >= 0; i--) {
368− ApplicationTest.removeSession(sessions[i]);
369− }
370− }
371308 }
372309}
tests/qmltests/Stages/tst_SpreadDelegate.qml+172−37
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2014 Canonical Ltd.
2+ * Copyright 2014-2015 Canonical Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -26,46 +26,89 @@ import Unity.Application 0.1
2626Rectangle {
2727 color: "red"
2828 id: root
29− width: units.gu(70)
30− height: units.gu(70)
29+ width: fakeShell.shortestDimension + controls.width
30+ height: fakeShell.longestDimension
3131
32− Component.onCompleted: {
33− root.fakeApplication = ApplicationManager.startApplication("gallery-app");
34− }
3532 property QtObject fakeApplication: null
3633
37− Component {
38− id: spreadDelegateComponent
39− SpreadDelegate {
40− anchors.fill: parent
41− swipeToCloseEnabled: swipeToCloseCheckbox.checked
42− closeable: closeableCheckbox.checked
43− application: fakeApplication
44− }
45− }
46− Loader {
47− id: spreadDelegateLoader
48− anchors {
49− top: parent.top
50− bottom: parent.bottom
51− left: parent.left
34+ Item {
35+ id: fakeShell
36+
37+ readonly property real shortestDimension: units.gu(40)
38+ readonly property real longestDimension: units.gu(70)
39+
40+ width: landscape ? longestDimension : shortestDimension
41+ height: landscape ? shortestDimension : longestDimension
42+
43+ x: landscape ? (height - width) / 2 : 0
44+ y: landscape ? (width - height) / 2 : 0
45+
46+ property bool landscape: orientationAngle == 90 || orientationAngle == 270
47+ property int orientationAngle: shellOrientationAngleSelector.value
48+
49+ rotation: orientationAngle
50+
51+ Loader {
52+ id: spreadDelegateLoader
53+ width: parent.width
54+ height: parent.height
55+
56+ active: false
57+ property bool itemDestroyed: false
58+ sourceComponent: SpreadDelegate {
59+ anchors.fill: parent
60+ swipeToCloseEnabled: swipeToCloseCheckbox.checked
61+ closeable: closeableCheckbox.checked
62+ application: fakeApplication
63+ shellOrientationAngle: shellOrientationAngleSelector.value
64+ shellOrientation: {
65+ switch (shellOrientationAngleSelector.value) {
66+ case 0:
67+ return Qt.PortraitOrientation;
68+ case 90:
69+ return Qt.InvertedLandscapeOrientation;
70+ case 180:
71+ return Qt.InvertedPortraitOrientation;
72+ default: // 270
73+ return Qt.LandscapeOrientation;
74+ }
75+ }
76+ shellPrimaryOrientation: Qt.PortraitOrientation
77+ nativeOrientation: Qt.PortraitOrientation
78+ maximizedAppTopMargin: units.gu(3)
79+ Component.onDestruction: {
80+ spreadDelegateLoader.itemDestroyed = true;
81+ }
82+ Component.onCompleted: {
83+ spreadDelegateLoader.itemDestroyed = false;
84+ }
85+ }
5286 }
53− width: units.gu(40)
54− sourceComponent: spreadDelegateComponent
5587 }
5688
5789 Rectangle {
90+ id: controls
5891 color: "white"
92+ x: fakeShell.shortestDimension
93+ width: units.gu(30)
5994 anchors {
6095 top: parent.top
6196 bottom: parent.bottom
62− left: spreadDelegateLoader.right
63− right: parent.right
6497 }
6598
6699 Column {
67100 anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
68101 spacing: units.gu(1)
102+ Button {
103+ id: loadWithWeatherApp
104+ text: "Load with ubuntu-weather-app"
105+ onClicked: { testCase.restartWithApp("ubuntu-weather-app"); }
106+ }
107+ Button {
108+ id: loadWithGalleryApp
109+ text: "Load with gallery-app"
110+ onClicked: { testCase.restartWithApp("gallery-app"); }
111+ }
69112 Row {
70113 anchors { left: parent.left; right: parent.right }
71114 CheckBox { id: swipeToCloseCheckbox; checked: false; }
@@ -76,6 +119,21 @@ Rectangle {
76119 CheckBox { id: closeableCheckbox; checked: false }
77120 Label { text: "closeable" }
78121 }
122+ ListItem.ItemSelector {
123+ id: shellOrientationAngleSelector
124+ anchors { left: parent.left; right: parent.right }
125+ text: "shellOrientationAngle"
126+ model: ["0", "90", "180", "270"]
127+ property int value: selectedIndex * 90
128+ }
129+ Button {
130+ text: "matchShellOrientation()"
131+ onClicked: { spreadDelegateLoader.item.matchShellOrientation(); }
132+ }
133+ Button {
134+ text: "animateToShellOrientation()"
135+ onClicked: { spreadDelegateLoader.item.animateToShellOrientation(); }
136+ }
79137 }
80138 }
81139
@@ -91,18 +149,47 @@ Rectangle {
91149 }
92150
93151 property var dragArea
152+ property Item spreadDelegate: spreadDelegateLoader.item
94153
95154 function init() {
96− dragArea = findInvisibleChild(spreadDelegateLoader.item, "dragArea");
97− dragArea.__dateTime = fakeDateTime;
98155 }
99156
100157 function cleanup() {
101− // reload our test subject to get it in a fresh state once again
102− spreadDelegateLoader.active = false;
158+ unloadSpreadDelegate();
159+ spyClosedSignal.clear();
160+ shellOrientationAngleSelector.selectedIndex = 0;
161+ ApplicationManager.stopApplication(root.fakeApplication.appId);
162+ root.fakeApplication = null;
163+ }
164+
165+ function restartWithApp(appId) {
166+ if (spreadDelegateLoader.active) {
167+ unloadSpreadDelegate();
168+ }
169+ if (root.fakeApplication) {
170+ ApplicationManager.stopApplication(root.fakeApplication.appId);
171+ }
172+
173+ root.fakeApplication = ApplicationManager.startApplication(appId);
103174 spreadDelegateLoader.active = true;
175+ tryCompare(spreadDelegateLoader, "status", Loader.Ready);
176+
177+ dragArea = findInvisibleChild(spreadDelegate, "dragArea");
178+ dragArea.__dateTime = fakeDateTime;
179+ }
180+
181+ function unloadSpreadDelegate() {
182+ spreadDelegateLoader.active = false;
183+ tryCompare(spreadDelegateLoader, "status", Loader.Null);
184+ tryCompare(spreadDelegateLoader, "item", null);
185+ // Loader.status might be Loader.Null and Loader.item might be null but the Loader
186+ // item might still be alive. So if we set Loader.active back to true
187+ // again right now we will get the very same Shell instance back. So no reload
188+ // actually took place. Likely because Loader waits until the next event loop
189+ // iteration to do its work. So to ensure the reload, we will wait until the
190+ // Shell instance gets destroyed.
191+ tryCompare(spreadDelegateLoader, "itemDestroyed", true);
104192
105− spyClosedSignal.clear();
106193 }
107194
108195 function test_swipeToClose_data() {
@@ -119,9 +206,10 @@ Rectangle {
119206 }
120207
121208 function test_swipeToClose(data) {
122− var appWindowWithShadow = findChild(spreadDelegateLoader.item, "appWindowWithShadow");
209+ loadWithGalleryApp.clicked();
210+ var displacedAppWindowWithShadow = findChild(spreadDelegateLoader.item, "displacedAppWindowWithShadow");
123211
124− verify(appWindowWithShadow.y === 0);
212+ verify(displacedAppWindowWithShadow.y === 0);
125213
126214 swipeToCloseCheckbox.checked = data.swipeToClose;
127215 closeableCheckbox.checked = data.closeable;
@@ -135,14 +223,14 @@ Rectangle {
135223 true /* beginTouch */, false /* endTouch */, dragArea.minSpeedToClose * 1.1 /* speed */);
136224
137225 if (data.swipeToClose) {
138− verify(appWindowWithShadow.y < 0);
226+ verify(displacedAppWindowWithShadow.y < 0);
139227 var threshold = findChild(spreadDelegateLoader.item, "dragArea").threshold
140228 if (data.closeable) {
141229 // Verify that the delegate started moving exactly "threshold" after the finger movement
142230 // and did not jump up to the finger, but lags the threshold behind
143− compare(Math.abs(Math.abs(appWindowWithShadow.y) - dragDistance), threshold);
231+ compare(Math.abs(Math.abs(displacedAppWindowWithShadow.y) - dragDistance), threshold);
144232 } else {
145− verify(Math.abs(Math.abs(appWindowWithShadow.y) - dragDistance) > threshold);
233+ verify(Math.abs(Math.abs(displacedAppWindowWithShadow.y) - dragDistance) > threshold);
146234 }
147235
148236 touchRelease(spreadDelegateLoader.item, touchX, toY - units.gu(1));
@@ -153,16 +241,63 @@ Rectangle {
153241 verify(spyClosedSignal.count === 1);
154242 } else {
155243 verify(spyClosedSignal.count === 0);
156− tryCompare(appWindowWithShadow, "y", 0);
244+ tryCompare(displacedAppWindowWithShadow, "y", 0);
157245 }
158246
159247 } else {
160− verify(appWindowWithShadow.y === 0);
248+ verify(displacedAppWindowWithShadow.y === 0);
161249
162250 touchRelease(spreadDelegateLoader.item, touchX, toY);
163251 }
164252 }
165253
254+ function test_loadingLandscapeOnlyAppWhenShellInPortrait() {
255+ loadWithWeatherApp.clicked();
256+
257+ var appWindow = findChild(spreadDelegate, "appWindow_ubuntu-weather-app");
258+ verify(appWindow);
259+
260+ // It must have landscape dimensions as it does not support portrait
261+ tryCompare(appWindow, "width", fakeShell.height);
262+ tryCompare(appWindow, "height", fakeShell.width - spreadDelegate.maximizedAppTopMargin);
263+ }
264+
265+ function test_keepsSceneTransformationWhenShellRotates_data() {
266+ return [
267+ {tag: "0", selectedIndex: 0},
268+ {tag: "90", selectedIndex: 1},
269+ {tag: "180", selectedIndex: 2},
270+ {tag: "270", selectedIndex: 3}
271+ ];
272+ }
273+ function test_keepsSceneTransformationWhenShellRotates(data) {
274+ loadWithGalleryApp.clicked();
275+
276+ var appWindowWithShadow = findChild(spreadDelegate, "appWindowWithShadow");
277+ verify(appWindowWithShadow);
278+
279+ // Wait until it reaches the state we are interested on.
280+ // It begins with "startingUp"
281+ tryCompare(appWindowWithShadow, "state", "keepSceneRotation");
282+
283+ shellOrientationAngleSelector.selectedIndex = data.selectedIndex;
284+
285+ // must keep same aspect ratio
286+ compare(appWindowWithShadow.width, fakeShell.shortestDimension);
287+ compare(appWindowWithShadow.height, fakeShell.longestDimension);
288+
289+
290+ // and scene transformation must be the identity (ie, no rotation or translation)
291+ var pointInDelegateCoords = appWindowWithShadow.mapToItem(root, 0, 0);
292+ compare(pointInDelegateCoords.x, 0);
293+ compare(pointInDelegateCoords.y, 0);
294+
295+ pointInDelegateCoords = appWindowWithShadow.mapToItem(root,
296+ fakeShell.shortestDimension, fakeShell.longestDimension);
297+ compare(pointInDelegateCoords.x, fakeShell.shortestDimension);
298+ compare(pointInDelegateCoords.y, fakeShell.longestDimension);
299+ }
300+
166301 function waitForCloseAnimationToFinish() {
167302 var closeAnimation = findInvisibleChild(spreadDelegateLoader.item, "closeAnimation");
168303 wait(closeAnimation.duration * 1.5);
tests/qmltests/Stages/tst_TabletStage.qml+51
@@ -49,6 +49,11 @@ Rectangle {
4949 dragAreaWidth: units.gu(2)
5050 maximizedAppTopMargin: units.gu(3) + units.dp(2)
5151 interactive: true
52+ shellOrientation: Qt.LandscapeOrientation
53+ shellPrimaryOrientation: Qt.LandscapeOrientation
54+ nativeOrientation: Qt.LandscapeOrientation
55+ nativeWidth: width
56+ nativeHeight: height
5257 focus: true
5358 }
5459 }
@@ -149,6 +154,52 @@ Rectangle {
149154 tryCompare(dialerApp.session.surface, "activeFocus", true);
150155 tryCompare(webbrowserApp.session.surface, "activeFocus", false);
151156 }
157+
158+
159+ function test_closeAppInSideStage() {
160+ dialerCheckBox.checked = true;
161+ waitUntilAppSurfaceShowsUp(dialerCheckBox.appId);
162+
163+ performEdgeSwipeToShowAppSpread();
164+
165+ var appDelegate = findChild(tabletStage, "tabletSpreadDelegate_" + dialerCheckBox.appId);
166+ verify(appDelegate);
167+ tryCompare(appDelegate, "swipeToCloseEnabled", true);
168+
169+ swipeAppUpwards(dialerCheckBox.appId);
170+
171+ // Check that dialer-app has been closed
172+
173+ tryCompareFunction(function() {
174+ return findChild(tabletStage, "appWindow_" + dialerCheckBox.appId);
175+ }, null);
176+
177+ tryCompareFunction(function() {
178+ return ApplicationManager.findApplication(dialerCheckBox.appId);
179+ }, null);
180+ }
181+
182+ function performEdgeSwipeToShowAppSpread() {
183+ var touchStartY = tabletStage.height / 2;
184+ touchFlick(tabletStage,
185+ tabletStage.width - 1, touchStartY,
186+ 0, touchStartY);
187+
188+ var spreadView = findChild(tabletStage, "spreadView");
189+ verify(spreadView);
190+ tryCompare(spreadView, "phase", 2);
191+ tryCompare(spreadView, "flicking", false);
192+ tryCompare(spreadView, "moving", false);
193+ }
194+
195+ function swipeAppUpwards(appId) {
196+ var appWindow = findChild(tabletStage, "appWindow_" + appId);
197+ verify(appWindow);
198+
199+ touchFlick(appWindow,
200+ appWindow.width / 2, appWindow.height / 2,
201+ appWindow.width / 2, -appWindow.height / 2);
202+ }
152203 }
153204
154205}
tests/qmltests/Stages/tst_WindowMoveResizeArea.qml+1
@@ -70,6 +70,7 @@ Item {
7070 }
7171
7272 UT.UnityTestCase {
73+ name: "WindowMoveResizeArea"
7374 when: windowShown
7475
7576 function init() {
tests/qmltests/Tutorial/tst_Tutorial.qml+1
@@ -73,6 +73,7 @@ Item {
7373 sourceComponent: Component {
7474 Shell {
7575 property string indicatorProfile: "phone"
76+ property string shellMode: "full-greeter" /* default */
7677
7778 Component.onDestruction: {
7879 shellLoader.itemDestroyed = true;
tests/qmltests/tst_OrientedShell.qmlnew file+1151
@@ -0,0 +1,1151 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import QtQuick.Layouts 1.1
19+import QtTest 1.0
20+import Ubuntu.Components 1.1
21+import Ubuntu.Components.ListItems 1.0 as ListItem
22+import Unity.Application 0.1
23+import Unity.Test 0.1
24+import LightDM 0.1 as LightDM
25+import Powerd 0.1
26+
27+import "../../qml"
28+
29+Rectangle {
30+ id: root
31+ color: "grey"
32+ width: units.gu(160) + controls.width
33+ height: units.gu(100)
34+
35+ QtObject {
36+ id: applicationArguments
37+
38+ function hasGeometry() {
39+ return false;
40+ }
41+
42+ function width() {
43+ return 0;
44+ }
45+
46+ function height() {
47+ return 0;
48+ }
49+ property string deviceName: "mako"
50+ }
51+
52+ QtObject {
53+ id: mockOrientationLock
54+ property int savedOrientation
55+ }
56+
57+ QtObject {
58+ id: mockUsageModeSettings
59+ property string usageMode: usageModeSelector.model[usageModeSelector.selectedIndex]
60+ }
61+
62+ property int physicalOrientation0
63+ property int physicalOrientation90
64+ property int physicalOrientation180
65+ property int physicalOrientation270
66+ property real primaryOrientationAngle
67+
68+ state: applicationArguments.deviceName
69+ states: [
70+ State {
71+ name: "mako"
72+ PropertyChanges {
73+ target: orientedShellLoader
74+ width: units.gu(40)
75+ height: units.gu(71)
76+ }
77+ PropertyChanges {
78+ target: root
79+ physicalOrientation0: Qt.PortraitOrientation
80+ physicalOrientation90: Qt.InvertedLandscapeOrientation
81+ physicalOrientation180: Qt.InvertedPortraitOrientation
82+ physicalOrientation270: Qt.LandscapeOrientation
83+ primaryOrientationAngle: 0
84+ }
85+ },
86+ State {
87+ name: "manta"
88+ PropertyChanges {
89+ target: orientedShellLoader
90+ width: units.gu(160)
91+ height: units.gu(100)
92+ }
93+ PropertyChanges {
94+ target: root
95+ physicalOrientation90: Qt.PortraitOrientation
96+ physicalOrientation180: Qt.InvertedLandscapeOrientation
97+ physicalOrientation270: Qt.InvertedPortraitOrientation
98+ physicalOrientation0: Qt.LandscapeOrientation
99+ primaryOrientationAngle: 0
100+ }
101+ },
102+ State {
103+ name: "flo"
104+ PropertyChanges {
105+ target: orientedShellLoader
106+ width: units.gu(62)
107+ height: units.gu(100)
108+ }
109+ PropertyChanges {
110+ target: root
111+ physicalOrientation270: Qt.PortraitOrientation
112+ physicalOrientation0: Qt.InvertedLandscapeOrientation
113+ physicalOrientation90: Qt.InvertedPortraitOrientation
114+ physicalOrientation180: Qt.LandscapeOrientation
115+ primaryOrientationAngle: 90
116+ }
117+ }
118+ ]
119+
120+ Loader {
121+ id: orientedShellLoader
122+
123+ x: ((root.width - controls.width) - width) / 2
124+ y: (root.height - height) / 2
125+
126+ focus: true
127+
128+ property bool itemDestroyed: false
129+ sourceComponent: Component {
130+ OrientedShell {
131+ anchors.fill: parent
132+ usageModeSettings: mockUsageModeSettings
133+ physicalOrientation: root.physicalOrientation0
134+ orientationLocked: orientationLockedCheckBox.checked
135+ orientationLock: mockOrientationLock
136+ Component.onDestruction: {
137+ orientedShellLoader.itemDestroyed = true;
138+ }
139+ }
140+ }
141+ }
142+
143+ function orientationsToStr(orientations) {
144+ if (orientations === Qt.PrimaryOrientation) {
145+ return "Primary";
146+ } else {
147+ var str = "";
148+ if (orientations & Qt.PortraitOrientation) {
149+ str += " Portrait";
150+ }
151+ if (orientations & Qt.InvertedPortraitOrientation) {
152+ str += " InvertedPortrait";
153+ }
154+ if (orientations & Qt.LandscapeOrientation) {
155+ str += " Landscape";
156+ }
157+ if (orientations & Qt.InvertedLandscapeOrientation) {
158+ str += " InvertedLandscape";
159+ }
160+ return str;
161+ }
162+ }
163+
164+ Rectangle {
165+ width: controls.width
166+ anchors {
167+ top: parent.top
168+ bottom: parent.bottom
169+ right: parent.right
170+ }
171+ color: "darkgrey"
172+ }
173+ Flickable {
174+ id: controls
175+ width: units.gu(30)
176+ anchors {
177+ top: parent.top
178+ bottom: parent.bottom
179+ right: parent.right
180+ }
181+
182+ boundsBehavior: Flickable.StopAtBounds
183+ contentHeight: controlsColumn.height
184+
185+ Column {
186+ id: controlsColumn
187+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
188+ spacing: units.gu(1)
189+ Button {
190+ text: "Show Greeter"
191+ activeFocusOnPress: false
192+ onClicked: {
193+ if (orientedShellLoader.status !== Loader.Ready)
194+ return;
195+
196+ LightDM.Greeter.showGreeter();
197+ }
198+ }
199+ Label {
200+ text: "Physical Orientation:"
201+ }
202+ Button {
203+ id: rotate0Button
204+ text: root.orientationsToStr(root.physicalOrientation0) + " (0)"
205+ activeFocusOnPress: false
206+ onClicked: {
207+ orientedShellLoader.item.physicalOrientation = root.physicalOrientation0;
208+ }
209+ }
210+ Button {
211+ id: rotate90Button
212+ text: root.orientationsToStr(root.physicalOrientation90) + " (90)"
213+ activeFocusOnPress: false
214+ onClicked: {
215+ orientedShellLoader.item.physicalOrientation = root.physicalOrientation90;
216+ }
217+ }
218+ Button {
219+ id: rotate180Button
220+ text: root.orientationsToStr(root.physicalOrientation180) + " (180)"
221+ activeFocusOnPress: false
222+ onClicked: {
223+ orientedShellLoader.item.physicalOrientation = root.physicalOrientation180;
224+ }
225+ }
226+ Button {
227+ id: rotate270Button
228+ text: root.orientationsToStr(root.physicalOrientation270) + " (270)"
229+ activeFocusOnPress: false
230+ onClicked: {
231+ orientedShellLoader.item.physicalOrientation = root.physicalOrientation270;
232+ }
233+ }
234+ RowLayout {
235+ Layout.fillWidth: true
236+ CheckBox {
237+ id: orientationLockedCheckBox
238+ checked: false
239+ activeFocusOnPress: false
240+ }
241+ Label {
242+ text: "Orientation Locked"
243+ anchors.verticalCenter: parent.verticalCenter
244+ }
245+ }
246+ Button {
247+ text: "Power dialog"
248+ activeFocusOnPress: false
249+ onClicked: { testCase.showPowerDialog(); }
250+ }
251+ ListItem.ItemSelector {
252+ id: deviceNameSelector
253+ anchors { left: parent.left; right: parent.right }
254+ activeFocusOnPress: false
255+ text: "Device Name"
256+ model: ["mako", "manta", "flo"]
257+ onSelectedIndexChanged: {
258+ testCase.tearDown();
259+ applicationArguments.deviceName = model[selectedIndex];
260+ orientedShellLoader.active = true;
261+ }
262+ }
263+ ListItem.ItemSelector {
264+ id: usageModeSelector
265+ anchors { left: parent.left; right: parent.right }
266+ activeFocusOnPress: false
267+ text: "Usage Mode"
268+ model: ["Staged", "Windowed", "Automatic"]
269+ }
270+ Button {
271+ text: "Switch fullscreen"
272+ activeFocusOnPress: false
273+ onClicked: {
274+ var app = ApplicationManager.findApplication(ApplicationManager.focusedApplicationId);
275+ app.fullscreen = !app.fullscreen;
276+ }
277+ }
278+ RowLayout {
279+ Layout.fillWidth: true
280+ CheckBox {
281+ checked: false
282+ activeFocusOnPress: false
283+ onCheckedChanged: {
284+ var surface = SurfaceManager.inputMethodSurface();
285+ if (checked) {
286+ surface.setState(MirSurfaceItem.Restored);
287+ } else {
288+ surface.setState(MirSurfaceItem.Minimized);
289+ }
290+ }
291+ }
292+ Label {
293+ text: "Input Method"
294+ anchors.verticalCenter: parent.verticalCenter
295+ }
296+ }
297+ Button {
298+ text: Powerd.status === Powerd.On ? "Display ON" : "Display OFF"
299+ activeFocusOnPress: false
300+ onClicked: {
301+ if (Powerd.status === Powerd.On) {
302+ Powerd.status = Powerd.Off;
303+ } else {
304+ Powerd.status = Powerd.On;
305+ }
306+ }
307+ }
308+ }
309+ }
310+
311+ UnityTestCase {
312+ id: testCase
313+ name: "OrientedShell"
314+ when: windowShown
315+
316+ property Item orientedShell: orientedShellLoader.status === Loader.Ready ? orientedShellLoader.item : null
317+ property Item shell
318+
319+ SignalSpy { id: signalSpy }
320+ SignalSpy { id: signalSpy2 }
321+
322+ Connections {
323+ id: spreadRepeaterConnections
324+ ignoreUnknownSignals : true
325+ property var itemAddedCallback: null
326+ onItemAdded: {
327+ if (itemAddedCallback) {
328+ itemAddedCallback(item);
329+ }
330+ }
331+ }
332+
333+ function init() {
334+ if (orientedShellLoader.active) {
335+ // happens for the very first test function as shell
336+ // is loaded by default
337+ tearDown();
338+ }
339+ }
340+
341+ function tearDown() {
342+ orientedShellLoader.itemDestroyed = false;
343+ orientedShellLoader.active = false;
344+
345+ tryCompare(orientedShellLoader, "status", Loader.Null);
346+ tryCompare(orientedShellLoader, "item", null);
347+ // Loader.status might be Loader.Null and Loader.item might be null but the Loader
348+ // actually took place. Likely because Loader waits until the next event loop
349+ // iteration to do its work. So to ensure the reload, we will wait until the
350+ // Shell instance gets destroyed.
351+ tryCompare(orientedShellLoader, "itemDestroyed", true);
352+
353+ // kill all (fake) running apps
354+ killApps();
355+
356+ spreadRepeaterConnections.target = null;
357+ spreadRepeaterConnections.itemAddedCallback = null;
358+ signalSpy.target = null;
359+ signalSpy.signalName = "";
360+
361+ LightDM.Greeter.authenticate(""); // reset greeter
362+ }
363+
364+ function cleanup() {
365+ tryCompare(shell, "enabled", true); // make sure greeter didn't leave us in disabled state
366+ shell = null;
367+
368+ tearDown();
369+ }
370+
371+ function test_appSupportingOnlyPrimaryOrientationMakesShellStayPut_data() {
372+ return [
373+ {tag: "mako", deviceName: "mako"},
374+ {tag: "manta", deviceName: "manta"},
375+ {tag: "flo", deviceName: "flo"}
376+ ];
377+ }
378+ function test_appSupportingOnlyPrimaryOrientationMakesShellStayPut(data) {
379+ loadShell(data.deviceName);
380+
381+ // unity8-dash supports only primary orientation and should be already running
382+ var dashAppWindow = findChild(shell, "appWindow_unity8-dash");
383+ verify(dashAppWindow);
384+ compare(ApplicationManager.focusedApplicationId, "unity8-dash");
385+ var dashApp = dashAppWindow.application
386+ verify(dashApp);
387+ compare(dashApp.rotatesWindowContents, false);
388+ compare(dashApp.supportedOrientations, Qt.PrimaryOrientation);
389+ compare(dashApp.stage, ApplicationInfoInterface.MainStage);
390+
391+ tryCompareFunction(function(){return dashApp.session.surface != null;}, true);
392+ verify(checkAppSurfaceOrientation(dashApp, root.primaryOrientationAngle));
393+
394+ compare(shell.transformRotationAngle, root.primaryOrientationAngle);
395+ rotateTo(90);
396+
397+ verify(checkAppSurfaceOrientation(dashApp, root.primaryOrientationAngle));
398+ compare(shell.transformRotationAngle, root.primaryOrientationAngle);
399+
400+ rotateTo(180);
401+
402+ verify(checkAppSurfaceOrientation(dashApp, root.primaryOrientationAngle));
403+ compare(shell.transformRotationAngle, root.primaryOrientationAngle);
404+
405+ rotateTo(270);
406+
407+ verify(checkAppSurfaceOrientation(dashApp, root.primaryOrientationAngle));
408+ compare(shell.transformRotationAngle, root.primaryOrientationAngle);
409+ }
410+
411+ function test_greeterRemainsInPrimaryOrientation_data() {
412+ return [
413+ {tag: "mako", deviceName: "mako"},
414+ {tag: "manta", deviceName: "manta"},
415+ {tag: "flo", deviceName: "flo"}
416+ ];
417+ }
418+ function test_greeterRemainsInPrimaryOrientation(data) {
419+ loadShell(data.deviceName);
420+
421+ var gmailApp = ApplicationManager.startApplication("gmail-webapp");
422+ verify(gmailApp);
423+
424+ // ensure the mock gmail-webapp is as we expect
425+ compare(gmailApp.rotatesWindowContents, false);
426+ compare(gmailApp.supportedOrientations, Qt.PortraitOrientation | Qt.LandscapeOrientation
427+ | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation);
428+ compare(gmailApp.stage, ApplicationInfoInterface.MainStage);
429+
430+ // wait until it's able to rotate
431+ tryCompare(shell, "orientationChangesEnabled", true);
432+
433+ compare(shell.transformRotationAngle, root.primaryOrientationAngle);
434+ rotateTo(90);
435+ tryCompare(shell, "transformRotationAngle", root.primaryOrientationAngle + 90);
436+
437+ showGreeter();
438+
439+ tryCompare(shell, "transformRotationAngle", root.primaryOrientationAngle);
440+ rotateTo(180);
441+ compare(shell.transformRotationAngle, root.primaryOrientationAngle);
442+ rotateTo(270);
443+ compare(shell.transformRotationAngle, root.primaryOrientationAngle);
444+ rotateTo(0);
445+ compare(shell.transformRotationAngle, root.primaryOrientationAngle);
446+ }
447+
448+ function test_appRotatesWindowContents_data() {
449+ return [
450+ {tag: "mako", deviceName: "mako", orientationAngleAfterRotation: 90},
451+ {tag: "manta", deviceName: "manta", orientationAngleAfterRotation: 90},
452+ {tag: "flo", deviceName: "flo", orientationAngleAfterRotation: 180}
453+ ];
454+ }
455+ function test_appRotatesWindowContents(data) {
456+ loadShell(data.deviceName);
457+ var cameraApp = ApplicationManager.startApplication("camera-app");
458+ verify(cameraApp);
459+
460+ // ensure the mock camera-app is as we expect
461+ compare(cameraApp.fullscreen, true);
462+ compare(cameraApp.rotatesWindowContents, true);
463+ compare(cameraApp.supportedOrientations, Qt.PortraitOrientation | Qt.LandscapeOrientation
464+ | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation);
465+
466+ waitUntilAppSurfaceShowsUp("camera-app")
467+
468+ var cameraSurface = cameraApp.session.surface;
469+ verify(cameraSurface);
470+
471+ var focusChangedSpy = signalSpy;
472+ focusChangedSpy.clear();
473+ focusChangedSpy.target = cameraSurface;
474+ focusChangedSpy.signalName = "activeFocusChanged";
475+ verify(focusChangedSpy.valid);
476+
477+ verify(cameraSurface.activeFocus);
478+
479+ tryCompare(shell, "orientationChangesEnabled", true);
480+
481+ var rotationStates = findInvisibleChild(orientedShell, "rotationStates");
482+ verify(rotationStates);
483+ var immediateTransition = null
484+ for (var i = 0; i < rotationStates.transitions.length && !immediateTransition; ++i) {
485+ var transition = rotationStates.transitions[i];
486+ if (transition.objectName == "immediateTransition") {
487+ immediateTransition = transition;
488+ }
489+ }
490+ verify(immediateTransition);
491+ var transitionSpy = signalSpy2;
492+ transitionSpy.clear();
493+ transitionSpy.target = immediateTransition;
494+ transitionSpy.signalName = "runningChanged";
495+ verify(transitionSpy.valid);
496+
497+ rotateTo(90);
498+
499+ tryCompare(cameraSurface, "orientationAngle", data.orientationAngleAfterRotation);
500+
501+ // the rotation should have been immediate
502+ // false -> true -> false
503+ compare(transitionSpy.count, 2);
504+
505+ // It should retain native dimensions regardless of its rotation/orientation
506+ compare(cameraSurface.width, orientedShell.width);
507+ compare(cameraSurface.height, orientedShell.height);
508+
509+ // Surface focus shouldn't have been touched because of the rotation
510+ compare(focusChangedSpy.count, 0);
511+ }
512+
513+ /*
514+ Preconditions:
515+ Shell orientation angle matches the screen one.
516+
517+ Use case:
518+ User switches to an app that has an orientation angle different from the
519+ shell one but that also happens to support the current shell orientation
520+ angle.
521+
522+ Expected outcome:
523+ The app should get rotated to match shell's orientation angle
524+ */
525+ function test_switchingToAppWithDifferentRotation_data() {
526+ return [
527+ {tag: "mako", deviceName: "mako", shellAngleAfterRotation: 90},
528+ {tag: "manta", deviceName: "manta", shellAngleAfterRotation: 90},
529+ {tag: "flo", deviceName: "flo", shellAngleAfterRotation: 180}
530+ ];
531+ }
532+ function test_switchingToAppWithDifferentRotation(data) {
533+ loadShell(data.deviceName);
534+ var gmailApp = ApplicationManager.startApplication("gmail-webapp");
535+ verify(gmailApp);
536+
537+ // ensure the mock gmail-webapp is as we expect
538+ compare(gmailApp.rotatesWindowContents, false);
539+ compare(gmailApp.supportedOrientations, Qt.PortraitOrientation | Qt.LandscapeOrientation
540+ | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation);
541+ compare(gmailApp.stage, ApplicationInfoInterface.MainStage);
542+
543+ waitUntilAppSurfaceShowsUp("gmail-webapp")
544+
545+ var musicApp = ApplicationManager.startApplication("music-app");
546+ verify(musicApp);
547+
548+ // ensure the mock music-app is as we expect
549+ compare(musicApp.rotatesWindowContents, false);
550+ compare(musicApp.supportedOrientations, Qt.PortraitOrientation | Qt.LandscapeOrientation
551+ | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation);
552+ compare(musicApp.stage, ApplicationInfoInterface.MainStage);
553+
554+ waitUntilAppSurfaceShowsUp("music-app")
555+ tryCompare(shell, "orientationChangesEnabled", true);
556+
557+ rotateTo(90);
558+ tryCompare(shell, "transformRotationAngle", data.shellAngleAfterRotation);
559+
560+ performEdgeSwipeToSwitchToPreviousApp();
561+
562+ tryCompare(shell, "mainAppWindowOrientationAngle", data.shellAngleAfterRotation);
563+ compare(shell.transformRotationAngle, data.shellAngleAfterRotation);
564+ }
565+
566+ /*
567+ Preconditions:
568+ - Device supports portrait, landscape and inverted-landscape
569+
570+ Steps:
571+ 1 - Launch app that supports all orientations
572+ 2 - Rotate device to inverted-landscape
573+ 3 - See that shell gets rotated to inverted-landscape accordingly
574+ 4 - Rotate device to inverted-portrait
575+
576+ Expected outcome:
577+ Shell stays at inverted-landscape
578+
579+ Actual outcome:
580+ Shell rotates to landscape
581+
582+ Comments:
583+ Rationale being that shell should be rotated to the closest supported orientation.
584+ In that case, landscape and inverted-landscape are both 90 degrees away from the physical
585+ orientation (Screen.orientation), so they are both equally good alternatives
586+ */
587+ function test_rotateToUnsupportedDeviceOrientation(data) {
588+ loadShell("mako");
589+ var twitterApp = ApplicationManager.startApplication("twitter-webapp");
590+ verify(twitterApp);
591+
592+ // ensure the mock twitter-webapp is as we expect
593+ compare(twitterApp.rotatesWindowContents, false);
594+ compare(twitterApp.supportedOrientations, Qt.PortraitOrientation | Qt.LandscapeOrientation
595+ | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation);
596+
597+ waitUntilAppSurfaceShowsUp("twitter-webapp")
598+
599+ rotateTo(data.rotationAngle);
600+ tryCompare(shell, "transformRotationAngle", data.rotationAngle);
601+
602+ rotateTo(180);
603+ tryCompare(shell, "transformRotationAngle", data.rotationAngle);
604+ }
605+ function test_rotateToUnsupportedDeviceOrientation_data() {
606+ return [
607+ {tag: "90", rotationAngle: 90},
608+ {tag: "270", rotationAngle: 270}
609+ ];
610+ }
611+
612+ function test_launchLandscapeOnlyAppFromPortrait() {
613+ loadShell("mako");
614+ var weatherApp = ApplicationManager.startApplication("ubuntu-weather-app");
615+ verify(weatherApp);
616+
617+ // ensure the mock app is as we expect
618+ compare(weatherApp.supportedOrientations, Qt.LandscapeOrientation | Qt.InvertedLandscapeOrientation);
619+
620+ waitUntilAppSurfaceShowsUp("ubuntu-weather-app");
621+
622+ var rotationStates = findInvisibleChild(orientedShell, "rotationStates");
623+ waitUntilTransitionsEnd(rotationStates);
624+
625+ tryCompareFunction(function (){return shell.transformRotationAngle === 90
626+ || shell.transformRotationAngle === 270;}, true);
627+ }
628+
629+ /*
630+ - launch an app that supports all orientations, such as twitter-webapp
631+ - wait a bit until that app is considered to have finished initializing and is thus
632+ ready to get resized/rotated
633+ - switch back to dash
634+ - rotate device to 90 degrees
635+ - Physical orientation is 90 but Shell orientation is kept at 0 because unity8-dash
636+ doesn't support such orientation
637+ - do a long right-edge drag to show the apps spread
638+ - tap on twitter-webapp
639+
640+ Shell will rotate to match the physical orientation.
641+
642+ This is a kind of tricky case as there are a few things happening at the same time:
643+ 1 - Stage switching from apps spread (phase 2) to showing the focused app (phase 0)
644+ 2 - orientation and aspect ratio (ie size) changes
645+
646+ This may trigger some corner case bugs. such as one were
647+ the greeter is not kept completely outside the shell, causing the black rect in Shell.qml
648+ have an opacity > 0.
649+ */
650+ function test_greeterStaysAwayAfterRotation() {
651+ loadShell("mako");
652+ var twitterApp = ApplicationManager.startApplication("twitter-webapp");
653+ verify(twitterApp);
654+
655+ // ensure the mock twitter-webapp is as we expect
656+ compare(twitterApp.rotatesWindowContents, false);
657+ compare(twitterApp.supportedOrientations, Qt.PortraitOrientation | Qt.LandscapeOrientation
658+ | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation);
659+
660+ waitUntilAppSurfaceShowsUp("twitter-webapp");
661+ waitUntilAppWindowCanRotate("twitter-webapp");
662+
663+ // go back to unity8-dash
664+ performEdgeSwipeToSwitchToPreviousApp();
665+
666+ rotateTo(90);
667+ wait(1); // spin the event loop to let all bindings do their thing
668+ // should not rotat as unity8-dash doesn't support it
669+ tryCompare(shell, "transformRotationAngle", 0);
670+
671+ performEdgeSwipeToShowAppSpread();
672+
673+ // wait until things have settled
674+ var greeter = findChild(shell, "greeter");
675+ tryCompare(greeter, "animating", false);
676+
677+ var twitterDelegate = findChild(shell, "appDelegate1");
678+ compare(twitterDelegate.application.appId, "twitter-webapp");
679+ twitterDelegate.clicked();
680+
681+ // now it should finally follow the physical orientation
682+ tryCompare(shell, "transformRotationAngle", 90);
683+
684+ // greeter should remaing completely hidden
685+ tryCompare(greeter, "shown", false);
686+ }
687+
688+ function test_appInSideStageDoesntRotateOnStartUp_data() {
689+ return [
690+ {tag: "manta", deviceName: "manta"},
691+ {tag: "flo", deviceName: "flo"}
692+ ];
693+ }
694+ function test_appInSideStageDoesntRotateOnStartUp(data) {
695+ loadShell(data.deviceName);
696+
697+ var twitterDelegate = null;
698+
699+ verify(spreadRepeaterConnections.target);
700+ spreadRepeaterConnections.itemAddedCallback = function(item) {
701+ verify(item.application.appId, "twitter-webapp");
702+ twitterDelegate = item;
703+ signalSpy.target = findInvisibleChild(item, "orientationChangeAnimation");
704+ verify(signalSpy.valid);
705+ }
706+
707+ signalSpy.clear();
708+ signalSpy.target = null;
709+ signalSpy.signalName = "runningChanged";
710+
711+ var twitterApp = ApplicationManager.startApplication("twitter-webapp");
712+ verify(twitterApp);
713+
714+ // ensure the mock twitter-webapp is as we expect
715+ compare(twitterApp.rotatesWindowContents, false);
716+ compare(twitterApp.supportedOrientations, Qt.PortraitOrientation | Qt.LandscapeOrientation
717+ | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation);
718+ compare(twitterApp.stage, ApplicationInfoInterface.SideStage);
719+
720+ // Wait until spreadRepeaterConnections hs caught the new SpreadDelegate and
721+ // set up the signalSpy target accordingly.
722+ tryCompareFunction(function(){ return signalSpy.target != null && signalSpy.valid; }, true);
723+
724+ tryCompare(twitterDelegate, "orientationChangesEnabled", true);
725+
726+ var appWindowWithShadow = findChild(twitterDelegate, "appWindowWithShadow");
727+ verify(appWindowWithShadow);
728+ tryCompare(appWindowWithShadow, "state", "keepSceneRotation");
729+
730+ // no reason for any rotation animation to have taken place
731+ compare(signalSpy.count, 0);
732+ }
733+
734+ function test_portraitOnlyAppInSideStage_data() {
735+ return [
736+ {tag: "manta", deviceName: "manta"},
737+ {tag: "flo", deviceName: "flo"}
738+ ];
739+ }
740+ function test_portraitOnlyAppInSideStage(data) {
741+ loadShell(data.deviceName);
742+
743+ var dialerDelegate = null;
744+ verify(spreadRepeaterConnections.target);
745+ spreadRepeaterConnections.itemAddedCallback = function(item) {
746+ dialerDelegate = item;
747+ verify(item.application.appId, "dialer-app");
748+ }
749+
750+ var dialerApp = ApplicationManager.startApplication("dialer-app");
751+ verify(dialerApp);
752+
753+ // ensure the mock dialer-app is as we expect
754+ compare(dialerApp.rotatesWindowContents, false);
755+ compare(dialerApp.supportedOrientations, Qt.PortraitOrientation | Qt.InvertedPortraitOrientation);
756+ compare(dialerApp.stage, ApplicationInfoInterface.SideStage);
757+
758+ tryCompareFunction(function(){ return dialerDelegate != null; }, true);
759+ tryCompare(dialerDelegate, "orientationChangesEnabled", true);
760+
761+ var appWindowWithShadow = findChild(dialerDelegate, "appWindowWithShadow");
762+ verify(appWindowWithShadow);
763+ tryCompare(appWindowWithShadow, "state", "keepSceneRotation");
764+
765+ // app must have portrait aspect ratio
766+ verify(appWindowWithShadow.width < appWindowWithShadow.height);
767+
768+ compare(appWindowWithShadow.rotation, 0);
769+
770+ verifyAppWindowWithinSpreadDelegateBoundaries(dialerDelegate);
771+
772+ // shell should remain in its primery orientation as the app in the main stage
773+ // is the one that dictates its orientation. In this case it's unity8-dash
774+ // which supports only primary orientation
775+ compare(shell.orientation, orientedShell.primaryOrientation);
776+ }
777+
778+ function test_sideStageAppsRemainPortraitInSpread_data() {
779+ return [
780+ {tag: "manta", deviceName: "manta"},
781+ {tag: "flo", deviceName: "flo"}
782+ ];
783+ }
784+ function test_sideStageAppsRemainPortraitInSpread(data) {
785+ loadShell(data.deviceName);
786+
787+
788+ ////
789+ // Launch dialer
790+ var dialerDelegate = null;
791+ verify(spreadRepeaterConnections.target);
792+ spreadRepeaterConnections.itemAddedCallback = function(item) {
793+ dialerDelegate = item;
794+ verify(item.application.appId, "dialer-app");
795+ }
796+
797+ var dialerApp = ApplicationManager.startApplication("dialer-app");
798+ verify(dialerApp);
799+
800+ // ensure the mock dialer-app is as we expect
801+ compare(dialerApp.rotatesWindowContents, false);
802+ compare(dialerApp.supportedOrientations, Qt.PortraitOrientation | Qt.InvertedPortraitOrientation);
803+ compare(dialerApp.stage, ApplicationInfoInterface.SideStage);
804+
805+ tryCompareFunction(function(){ return dialerDelegate != null; }, true);
806+ waitUntilAppDelegateIsFullyInit(dialerDelegate);
807+
808+
809+ ////
810+ // Launch twitter
811+ var twitterDelegate = null;
812+ spreadRepeaterConnections.itemAddedCallback = function(item) {
813+ twitterDelegate = item;
814+ verify(item.application.appId, "twitter-webapp");
815+ }
816+
817+ var twitterApp = ApplicationManager.startApplication("twitter-webapp");
818+ verify(twitterApp);
819+
820+ // ensure the mock twitter-webapp is as we expect
821+ compare(twitterApp.rotatesWindowContents, false);
822+ compare(twitterApp.supportedOrientations, Qt.PortraitOrientation | Qt.LandscapeOrientation
823+ | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation);
824+ compare(twitterApp.stage, ApplicationInfoInterface.SideStage);
825+
826+ tryCompareFunction(function(){ return twitterDelegate != null; }, true);
827+ waitUntilAppDelegateIsFullyInit(twitterDelegate);
828+
829+ ////
830+ // Edge swipe to show spread and check orientations
831+
832+ performEdgeSwipeToShowAppSpread();
833+
834+ {
835+ var appWindowWithShadow = findChild(dialerDelegate, "appWindowWithShadow");
836+ verify(appWindowWithShadow);
837+ tryCompare(appWindowWithShadow, "state", "keepSceneRotation");
838+ compare(appWindowWithShadow.width, dialerDelegate.width);
839+ compare(appWindowWithShadow.height, dialerDelegate.height);
840+ compare(appWindowWithShadow.rotation, 0);
841+ }
842+ {
843+ var appWindowWithShadow = findChild(twitterDelegate, "appWindowWithShadow");
844+ verify(appWindowWithShadow);
845+ tryCompare(appWindowWithShadow, "state", "keepSceneRotation");
846+ compare(appWindowWithShadow.width, twitterDelegate.width);
847+ compare(appWindowWithShadow.height, twitterDelegate.height);
848+ compare(appWindowWithShadow.rotation, 0);
849+ }
850+
851+ verifyAppWindowWithinSpreadDelegateBoundaries(dialerDelegate);
852+ verifyAppWindowWithinSpreadDelegateBoundaries(twitterDelegate);
853+ }
854+
855+ function test_launchedAppHasActiveFocus_data() {
856+ return [
857+ {tag: "mako", deviceName: "mako"},
858+ {tag: "manta", deviceName: "manta"},
859+ {tag: "flo", deviceName: "flo"}
860+ ];
861+ }
862+ function test_launchedAppHasActiveFocus(data) {
863+ loadShell(data.deviceName);
864+
865+ var gmailApp = ApplicationManager.startApplication("gmail-webapp");
866+ verify(gmailApp);
867+ waitUntilAppSurfaceShowsUp("gmail-webapp")
868+
869+ verify(gmailApp.session.surface);
870+
871+ tryCompare(gmailApp.session.surface, "activeFocus", true);
872+ }
873+
874+ function test_launchLandscapeOnlyAppOverPortraitOnlyDashThenSwitchToDash() {
875+ loadShell("mako");
876+
877+ // starts as portrait, as unity8-dash is portrait only
878+ tryCompare(shell, "transformRotationAngle", 0);
879+
880+ var weatherApp = ApplicationManager.startApplication("ubuntu-weather-app");
881+ verify(weatherApp);
882+
883+ // ensure the mock app is as we expect
884+ compare(weatherApp.supportedOrientations, Qt.LandscapeOrientation | Qt.InvertedLandscapeOrientation);
885+
886+ waitUntilAppSurfaceShowsUp("ubuntu-weather-app");
887+
888+ // should have rotated to landscape
889+ tryCompareFunction(function () { return shell.transformRotationAngle == 270
890+ || shell.transformRotationAngle == 90; }, true);
891+
892+ var rotationStates = findInvisibleChild(orientedShell, "rotationStates");
893+ waitUntilTransitionsEnd(rotationStates);
894+
895+ performLeftEdgeSwipeToSwitchToDash();
896+
897+ // Should be back to portrait
898+ tryCompare(shell, "transformRotationAngle", 0);
899+ }
900+
901+ // angle - rotation angle in degrees clockwise, relative to the primary orientation.
902+ function rotateTo(angle) {
903+ switch (angle) {
904+ case 0:
905+ rotate0Button.clicked();
906+ break;
907+ case 90:
908+ rotate90Button.clicked();
909+ break;
910+ case 180:
911+ rotate180Button.clicked();
912+ break;
913+ case 270:
914+ rotate270Button.clicked();
915+ break;
916+ default:
917+ verify(false);
918+ }
919+
920+ var rotationStates = findInvisibleChild(orientedShell, "rotationStates");
921+ waitUntilTransitionsEnd(rotationStates);
922+ }
923+
924+ function waitUntilAppDelegateIsFullyInit(spreadDelegate) {
925+ tryCompare(spreadDelegate, "orientationChangesEnabled", true);
926+
927+ var appWindowWithShadow = findChild(spreadDelegate, "appWindowWithShadow");
928+ verify(appWindowWithShadow);
929+ tryCompare(appWindowWithShadow, "state", "keepSceneRotation");
930+
931+ var appWindowStates = findInvisibleChild(appWindowWithShadow, "applicationWindowStateGroup");
932+ verify(appWindowStates);
933+ tryCompare(appWindowStates, "state", "surface");
934+ }
935+
936+ function waitUntilAppSurfaceShowsUp(appId) {
937+ var appWindow = findChild(shell, "appWindow_" + appId);
938+ verify(appWindow);
939+ var appWindowStates = findInvisibleChild(appWindow, "applicationWindowStateGroup");
940+ verify(appWindowStates);
941+ tryCompare(appWindowStates, "state", "surface");
942+ }
943+
944+ function waitUntilAppWindowCanRotate(appId) {
945+ var appWindow = findChild(shell, "appWindow_" + appId);
946+ verify(appWindow);
947+ tryCompare(appWindow, "orientationChangesEnabled", true);
948+ }
949+
950+ function waitUntilShellIsInOrientation(orientation) {
951+ tryCompare(shell, "orientation", orientation);
952+ var rotationStates = findInvisibleChild(orientedShell, "rotationStates");
953+ waitUntilTransitionsEnd(rotationStates);
954+ }
955+
956+ function performLeftEdgeSwipeToSwitchToDash() {
957+ var spreadView = findChild(shell, "spreadView");
958+ var swipeLength = spreadView.width * 0.7;
959+
960+ var touchStartX = 1;
961+ var touchStartY = shell.height / 2;
962+ touchFlick(shell,
963+ touchStartX, touchStartY,
964+ touchStartX + swipeLength, touchStartY);
965+
966+ tryCompare(ApplicationManager, "focusedApplicationId", "unity8-dash");
967+ }
968+
969+ function performEdgeSwipeToSwitchToPreviousApp() {
970+ // swipe just enough to ensure an app switch action.
971+ // If we swipe too much we will trigger the spread mode
972+ // and we don't want that.
973+ var spreadView = findChild(shell, "spreadView");
974+ verify(spreadView);
975+
976+ verify(ApplicationManager.count >= 2);
977+ var previousApp = ApplicationManager.get(1);
978+
979+ var touchStartX = shell.width - 1;
980+ var touchStartY = shell.height / 2;
981+
982+ var condition;
983+ if (applicationArguments.deviceName === "phone") {
984+ condition = function() {
985+ return spreadView.shiftedContentX > units.gu(2) &&
986+ spreadView.shiftedContentX < spreadView.positionMarker1 * spreadView.width;
987+ };
988+ } else {
989+ condition = function() {
990+ return spreadView.shiftedContentX > spreadView.width * spreadView.positionMarker1
991+ && spreadView.shiftedContentX < spreadView.width * spreadView.positionMarker2;
992+ };
993+ }
994+
995+ touchDragUntil(shell,
996+ touchStartX, touchStartY,
997+ -units.gu(1), 0,
998+ condition);
999+
1000+ // ensure the app switch animation has ended
1001+ tryCompare(spreadView, "shiftedContentX", 0);
1002+
1003+ compare(ApplicationManager.get(0).appId, previousApp.appId);
1004+ }
1005+
1006+ function performEdgeSwipeToShowAppSpread() {
1007+ var touchStartY = shell.height / 2;
1008+ touchFlick(shell,
1009+ shell.width - 1, touchStartY,
1010+ 0, touchStartY);
1011+
1012+ var spreadView = findChild(shell, "spreadView");
1013+ verify(spreadView);
1014+ tryCompare(spreadView, "phase", 2);
1015+ tryCompare(spreadView, "flicking", false);
1016+ tryCompare(spreadView, "moving", false);
1017+ }
1018+
1019+ function showPowerDialog() {
1020+ var dialogs = findChild(orientedShell, "dialogs");
1021+ var dialogsPrivate = findInvisibleChild(dialogs, "dialogsPrivate");
1022+ dialogsPrivate.showPowerDialog();
1023+ }
1024+
1025+ function verifyAppWindowWithinSpreadDelegateBoundaries(spreadDelegate) {
1026+ var appWindowWithShadow = findChild(spreadDelegate, "appWindowWithShadow");
1027+ verify(appWindowWithShadow);
1028+
1029+ var windowInDelegateCoords = appWindowWithShadow.mapToItem(spreadDelegate, 0, 0,
1030+ appWindowWithShadow.width, appWindowWithShadow.height);
1031+
1032+ compare(windowInDelegateCoords.x, 0);
1033+ compare(windowInDelegateCoords.y, 0);
1034+ compare(windowInDelegateCoords.width, spreadDelegate.width);
1035+ compare(windowInDelegateCoords.height, spreadDelegate.height);
1036+ }
1037+
1038+ function swipeAwayGreeter() {
1039+ var greeter = findChild(shell, "greeter");
1040+ tryCompare(greeter, "fullyShown", true);
1041+
1042+ var touchX = shell.width - (shell.edgeSize / 2);
1043+ var touchY = shell.height / 2;
1044+ touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY);
1045+
1046+ // wait until the animation has finished
1047+ tryCompare(greeter, "shown", false);
1048+ waitForRendering(greeter);
1049+ }
1050+
1051+ function showGreeter() {
1052+ LightDM.Greeter.showGreeter();
1053+ // wait until the animation has finished
1054+ var greeter = findChild(shell, "greeter");
1055+ tryCompare(greeter, "fullyShown", true);
1056+ }
1057+
1058+ function killApps() {
1059+ while (ApplicationManager.count > 1) {
1060+ var appIndex = ApplicationManager.get(0).appId == "unity8-dash" ? 1 : 0
1061+ ApplicationManager.stopApplication(ApplicationManager.get(appIndex).appId);
1062+ }
1063+ compare(ApplicationManager.count, 1)
1064+ }
1065+
1066+ function loadShell(deviceName) {
1067+ applicationArguments.deviceName = deviceName;
1068+
1069+ // reload our test subject to get it in a fresh state once again
1070+ orientedShellLoader.active = true;
1071+
1072+ tryCompare(orientedShellLoader, "status", Loader.Ready);
1073+ removeTimeConstraintsFromDirectionalDragAreas(orientedShellLoader.item);
1074+
1075+ shell = findChild(orientedShell, "shell");
1076+
1077+ tryCompare(shell, "enabled", true); // enabled by greeter when ready
1078+
1079+ waitUntilShellIsInOrientation(root.physicalOrientation0);
1080+
1081+ waitForGreeterToStabilize();
1082+
1083+ swipeAwayGreeter();
1084+
1085+ var spreadRepeater = findChild(shell, "spreadRepeater");
1086+ if (spreadRepeater) {
1087+ spreadRepeaterConnections.target = spreadRepeater;
1088+ }
1089+ }
1090+
1091+ function waitForGreeterToStabilize() {
1092+ var greeter = findChild(shell, "greeter");
1093+ verify(greeter);
1094+
1095+ var loginList = findChild(greeter, "loginList");
1096+ // Only present in WideView
1097+ if (loginList) {
1098+ var userList = findChild(loginList, "userList");
1099+ verify(userList);
1100+ tryCompare(userList, "movingInternally", false);
1101+ }
1102+ }
1103+
1104+ // expectedAngle is in orientedShell's coordinate system
1105+ function checkAppSurfaceOrientation(app, expectedAngle) {
1106+ var surface = app.session.surface;
1107+ if (!surface) {
1108+ console.warn("no surface");
1109+ return false;
1110+ }
1111+
1112+ if (!itemIsChildOfOrientedShell(surface)) {
1113+ console.warn("surface not a child of OrientedShell");
1114+ return false;
1115+ }
1116+
1117+ var topMargin = 0.;
1118+ if (!app.fullscreen) {
1119+ var appsDisplayLoader = findChild(shell, "applicationsDisplayLoader");
1120+ verify(appsDisplayLoader);
1121+ verify(appsDisplayLoader.item);
1122+ verify(appsDisplayLoader.item.maximizedAppTopMargin !== undefined);
1123+ topMargin = appsDisplayLoader.item.maximizedAppTopMargin;
1124+ }
1125+
1126+ var point = surface.mapToItem(orientedShell, 0, 0);
1127+
1128+ switch (expectedAngle) {
1129+ case 0:
1130+ return point.x === 0. && point.y === topMargin;
1131+ case 90:
1132+ return point.x === orientedShell.width - topMargin && point.y === 0;
1133+ case 180:
1134+ return point.x === orientedShell.width && point.y === orientedShell.height - topMargin;
1135+ default: // 270
1136+ return point.x === topMargin && point.y === orientedShell.height;
1137+ }
1138+ }
1139+
1140+ function itemIsChildOfOrientedShell(item) {
1141+ var parent = item.parent;
1142+ var found = false;
1143+ while (parent && !found) {
1144+ found = parent === orientedShell;
1145+ parent = parent.parent;
1146+ }
1147+ return found;
1148+ }
1149+ }
1150+
1151+}
tests/qmltests/tst_Shell.qml+29−84
@@ -1,9 +1,6 @@
11/*
22 * Copyright (C) 2013-2015 Canonical, Ltd.
33 *
4− * Authors:
5− * Daniel d'Andrada <daniel.dandrada@canonical.com>
6− *
74 * This program is free software; you can redistribute it and/or modify
85 * it under the terms of the GNU General Public License as published by
96 * the Free Software Foundation; version 3.
@@ -49,22 +46,6 @@ Rectangle {
4946 shellLoader.active = true;
5047 }
5148
52− QtObject {
53− id: applicationArguments
54−
55− function hasGeometry() {
56− return false;
57− }
58−
59− function width() {
60− return 0;
61− }
62−
63− function height() {
64− return 0;
65− }
66− }
67−
6849 Item {
6950 anchors.left: root.left
7051 anchors.right: controls.left
@@ -100,8 +81,12 @@ Rectangle {
10081 property bool itemDestroyed: false
10182 sourceComponent: Component {
10283 Shell {
103− property string indicatorProfile: "phone"
84+ property string shellMode: "full-greeter" /* default */
10485
86+ usageScenario: usageScenarioSelector.model[usageScenarioSelector.selectedIndex]
87+ orientation: Qt.PortraitOrientation
88+ primaryOrientation: Qt.PortraitOrientation
89+ nativeOrientation: Qt.PortraitOrientation
10590 Component.onDestruction: {
10691 shellLoader.itemDestroyed = true;
10792 }
@@ -166,14 +151,11 @@ Rectangle {
166151 }
167152 }
168153 ListItem.ItemSelector {
169− id: usageModeSelector
154+ id: usageScenarioSelector
170155 anchors { left: parent.left; right: parent.right }
171156 activeFocusOnPress: false
172− text: "Usage mode"
173− model: ["Staged", "Windowed"]
174− onSelectedIndexChanged: {
175− GSettingsController.setUsageMode(model[selectedIndex]);
176− }
157+ text: "Usage scenario"
158+ model: ["phone", "tablet", "desktop"]
177159 }
178160 MouseTouchEmulationCheckbox {
179161 id: mouseEmulation
@@ -664,15 +646,6 @@ Rectangle {
664646 tryCompare(item, "visible", false);
665647 }
666648
667− // wait until any transition animation has finished
668− function waitUntilTransitionsEnd(stateGroup) {
669− var transitions = stateGroup.transitions;
670− for (var i = 0; i < transitions.length; ++i) {
671− var transition = transitions[i];
672− tryCompare(transition, "running", false, 2000);
673− }
674− }
675−
676649 // Wait until the ApplicationWindow for the given Application object is fully loaded
677650 // (ie, the real surface has replaced the splash screen)
678651 function waitUntilAppWindowIsFullyLoaded(app) {
@@ -749,7 +722,7 @@ Rectangle {
749722
750723 tryCompare(webApp.session.surface, "activeFocus", true);
751724
752− GSettingsController.setUsageMode("Windowed");
725+ shell.usageScenario = "desktop";
753726
754727 // check that the desktop stage and window have been loaded
755728 {
@@ -759,7 +732,7 @@ Rectangle {
759732
760733 tryCompare(webApp.session.surface, "activeFocus", true);
761734
762− GSettingsController.setUsageMode("Staged");
735+ shell.usageScenario = "tablet";
763736
764737 // check that the tablet stage and app surface delegate have been loaded
765738 {
@@ -865,37 +838,6 @@ Rectangle {
865838 removeTimeConstraintsFromDirectionalDragAreas(greeter);
866839 }
867840
868− function test_greeterDoesNotChangeIndicatorProfile() {
869− loadShell("phone");
870− swipeAwayGreeter();
871− var panel = findChild(shell, "panel");
872− tryCompare(panel.indicators.indicatorsModel, "profile", shell.indicatorProfile);
873−
874− showGreeter();
875− tryCompare(panel.indicators.indicatorsModel, "profile", shell.indicatorProfile);
876−
877− LightDM.Greeter.hideGreeter();
878− tryCompare(panel.indicators.indicatorsModel, "profile", shell.indicatorProfile);
879− }
880−
881− function test_shellProfileChangesReachIndicators() {
882− loadShell("phone");
883− swipeAwayGreeter();
884− var panel = findChild(shell, "panel");
885−
886− shell.indicatorProfile = "test1";
887− for (var i = 0; i < panel.indicators.indicatorsModel.count; ++i) {
888− var properties = panel.indicators.indicatorsModel.data(i, IndicatorsModelRole.IndicatorProperties);
889− verify(properties["menuObjectPath"].substr(-5), "test1");
890− }
891−
892− shell.indicatorProfile = "test2";
893− for (var i = 0; i < panel.indicators.indicatorsModel.count; ++i) {
894− var properties = panel.indicators.indicatorsModel.data(i, IndicatorsModelRole.IndicatorProperties);
895− verify(properties["menuObjectPath"].substr(-5), "test2");
896− }
897− }
898−
899841 function test_focusRequestedHidesGreeter() {
900842 loadShell("phone");
901843 swipeAwayGreeter();
@@ -1070,9 +1012,14 @@ Rectangle {
10701012 loadShell("phone");
10711013 swipeAwayGreeter();
10721014 var topmostSpreadDelegate = findChild(shell, "appDelegate0");
1015+ verify(topmostSpreadDelegate);
1016+
1017+ waitUntilFocusedApplicationIsShowingItsSurface();
1018+
10731019 var topmostSurface = findChild(topmostSpreadDelegate, "surfaceContainer").surface;
1074− var rightEdgeDragArea = findChild(shell, "spreadDragArea");
1020+ verify(topmostSurface);
10751021
1022+ var rightEdgeDragArea = findChild(shell, "spreadDragArea");
10761023 topmostSurface.touchPressCount = 0;
10771024 topmostSurface.touchReleaseCount = 0;
10781025
@@ -1159,13 +1106,11 @@ Rectangle {
11591106 // left edge drag area.
11601107 {
11611108 var buttonShowDashHome = findChild(launcher, "buttonShowDashHome");
1162− var startPos = buttonShowDashHome.mapToItem(shell,
1163− buttonShowDashHome.width * 0.2,
1164− buttonShowDashHome.height * 0.8);
1165− var endPos = buttonShowDashHome.mapToItem(shell,
1166− buttonShowDashHome.width * 0.8,
1167− buttonShowDashHome.height * 0.2);
1168− touchFlick(shell, startPos.x, startPos.y, endPos.x, endPos.y);
1109+ touchFlick(buttonShowDashHome,
1110+ buttonShowDashHome.width * 0.2, /* startPos.x */
1111+ buttonShowDashHome.height * 0.8, /* startPos.y */
1112+ buttonShowDashHome.width * 0.8, /* endPos.x */
1113+ buttonShowDashHome.height * 0.2 /* endPos.y */);
11691114 }
11701115
11711116 compare(launcherShowDashHomeSpy.count, 1);
@@ -1257,30 +1202,30 @@ Rectangle {
12571202
12581203 function test_stageLoader_data() {
12591204 return [
1260− {tag: "phone", source: "Stages/PhoneStage.qml", formFactor: "phone", usageMode: "Staged"},
1261− {tag: "tablet", source: "Stages/TabletStage.qml", formFactor: "tablet", usageMode: "Staged"},
1262− {tag: "desktop", source: "Stages/DesktopStage.qml", formFactor: "tablet", usageMode: "Windowed"}
1205+ {tag: "phone", source: "Stages/PhoneStage.qml", formFactor: "phone", usageScenario: "phone"},
1206+ {tag: "tablet", source: "Stages/TabletStage.qml", formFactor: "tablet", usageScenario: "tablet"},
1207+ {tag: "desktop", source: "Stages/DesktopStage.qml", formFactor: "tablet", usageScenario: "desktop"}
12631208 ]
12641209 }
12651210
12661211 function test_stageLoader(data) {
1267− GSettingsController.setUsageMode(data.usageMode);
12681212 loadShell(data.formFactor);
1213+ shell.usageScenario = data.usageScenario;
12691214 var stageLoader = findChild(shell, "applicationsDisplayLoader");
12701215 verify(String(stageLoader.source).indexOf(data.source) >= 0);
12711216 }
12721217
12731218 function test_launcherInverted_data() {
12741219 return [
1275− {tag: "phone", formFactor: "phone", usageMode: "Staged", launcherInverted: true},
1276− {tag: "tablet", formFactor: "tablet", usageMode: "Staged", launcherInverted: true},
1277− {tag: "desktop", formFactor: "tablet", usageMode: "Windowed", launcherInverted: false}
1220+ {tag: "phone", formFactor: "phone", usageScenario: "phone", launcherInverted: true},
1221+ {tag: "tablet", formFactor: "tablet", usageScenario: "tablet", launcherInverted: true},
1222+ {tag: "desktop", formFactor: "tablet", usageScenario: "desktop", launcherInverted: false}
12781223 ]
12791224 }
12801225
12811226 function test_launcherInverted(data) {
1282− GSettingsController.setUsageMode(data.usageMode);
12831227 loadShell(data.formFactor);
1228+ shell.usageScenario = data.usageScenario;
12841229
12851230 var launcher = findChild(shell, "launcher");
12861231 compare(launcher.inverted, data.launcherInverted);
tests/qmltests/tst_ShellWithPin.qml+7−9
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -69,7 +69,7 @@ Item {
6969 property bool itemDestroyed: false
7070 sourceComponent: Component {
7171 Shell {
72− property string indicatorProfile: "phone"
72+ property string shellMode: "full-greeter" /* default */
7373
7474 Component.onDestruction: {
7575 shellLoader.itemDestroyed = true
@@ -397,24 +397,22 @@ Item {
397397 var applicationsDisplayLoader = findChild(shell, "applicationsDisplayLoader")
398398
399399 // We start in phone mode
400− tryCompare(shell, "sideStageEnabled", false)
401− tryCompare(applicationsDisplayLoader, "tabletMode", false)
400+ compare(shell.usageScenario, "phone");
401+ compare(applicationsDisplayLoader.usageScenario, "phone");
402402
403403 var lockscreen = findChild(shell, "lockscreen")
404404 lockscreen.emergencyCall()
405405 confirmLockedApp("dialer-app")
406406
407407 // OK, we're in. Now try (but fail) to switch to tablet mode
408− shell.tablet = true
409− tryCompare(shell, "sideStageEnabled", true)
410− tryCompare(applicationsDisplayLoader, "tabletMode", false)
408+ shell.usageScenario = "tablet";
409+ compare(applicationsDisplayLoader.usageScenario, "phone");
411410
412411 // And when we kill the app, we go back to locked tablet mode
413412 killApps()
414413 var greeter = findChild(shell, "greeter")
415414 tryCompare(greeter, "fullyShown", true)
416− tryCompare(shell, "sideStageEnabled", true)
417− tryCompare(applicationsDisplayLoader, "tabletMode", true)
415+ compare(applicationsDisplayLoader.usageScenario, "tablet");
418416 }
419417
420418 function test_emergencyDialerIncoming() {
tests/qmltests/utils/Unity/Test/tst_UnityTest.qml+5−8
@@ -20,6 +20,8 @@ import QtTest 1.0
2020import Unity.Test 0.1
2121
2222TestCase {
23+ name: "UnityTest"
24+
2325 Rectangle {
2426 id: rect
2527 }
@@ -28,28 +30,23 @@ TestCase {
2830 id: testObject
2931 }
3032
31− // Singletons need to be bound to a property and not-named-imported
32− // for them to be able to be properly passed back to C++.
33− // See https://bugreports.qt-project.org/browse/QTBUG-30730
34− property var util: Util
35−
3633 function test_direct() {
3734 compare(Util.isInstanceOf(rect, "QQuickRectangle"), true, "rect should be an instance of QQuickRectangle");
38− compare(Util.isInstanceOf(util, "TestUtil"), true, "Util should be an instance of TestUtil");
35+ compare(Util.isInstanceOf(Util, "TestUtil"), true, "Util should be an instance of TestUtil");
3936 compare(Util.isInstanceOf(testObject, "MockObjectForInstanceOfTestChild"), true, "testObject should be an instance of MockObjectForInstanceOfTestChild");
4037 }
4138
4239 function test_inherited() {
4340 compare(Util.isInstanceOf(rect, "QQuickItem"), true, "rect should be an instance of QQuickItem");
4441 compare(Util.isInstanceOf(rect, "QObject"), true, "rect should be an instance of QObject");
45− compare(Util.isInstanceOf(util, "QObject"), true, "Util should be an instance of QObject");
42+ compare(Util.isInstanceOf(Util, "QObject"), true, "Util should be an instance of QObject");
4643 compare(Util.isInstanceOf(testObject, "MockObjectForInstanceOfTest"), true, "testObject should be an instance of MockObjectForInstanceOfTest");
4744 compare(Util.isInstanceOf(testObject, "QQuickRectangle"), true, "testObject should be an instance of QQuickRectangle");
4845 }
4946
5047 function test_negative() {
5148 compare(Util.isInstanceOf(rect, "QQuickMouseArea"), false, "rect should not be an instance of MouseArea");
52− compare(Util.isInstanceOf(util, "QQuickItem"), false, "Util should not be an instance of QQuickItem");
49+ compare(Util.isInstanceOf(Util, "QQuickItem"), false, "Util should not be an instance of QQuickItem");
5350 }
5451
5552 function test_undefined() {
tests/utils/modules/Unity/Test/UnityTestCase.qml+55−2
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2013 Canonical Ltd.
2+ * Copyright 2013-2015 Canonical Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -26,6 +26,7 @@ TestCase {
2626 // This is needed for waitForRendering calls to return
2727 // if the watched element already got rendered
2828 Rectangle {
29+ id: rotatingRectangle
2930 width: units.gu(1)
3031 height: width
3132 parent: testCase.parent
@@ -35,7 +36,7 @@ TestCase {
3536 visible: testCase.running
3637
3738 RotationAnimation on rotation {
38− running: parent.visible
39+ running: rotatingRectangle.visible
3940 from: 0
4041 to: 360
4142 loops: Animation.Infinite
@@ -308,6 +309,48 @@ TestCase {
308309 }
309310 }
310311
312+ // perform a drag in the given direction until the given condition is true
313+ // The condition is a function to be evaluated after every step
314+ function touchDragUntil(item, startX, startY, stepX, stepY, condition) {
315+
316+ var root = fetchRootItem(item);
317+ var pos = item.mapToItem(root, startX, startY);
318+
319+ // convert step to scene coords
320+ {
321+ var stepStart = item.mapToItem(root, 0, 0);
322+ var stepEnd = item.mapToItem(root, stepX, stepY);
323+ }
324+ stepX = stepEnd.x - stepStart.x;
325+ stepY = stepEnd.y - stepStart.y;
326+
327+ var event = touchEvent(item)
328+ event.press(0 /* touchId */, pos.x, pos.y)
329+ event.commit()
330+
331+ // we have to stop at some point
332+ var maxSteps = 100;
333+ var stepsDone = 0;
334+
335+ while (!condition() && stepsDone < maxSteps) {
336+ wait(25);
337+ fakeDateTime.currentTimeMs += 25;
338+
339+ pos.x += stepX;
340+ pos.y += stepY;
341+
342+ event = touchEvent(item);
343+ event.move(0 /* touchId */, pos.x, pos.y);
344+ event.commit();
345+
346+ stepsDone += 1;
347+ }
348+
349+ event = touchEvent(item)
350+ event.release(0 /* touchId */, pos.x, pos.y)
351+ event.commit()
352+ }
353+
311354 function touchPinch(item, x1Start, y1Start, x1End, y1End, x2Start, y2Start, x2End, y2End) {
312355 // Make sure the item is rendered
313356 waitForRendering(item);
@@ -419,4 +462,14 @@ TestCase {
419462 return qtest_results.waitForRendering(item, timeout);
420463 }
421464
465+ /*
466+ Wait until any transition animation has finished for the given StateGroup or Item
467+ */
468+ function waitUntilTransitionsEnd(stateGroup) {
469+ var transitions = stateGroup.transitions;
470+ for (var i = 0; i < transitions.length; ++i) {
471+ var transition = transitions[i];
472+ tryCompare(transition, "running", false, 2000);
473+ }
474+ }
422475}
tests/whitespace/CMakeLists.txt+1−1
@@ -2,4 +2,4 @@
22# Test that all source files, cmakefiles, etc. do not contain trailing whitespace.
33#
44
5−add_test(whitespace ${CMAKE_CURRENT_SOURCE_DIR}/check_whitespace.py ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR})
5+add_test(Whitespace ${CMAKE_CURRENT_SOURCE_DIR}/check_whitespace.py ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR})