on xenial help sign in
Merge trunk.
commitf2d60db155aa9be2c971ff281aa0fbb10814b976 authorMichał Sawicz <michal.sawicz@canonical.com> dateWed 14 May 2014, 12:11 (12 years ago) parent2141d6df4d8085
110 files changed+2804−1506
CMakeLists.txt+5−5 CODING+11−11 build → build.sh+1−2 debian/changelog+72 debian/control+9−10 debian/rules+1−2 debian/unity8-private.install+1−1 debian/unity8.install−1 plugins/CMakeLists.txt+1−1 plugins/DashViews/CMakeLists.txt → plugins/Dash/CMakeLists.txt+4−4 plugins/Dash/CardCreator.js+518 plugins/Dash/CardCreatorCache.qml+40 plugins/DashViews/abstractdashview.cpp → plugins/Dash/abstractdashview.cpp−34 plugins/DashViews/abstractdashview.h → plugins/Dash/abstractdashview.h−17 plugins/DashViews/horizontaljournal.cpp → plugins/Dash/horizontaljournal.cpp−12 plugins/DashViews/horizontaljournal.h → plugins/Dash/horizontaljournal.h−4 plugins/DashViews/listviewwithpageheader.cpp → plugins/Dash/listviewwithpageheader.cpp−50 plugins/DashViews/listviewwithpageheader.h → plugins/Dash/listviewwithpageheader.h−20 plugins/DashViews/organicgrid.cpp → plugins/Dash/organicgrid.cpp−8 plugins/DashViews/organicgrid.h → plugins/Dash/organicgrid.h−4 plugins/DashViews/plugin.cpp → plugins/Dash/plugin.cpp+2−2 plugins/DashViews/plugin.h → plugins/Dash/plugin.h+3−3 plugins/Dash/qmldir+4 plugins/DashViews/verticaljournal.cpp → plugins/Dash/verticaljournal.cpp−8 plugins/DashViews/verticaljournal.h → plugins/Dash/verticaljournal.h−4 plugins/DashViews/qmldir−3 plugins/Unity/Indicators/menucontentactivator.cpp−1 plugins/Unity/Launcher/backend/launcherbackend.cpp+266−16 plugins/Unity/Launcher/backend/launcherbackend.h+52−4 plugins/Utils/CMakeLists.txt−1 plugins/Utils/bottombarvisibilitycommunicatorshell.cpp−49 plugins/Utils/bottombarvisibilitycommunicatorshell.h−59 plugins/Utils/plugin.cpp−10 po/fr.po+24−15 po/gl.po+4−4 po/lv.po+21−21 qml/Bottombar/Bottombar.qml−227 qml/Bottombar/HudButton.qml−64 qml/Bottombar/graphics/hud@20.png qml/Bottombar/graphics/hud_invoke_button_active@20.png qml/CMakeLists.txt−1 qml/Components/LazyImage.qml+1 qml/Components/ZoomableImage.qml+153 qml/Dash/Card.qml−236 qml/Dash/CardCarousel.qml+13−10 qml/Dash/CardFilterGrid.qml+19−16 qml/Dash/CardHeader.qml−126 qml/Dash/CardTool.qml+30−22 qml/Dash/DashContent.qml+18−10 qml/Dash/Previews/PreviewHeader.qml+86−9 qml/Dash/Previews/PreviewProgress.qml+3−2 qml/Dash/Previews/PreviewTextSummary.qml+4−2 qml/Dash/Previews/PreviewZoomableImage.qml+4−7 qml/Dash/ScopeListView.qml+1−1 qml/Notifications/Notifications.qml+11 qml/Shell.qml+21−48 run → run.sh+1−1 run_on_device → run_on_device.sh+2−2 src/ApplicationArguments.h+4−8 src/main.cpp+43−6 tests/autopilot/unity8/shell/emulators/dash.py+3−4 tests/autopilot/unity8/shell/tests/test_notifications.py+83 tests/mocks/Unity/fake_categories.cpp+15−2 tests/mocks/Unity/fake_resultsmodel.cpp+1 tests/plugins/CMakeLists.txt+1−1 tests/plugins/DashViews/CMakeLists.txt → tests/plugins/Dash/CMakeLists.txt+16−9 tests/plugins/Dash/cardcreator/1.res+77 tests/plugins/Dash/cardcreator/1.tst+3 tests/plugins/Dash/cardcreator/2.res+115 tests/plugins/Dash/cardcreator/2.tst+3 tests/plugins/Dash/cardcreator/3.res+94 tests/plugins/Dash/cardcreator/3.tst+3 tests/plugins/Dash/cardcreator/4.res+95 tests/plugins/Dash/cardcreator/4.tst+3 tests/plugins/Dash/cardcreator/5.res+137 tests/plugins/Dash/cardcreator/5.tst+3 tests/plugins/Dash/cardcreatortest.cpp+91 tests/plugins/Dash/cardcreatortest.qml+29 tests/plugins/DashViews/horizontaljournaltest.cpp → tests/plugins/Dash/horizontaljournaltest.cpp tests/plugins/DashViews/horizontaljournaltest.qml → tests/plugins/Dash/horizontaljournaltest.qml+1−1 tests/plugins/DashViews/horizontaljournaltry.cpp → tests/plugins/Dash/horizontaljournaltry.cpp tests/plugins/DashViews/horizontaljournaltry.qml → tests/plugins/Dash/horizontaljournaltry.qml+1−1 tests/plugins/DashViews/listviewwithpageheadersectionexternalmodeltest.cpp → tests/plugins/Dash/listviewwithpageheadersectionexternalmodeltest.cpp−4 tests/plugins/DashViews/listviewwithpageheadersectiontest.cpp → tests/plugins/Dash/listviewwithpageheadersectiontest.cpp−12 tests/plugins/DashViews/listviewwithpageheadertest.cpp → tests/plugins/Dash/listviewwithpageheadertest.cpp−15 tests/plugins/DashViews/listviewwithpageheadertest.qml → tests/plugins/Dash/listviewwithpageheadertest.qml+1−1 tests/plugins/DashViews/listviewwithpageheadertestsection.qml → tests/plugins/Dash/listviewwithpageheadertestsection.qml+1−1 tests/plugins/DashViews/listviewwithpageheadertestsectionexternalmodel.qml → tests/plugins/Dash/listviewwithpageheadertestsectionexternalmodel.qml+1−1 tests/plugins/DashViews/organicgridtest.cpp → tests/plugins/Dash/organicgridtest.cpp tests/plugins/DashViews/organicgridtest.qml → tests/plugins/Dash/organicgridtest.qml+1−1 tests/plugins/DashViews/organicgridtry.cpp → tests/plugins/Dash/organicgridtry.cpp tests/plugins/DashViews/organicgridtry.qml → tests/plugins/Dash/organicgridtry.qml+1−1 tests/plugins/DashViews/tst_ListViewWithPageHeaderQML.qml → tests/plugins/Dash/tst_ListViewWithPageHeaderQML.qml+1−1 tests/plugins/DashViews/verticaljournaltest.cpp → tests/plugins/Dash/verticaljournaltest.cpp tests/plugins/DashViews/verticaljournaltest.qml → tests/plugins/Dash/verticaljournaltest.qml+1−1 tests/plugins/DashViews/verticaljournaltry.cpp → tests/plugins/Dash/verticaljournaltry.cpp tests/plugins/DashViews/verticaljournaltry.qml → tests/plugins/Dash/verticaljournaltry.qml+1−1 tests/plugins/Unity/Launcher/CMakeLists.txt+3−3 tests/plugins/Unity/Launcher/launcherbackendtest.cpp+167 tests/qmltests/CMakeLists.txt+4−3 tests/qmltests/Components/tst_ZoomableImage.qml+192 tests/qmltests/Dash/Previews/tst_Preview.qml+1−1 tests/qmltests/Dash/Previews/tst_PreviewHeader.qml+66−4 tests/qmltests/Dash/Previews/tst_PreviewTextSummary.qml+5−5 tests/qmltests/Dash/Previews/tst_PreviewZoomableImage.qml+6−3 tests/qmltests/Dash/tst_Card.qml+78−92 tests/qmltests/Dash/tst_CardBenchmark.qml+10−7 tests/qmltests/Dash/tst_CardHeader.qml−116 tests/qmltests/Dash/tst_CardTool.qml+11−31 tests/utils/modules/Unity/Test/UnityTestCase.qml+26
CMakeLists.txt+5−5
@@ -48,11 +48,11 @@ add_custom_target(cppcheck COMMAND cppcheck --enable=all -q --error-exitcode=2
4848 ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/tests)
4949
5050include(FindPkgConfig)
51−find_package(Qt5Core)
52−find_package(Qt5Qml)
53−find_package(Qt5Quick)
54−find_package(Qt5Gui)
55−find_package(Qt5DBus)
51+find_package(Qt5Core 5.2 REQUIRED)
52+find_package(Qt5Qml 5.2 REQUIRED)
53+find_package(Qt5Quick 5.2 REQUIRED)
54+find_package(Qt5Gui 5.2 REQUIRED)
55+find_package(Qt5DBus 5.2 REQUIRED)
5656
5757# Standard install paths
5858include(GNUInstallDirs)
CODING+11−11
@@ -20,7 +20,7 @@ install dependencies
2020If its your first time, install build dependencies
2121
2222$ cd ~/unity8/trunk
23−$ ./build --setup
23+$ ./build.sh --setup
2424
2525 this will:
2626 - install the required dependencies to build, as well as run on the desktop
@@ -31,7 +31,7 @@ build Unity 8
3131-------------
3232
3333$ cd ~/unity8/trunk
34−$ ./build
34+$ ./build.sh
3535
3636You may also rebuild with the -c for clean, which just deletes the build
3737directory.
@@ -43,10 +43,10 @@ Running Unity 8 on your desktop
4343run the Unity 8 shell
4444---------------------
4545$ cd ~/unity8/trunk
46−$ ./run
46+$ ./run.sh
4747
4848Options can be passed to test the lock screen
49−$ ./run -p OR -k
49+$ ./run.sh -p OR -k
5050
5151-p is for pin lock, for which the default prompt answer is "1234"
5252-k is for keylock passphrase, for which the default prompt answer is "password"
@@ -78,19 +78,19 @@ Running Unity 8 on devices
7878 on the device yourself)
7979
8080$ cd ~/unity/trunk
81−$ ./run_on_device --setup
81+$ ./run_on_device.sh --setup
8282 this will:
8383 - install your public SSH key on the device
8484 - install the needed dependencies
8585
86−$ ./run_on_device
86+$ ./run_on_device.sh
8787 this will:
8888 - rsync the code to the device
8989 - build on the device
9090 - replace the running shell
9191
9292Options can be passed to test the lock screen
93−$ ./run_on_device -p OR -k
93+$ ./run_on_device.sh -p OR -k
9494
9595-p is for pin lock, for which the default prompt answer is "1234"
9696-k is for keylock passphrase, for which the default prompt answer is "password"
@@ -107,9 +107,9 @@ Notes
107107- exporting ANDROID_SERIAL=$your_device_serial or
108108 ANDROID_SERIAL=$your_device_ip:5555 will make adb not complain about
109109 multiple devices
110−- you can export TARGET_IP=$your_device_ip TARGET_SSH_PORT=22 for run_on_device
110+- you can export TARGET_IP=$your_device_ip TARGET_SSH_PORT=22 for run_on_device.sh
111111 to not use adb forwarding - useful if you use multiple devices
112−- using Ninja (package ninja-build - run_on_device -s installs this now)
112+- using Ninja (package ninja-build - run_on_device.sh -s installs this now)
113113 will improve build times (especially in the nothing-to-build case)
114114
115115
@@ -131,7 +131,7 @@ Running tests
131131=============
132132
133133Tests are run from the build directory (which will be ./builddir if you used
134−the ./build script).
134+the ./build.sh script).
135135
136136To run the tests that do not require any graphical capability and therefore
137137are pretty fast run:
@@ -171,7 +171,7 @@ Running autopilot tests
171171=======================
172172
173173Autopilot tests are run from the build directory (which will be ./builddir if you used
174−the ./build script).
174+the ./build.sh script).
175175
176176$ make autopilot
177177
build → build.shrenamed+1−2
@@ -43,13 +43,12 @@ install_dependencies() {
4343 gsettings-desktop-schemas \
4444 gsettings-ubuntu-schemas \
4545 qmenumodel-qml \
46− qtdeclarative5-dee-plugin \
46+ qml-module-qtquick-xmllistmodel \
4747 qtdeclarative5-gsettings1.0 \
4848 qtdeclarative5-ubuntu-ui-toolkit-plugin \
4949 qtdeclarative5-ubuntu-settings-components \
5050 qtdeclarative5-ubuntu-thumbnailer0.1 \
5151 unity-notifications-impl \
52− qtdeclarative5-xmllistmodel-plugin \
5352 ubuntu-mobile-icons \
5453 unity-scope-home \
5554 unity-lens-applications \
debian/changelog+72
@@ -1,3 +1,75 @@
1+unity8 (7.86+14.10.20140513-0ubuntu1) utopic; urgency=low
2+
3+ [ Andrea Cimitan ]
4+ * Improve padding in Text preview widget. (LP: #1316683)
5+
6+ [ CI bot ]
7+ * Resync trunk
8+
9+ [ Nick Dedekind ]
10+ * Removed binding loop from Unity.Indicators.MenuContentActivator
11+ Change handler for QMLListProperty used by MenuContent.qml:
12+ menuActivator.content[index].active If we're already asking for the
13+ index, we know it exists already. No need to send a
14+ changeNotification on an implied creation.
15+
16+ [ Josh Arenson ]
17+ * Implements usage-style documentation for unity8 executable. Fixes
18+ lp:1269282 (LP: #1269282)
19+
20+ [ Albert Astals ]
21+ * Create specialized Card code in Javascript instead of having various
22+ copied&pasted files (LP: #1297197)
23+
24+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Tue, 13 May 2014 08:34:02 +0000
25+
26+unity8 (7.86+14.10.20140507.3-0ubuntu1) utopic; urgency=low
27+
28+ [ Michał Sawicz ]
29+ * Remove HUD from the bottom edge. Again.
30+
31+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Wed, 07 May 2014 11:14:30 +0000
32+
33+unity8 (7.86+14.10.20140505-0ubuntu1) utopic; urgency=low
34+
35+ [ Ted Gould ]
36+ * Provide a dbus interface for setting the count and countVisible
37+ properties. (LP: #1301400)
38+
39+ [ Michał Sawicz ]
40+ * Pass env variables to initctl start.
41+ * Suffix .sh to our scripts and clean up debian/rules.
42+ * Adapt to Debian Qt package renames and drop unneeded Dee plugin
43+ dependency.
44+
45+ [ Ying-Chun Liu ]
46+ * Add Zoomable Image for Preview widgets.
47+
48+ [ Albert Astals ]
49+ * Remove support for Qt <= 5.2.1
50+
51+ [ Mirco Müller ]
52+ * Implemented feature-request from Design for modal snap-decision
53+ notifications on the phone. See LP #1285712 (LP: #1285712)
54+
55+ [ Andrea Cimitan ]
56+ * Make progressbas in preview widget big as the button
57+
58+ [ CI bot ]
59+ * Resync trunk
60+
61+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Mon, 05 May 2014 12:09:43 +0000
62+
63+unity8 (7.86+14.10.20140502.6-0ubuntu1) utopic; urgency=low
64+
65+ [ tpeeters ]
66+ * Adapt to new TabBar
67+
68+ [ Tim Peeters ]
69+ * Adapt to new TabBar
70+
71+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Fri, 02 May 2014 16:44:52 +0000
72+
173unity8 (7.86+14.10.20140429.2-0ubuntu1) utopic; urgency=medium
274
375 [ Andrea Cimitan ]
debian/control+9−10
@@ -26,19 +26,19 @@ Build-Depends: cmake,
2626 python3-all:any,
2727 python3-setuptools,
2828 python:any (>= 2.7),
29+ qml-module-qtquick-xmllistmodel,
30+ qml-module-qtquick2,
31+ qml-module-qttest,
2932 qt5-default,
30− qtbase5-dev,
33+ qtbase5-dev (>= 5.2.1),
3134 qtbase5-dev-tools,
32− qtbase5-private-dev,
33− qtdeclarative5-dev,
35+ qtbase5-private-dev (>= 5.2.1),
36+ qtdeclarative5-dev (>= 5.2.1),
3437 qtdeclarative5-dev-tools,
35− qtdeclarative5-private-dev,
38+ qtdeclarative5-private-dev (>= 5.2.1),
3639 qtdeclarative5-qtmultimedia-plugin,
37− qtdeclarative5-qtquick2-plugin,
38− qtdeclarative5-test-plugin,
3940 qtdeclarative5-ubuntu-settings-components,
4041 qtdeclarative5-ubuntu-ui-toolkit-plugin,
41− qtdeclarative5-xmllistmodel-plugin,
4242 ttf-ubuntu-font-family,
4343Standards-Version: 3.9.4
4444Homepage: http://launchpad.net/unity
@@ -76,10 +76,9 @@ Depends: gsettings-desktop-schemas,
7676 libglib2.0-bin,
7777 libunity-mir1 (>= 0.4),
7878 qmenumodel-qml (>= 0.2.7),
79− qtdeclarative5-dee-plugin,
79+ qml-module-qtquick-xmllistmodel,
8080 qtdeclarative5-gsettings1.0,
8181 qtdeclarative5-ubuntu-settings-components,
82− qtdeclarative5-xmllistmodel-plugin,
8382 unity-launcher-impl-3,
8483 unity8-common (= ${source:Version}),
8584 unity8-private (= ${binary:Version}),
@@ -102,7 +101,7 @@ Depends: qtdeclarative5-ubuntu-thumbnailer0.1 | ubuntu-thumbnailer-impl,
102101 qtdeclarative5-unity-notifications-plugin | unity-notifications-impl,
103102 ubuntu-thumbnailer-impl-0,
104103 unity-application-impl-2,
105− unity-notifications-impl-2,
104+ unity-notifications-impl-3,
106105 unity-plugin-scopes | unity-scopes-impl,
107106 unity-scopes-impl-0,
108107 unity8-fake-env | unity-application-impl,
debian/rules+1−2
@@ -6,8 +6,7 @@
66export DPKG_GENSYMBOLS_CHECK_LEVEL=4
77export NO_PNG_PKG_MANGLE=1
88
9−.PHONY: build
10−build %:
9+%:
1110 dh $@ --parallel --fail-missing --with python2,python3
1211
1312override_dh_auto_clean:
debian/unity8-private.install+1−1
@@ -1,5 +1,5 @@
11usr/lib/*/unity8/qml/AccountsService
2−usr/lib/*/unity8/qml/DashViews
2+usr/lib/*/unity8/qml/Dash
33usr/lib/*/unity8/qml/HudClient
44usr/lib/*/unity8/qml/LightDM
55usr/lib/*/unity8/qml/Powerd
debian/unity8.install−1
@@ -1,7 +1,6 @@
11data/unity8.conf usr/share/upstart/sessions/
22usr/bin/unity8
33usr/share/applications/unity8.desktop
4−usr/share/unity8/Bottombar
54usr/share/unity8/Greeter
65usr/share/unity8/Hud
76usr/share/unity8/Launcher
plugins/CMakeLists.txt+1−1
@@ -14,7 +14,7 @@ endmacro()
1414add_subdirectory(AccountsService)
1515add_subdirectory(HudClient)
1616add_subdirectory(LightDM)
17−add_subdirectory(DashViews)
17+add_subdirectory(Dash)
1818add_subdirectory(Powerd)
1919add_subdirectory(SessionBroadcast)
2020add_subdirectory(Ubuntu)
plugins/DashViews/CMakeLists.txt → plugins/Dash/CMakeLists.txtrenamed+4−4
@@ -27,15 +27,15 @@ set(QMLPLUGIN_SRC
2727 organicgrid.cpp
2828 )
2929
30−add_library(DashViews-qml MODULE
30+add_library(Dash-qml MODULE
3131 ${QMLPLUGIN_SRC}
3232 )
3333
34−target_link_libraries(DashViews-qml
34+target_link_libraries(Dash-qml
3535 ${Qt5Gui_LIBRARIES}
3636 ${Qt5Quick_LIBRARIES}
3737 )
3838
39−qt5_use_modules(DashViews-qml Qml Quick)
39+qt5_use_modules(Dash-qml Qml Quick)
4040
41−add_unity8_plugin(DashViews 0.1 DashViews TARGETS DashViews-qml)
41+add_unity8_plugin(Dash 0.1 Dash TARGETS Dash-qml)
plugins/Dash/CardCreator.jsnew file+518
@@ -0,0 +1,518 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+.pragma library
18+
19+var kBackgroundLoaderCode = 'Loader {\n\
20+ id: backgroundLoader; \n\
21+ objectName: "backgroundLoader"; \n\
22+ anchors.fill: parent; \n\
23+ asynchronous: root.asynchronous; \n\
24+ visible: status == Loader.Ready; \n\
25+ sourceComponent: UbuntuShape { \n\
26+ objectName: "background"; \n\
27+ radius: "medium"; \n\
28+ color: getColor(0) || "white"; \n\
29+ gradientColor: getColor(1) || color; \n\
30+ anchors.fill: parent; \n\
31+ image: backgroundImage.source ? backgroundImage : null; \n\
32+ property real luminance: 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b; \n\
33+ property Image backgroundImage: Image { \n\
34+ objectName: "backgroundImage"; \n\
35+ source: { \n\
36+ 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\
39+ } \n\
40+ } \n\
41+ function getColor(index) { \n\
42+ if (cardData && typeof cardData["background"] === "object" \n\
43+ && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) { \n\
44+ 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\
49+ } \n\
50+ } \n\
51+ }\n';
52+
53+// %1 is used as anchors of artShapeHolder
54+// %2 is used as image width
55+// %3 is used as image height
56+var kArtShapeHolderCode = 'Item { \n\
57+ id: artShapeHolder; \n\
58+ height: root.fixedArtShapeSize.height != -1 ? root.fixedArtShapeSize.height : artShapeLoader.height; \n\
59+ width: root.fixedArtShapeSize.width != -1 ? root.fixedArtShapeSize.width : artShapeLoader.width; \n\
60+ anchors { %1 } \n\
61+ Loader { \n\
62+ id: artShapeLoader; \n\
63+ objectName: "artShapeLoader"; \n\
64+ active: cardData && cardData["art"] || false; \n\
65+ asynchronous: root.asynchronous; \n\
66+ visible: status == Loader.Ready; \n\
67+ sourceComponent: UbuntuShape { \n\
68+ id: artShape; \n\
69+ objectName: "artShape"; \n\
70+ radius: "medium"; \n\
71+ readonly property real aspect: components !== undefined ? components["art"]["aspect-ratio"] : 1; \n\
72+ readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect; \n\
73+ Component.onCompleted: updateWidthHeightBindings(); \n\
74+ onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings(); \n\
75+ visible: image.status == Image.Ready; \n\
76+ function updateWidthHeightBindings() { \n\
77+ if (aspectSmallerThanImageAspect) { \n\
78+ width = Qt.binding(function() { return !visible ? 0 : image.width }); \n\
79+ height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect }); \n\
80+ } else { \n\
81+ width = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.width : height * image.aspect }); \n\
82+ height = Qt.binding(function() { return !visible ? 0 : image.height }); \n\
83+ } \n\
84+ } \n\
85+ image: Image { \n\
86+ objectName: "artImage"; \n\
87+ source: cardData && cardData["art"] || ""; \n\
88+ cache: true; \n\
89+ asynchronous: root.asynchronous; \n\
90+ fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop; \n\
91+ readonly property real aspect: implicitWidth / implicitHeight; \n\
92+ width: %2; \n\
93+ height: %3; \n\
94+ } \n\
95+ } \n\
96+ } \n\
97+ }\n';
98+
99+var kOverlayLoaderCode = 'Loader { \n\
100+ id: overlayLoader; \n\
101+ anchors { \n\
102+ left: artShapeHolder.left; \n\
103+ right: artShapeHolder.right; \n\
104+ bottom: artShapeHolder.bottom; \n\
105+ } \n\
106+ active: artShapeLoader.active && artShapeLoader.item && artShapeLoader.item.image.status === Image.Ready || false; \n\
107+ asynchronous: root.asynchronous; \n\
108+ visible: showHeader && status == Loader.Ready; \n\
109+ sourceComponent: ShaderEffect { \n\
110+ id: overlay; \n\
111+ height: fixedHeaderHeight != -1 ? fixedHeaderHeight : headerHeight; \n\
112+ opacity: 0.6; \n\
113+ property var source: ShaderEffectSource { \n\
114+ id: shaderSource; \n\
115+ sourceItem: artShapeLoader.item; \n\
116+ onVisibleChanged: if (visible) scheduleUpdate(); \n\
117+ live: false; \n\
118+ sourceRect: Qt.rect(0, artShapeLoader.height - overlay.height, artShapeLoader.width, overlay.height); \n\
119+ } \n\
120+ vertexShader: " \n\
121+ uniform highp mat4 qt_Matrix; \n\
122+ attribute highp vec4 qt_Vertex; \n\
123+ attribute highp vec2 qt_MultiTexCoord0; \n\
124+ varying highp vec2 coord; \n\
125+ void main() { \n\
126+ coord = qt_MultiTexCoord0; \n\
127+ gl_Position = qt_Matrix * qt_Vertex; \n\
128+ }"; \n\
129+ fragmentShader: " \n\
130+ varying highp vec2 coord; \n\
131+ uniform sampler2D source; \n\
132+ uniform lowp float qt_Opacity; \n\
133+ void main() { \n\
134+ lowp vec4 tex = texture2D(source, coord); \n\
135+ gl_FragColor = vec4(0, 0, 0, tex.a) * qt_Opacity; \n\
136+ }"; \n\
137+ } \n\
138+ }\n';
139+
140+// %1 is used as anchors of row
141+// %2 is used as first child of the row
142+// %3 is used as second child of the row
143+var kHeaderRow2Code = 'Row { \n\
144+ id: row; \n\
145+ objectName: "outerRow"; \n\
146+ property real margins: units.gu(1); \n\
147+ spacing: margins; \n\
148+ anchors { %1 } \n\
149+ anchors.right: parent.right; \n\
150+ anchors.margins: margins;\n\
151+ data: [ %2\n\
152+ ,\n\
153+ %3 \n\
154+ ] \n\
155+ }\n';
156+
157+// %1 is used as anchors of row
158+// %2 is used as first child of the row
159+// %3 is used as second child of the row
160+// %4 is used as third child of the row
161+var kHeaderRow3Code = 'Row { \n\
162+ id: row; \n\
163+ objectName: "outerRow"; \n\
164+ property real margins: units.gu(1); \n\
165+ spacing: margins; \n\
166+ anchors { %1 } \n\
167+ anchors.right: parent.right; \n\
168+ anchors.margins: margins;\n\
169+ data: [ %2\n\
170+ ,\n\
171+ %3 \n\
172+ ,\n\
173+ %4 \n\
174+ ] \n\
175+ }\n';
176+
177+// %1 is used as first child of the column
178+// %2 is used as second child of the column
179+var kHeaderColumnCode = 'Column { \n\
180+ anchors.verticalCenter: parent.verticalCenter; \n\
181+ spacing: units.dp(2); \n\
182+ width: parent.width - x;\n\
183+ data: [ %1\n\
184+ ,\n\
185+ %2 \n\
186+ ] \n\
187+ }\n';
188+
189+// %1 is used as anchors of mascotShapeLoader
190+var kMascotShapeLoaderCode = 'Loader { \n\
191+ id: mascotShapeLoader; \n\
192+ objectName: "mascotShapeLoader"; \n\
193+ asynchronous: root.asynchronous; \n\
194+ active: mascotImage.status === Image.Ready; \n\
195+ visible: showHeader && active && status == Loader.Ready; \n\
196+ width: units.gu(6); \n\
197+ height: units.gu(5.625); \n\
198+ sourceComponent: UbuntuShape { image: mascotImage } \n\
199+ anchors { %1 } \n\
200+ }\n';
201+
202+// %1 is used as anchors of mascotImage
203+// %2 is used as visible of mascotImage
204+var kMascotImageCode = 'Image { \n\
205+ id: mascotImage; \n\
206+ objectName: "mascotImage"; \n\
207+ anchors { %1 } \n\
208+ readonly property int maxSize: Math.max(width, height) * 4; \n\
209+ source: cardData && cardData["mascot"]; \n\
210+ width: units.gu(6); \n\
211+ height: units.gu(5.625); \n\
212+ sourceSize { width: maxSize; height: maxSize } \n\
213+ fillMode: Image.PreserveAspectCrop; \n\
214+ horizontalAlignment: Image.AlignHCenter; \n\
215+ verticalAlignment: Image.AlignVCenter; \n\
216+ visible: %2; \n\
217+ }\n';
218+
219+// %1 is used as anchors of titleLabel
220+// %1 is used as color of titleLabel
221+// %3 is used as extra condition for visible of titleLabel
222+var kTitleLabelCode = 'Label { \n\
223+ id: titleLabel; \n\
224+ objectName: "titleLabel"; \n\
225+ anchors { %1 } \n\
226+ elide: Text.ElideRight; \n\
227+ fontSize: "small"; \n\
228+ wrapMode: Text.Wrap; \n\
229+ maximumLineCount: 2; \n\
230+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale); \n\
231+ color: %2; \n\
232+ visible: showHeader %3; \n\
233+ text: root.title; \n\
234+ font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal; \n\
235+ horizontalAlignment: root.headerAlignment; \n\
236+ }\n';
237+
238+// %1 is used as anchors of subtitleLabel
239+// %2 is used as color of subtitleLabel
240+var kSubtitleLabelCode = 'Label { \n\
241+ id: subtitleLabel; \n\
242+ objectName: "subtitleLabel"; \n\
243+ anchors { %1 } \n\
244+ elide: Text.ElideRight; \n\
245+ fontSize: "small"; \n\
246+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale); \n\
247+ color: %2; \n\
248+ visible: titleLabel.visible && titleLabel.text; \n\
249+ text: cardData && cardData["subtitle"] || ""; \n\
250+ font.weight: Font.Light; \n\
251+ horizontalAlignment: root.headerAlignment; \n\
252+ }\n';
253+
254+// %1 is used as top anchor of summary
255+// %2 is used as topMargin anchor of summary
256+// %3 is used as color of summary
257+var kSummaryLabelCode = 'Label { \n\
258+ id: summary; \n\
259+ objectName: "summaryLabel"; \n\
260+ anchors { \n\
261+ top: %1; \n\
262+ left: parent.left; \n\
263+ right: parent.right; \n\
264+ margins: units.gu(1); \n\
265+ topMargin: %2; \n\
266+ } \n\
267+ wrapMode: Text.Wrap; \n\
268+ maximumLineCount: 5; \n\
269+ elide: Text.ElideRight; \n\
270+ text: cardData && cardData["summary"] || ""; \n\
271+ height: text ? implicitHeight : 0; \n\
272+ fontSize: "small"; \n\
273+ color: %3; \n\
274+ }\n';
275+
276+function cardString(template, components) {
277+ var code;
278+ code = 'AbstractButton { \n\
279+ id: root; \n\
280+ property var template; \n\
281+ property var components; \n\
282+ property var cardData; \n\
283+ property real fontScale: 1.0; \n\
284+ property int headerAlignment: Text.AlignLeft; \n\
285+ property int fixedHeaderHeight: -1; \n\
286+ property size fixedArtShapeSize: Qt.size(-1, -1); \n\
287+ readonly property string title: cardData && cardData["title"] || ""; \n\
288+ property bool asynchronous: true; \n\
289+ property bool showHeader: true; \n\
290+ implicitWidth: childrenRect.width; \n';
291+
292+ var hasArt = components["art"] && components["art"]["field"] || false;
293+ var hasSummary = components["summary"] || false;
294+ var artAndSummary = hasArt && hasSummary;
295+ var isHorizontal = template["card-layout"] === "horizontal";
296+ var hasBackground = !isHorizontal && (template["card-background"] || components["background"] || artAndSummary);
297+ var hasTitle = components["title"] || false;
298+ var hasMascot = components["mascot"] || false;
299+ var headerAsOverlay = hasArt && template && template["overlay"] === true && (hasTitle || hasMascot);
300+ var hasSubtitle = components["subtitle"] || false;
301+ var hasHeaderRow = hasMascot && hasTitle;
302+
303+ if (hasBackground) {
304+ code += kBackgroundLoaderCode;
305+ }
306+
307+ if (hasArt) {
308+ code += 'readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);\n';
309+
310+ var widthCode, heightCode;
311+ var anchors;
312+ if (isHorizontal) {
313+ anchors = 'left: parent.left';
314+ if (hasMascot || hasTitle) {
315+ widthCode = 'height * artShape.aspect'
316+ heightCode = 'headerHeight';
317+ } else {
318+ // This side of the else is a bit silly, who wants an horizontal layout without mascot and title?
319+ // So we define a "random" height of the image height + 2 gu for the margins
320+ widthCode = 'height * artShape.aspect'
321+ heightCode = 'units.gu(7.625)';
322+ }
323+ } else {
324+ anchors = 'horizontalCenter: parent.horizontalCenter;';
325+ widthCode = 'root.width'
326+ heightCode = 'width / artShape.aspect';
327+ }
328+
329+ code += kArtShapeHolderCode.arg(anchors).arg(widthCode).arg(heightCode);
330+ } else {
331+ code += 'readonly property size artShapeSize: Qt.size(-1, -1);\n'
332+ }
333+
334+ if (headerAsOverlay) {
335+ code += kOverlayLoaderCode;
336+ }
337+
338+ var headerVerticalAnchors;
339+ if (headerAsOverlay) {
340+ headerVerticalAnchors = 'bottom: artShapeHolder.bottom; \n\
341+ bottomMargin: units.gu(1);\n';
342+ } else {
343+ if (hasArt) {
344+ if (isHorizontal) {
345+ headerVerticalAnchors = 'top: artShapeHolder.top; \n\
346+ topMargin: units.gu(1);\n';
347+ } else {
348+ headerVerticalAnchors = 'top: artShapeHolder.bottom; \n\
349+ topMargin: units.gu(1);\n';
350+ }
351+ } else {
352+ headerVerticalAnchors = 'top: parent.top; \n\
353+ topMargin: units.gu(1);\n';
354+ }
355+ }
356+ var headerLeftAnchor;
357+ var headerLeftAnchorHasMagin = false;
358+ if (isHorizontal && hasArt) {
359+ headerLeftAnchor = 'left: artShapeHolder.right; \n\
360+ leftMargin: units.gu(1);\n';
361+ headerLeftAnchorHasMagin = true;
362+ } else {
363+ headerLeftAnchor = 'left: parent.left;\n';
364+ }
365+
366+ if (hasHeaderRow) {
367+ code += 'readonly property int headerHeight: row.height + row.margins * 2;\n'
368+ } else if (hasMascot) {
369+ code += 'readonly property int headerHeight: mascotImage.height + units.gu(1) * 2;\n'
370+ } else if (hasSubtitle) {
371+ code += 'readonly property int headerHeight: titleLabel.height + titleLabel.anchors.topMargin * 2 + subtitleLabel.height + subtitleLabel.anchors.topMargin;\n'
372+ } else if (hasTitle) {
373+ code += 'readonly property int headerHeight: titleLabel.height + titleLabel.anchors.topMargin * 2;\n'
374+ } else {
375+ code += 'readonly property int headerHeight: 0;\n'
376+ }
377+
378+ var mascotShapeCode = "";
379+ var mascotCode = "";
380+ if (hasMascot) {
381+ var useMascotShape = !hasBackground && !headerAsOverlay;
382+ var anchors = "";
383+ if (!hasHeaderRow) {
384+ anchors += headerLeftAnchor;
385+ anchors += headerVerticalAnchors;
386+ if (!headerLeftAnchorHasMagin) {
387+ anchors += 'leftMargin: units.gu(1);\n'
388+ }
389+ } else {
390+ anchors = "verticalCenter: parent.verticalCenter;"
391+ }
392+
393+ if (useMascotShape) {
394+ mascotShapeCode = kMascotShapeLoaderCode.arg(anchors);
395+ }
396+
397+ var mascotImageVisible = useMascotShape ? 'false' : 'showHeader';
398+ mascotCode = kMascotImageCode.arg(anchors).arg(mascotImageVisible);
399+ }
400+
401+ var summaryColorWithBackground = 'backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : "grey"';
402+
403+ var titleSubtitleCode = "";
404+ if (hasTitle) {
405+ var color;
406+ if (headerAsOverlay) {
407+ color = '"white"';
408+ } else if (hasSummary) {
409+ color = 'summary.color';
410+ } else if (hasBackground) {
411+ color = summaryColorWithBackground;
412+ } else {
413+ color = '"grey"';
414+ }
415+
416+ var titleAnchors;
417+ var subtitleAnchors;
418+ if (hasMascot && hasSubtitle) {
419+ // Using row + column
420+ titleAnchors = 'left: parent.left; right: parent.right';
421+ subtitleAnchors = titleAnchors;
422+ } else if (hasMascot) {
423+ // Using row + label
424+ titleAnchors = 'verticalCenter: parent.verticalCenter;\n'
425+ } else {
426+ if (headerAsOverlay) {
427+ // Using anchors to the overlay
428+ titleAnchors = 'left: parent.left; \n\
429+ leftMargin: units.gu(1); \n\
430+ right: parent.right; \n\
431+ top: overlayLoader.top; \n\
432+ topMargin: units.gu(1);\n';
433+ } else {
434+ // Using anchors to the mascot/parent
435+ titleAnchors = "right: parent.right;";
436+ titleAnchors += headerLeftAnchor;
437+ titleAnchors += headerVerticalAnchors;
438+ }
439+ subtitleAnchors = 'left: titleLabel.left; \n\
440+ leftMargin: titleLabel.leftMargin; \n\
441+ right: titleLabel.right; \n\
442+ top: titleLabel.bottom; \n\
443+ topMargin: units.dp(2);\n';
444+ }
445+
446+ var titleLabelVisibleExtra = (headerAsOverlay ? '&& overlayLoader.active': '');
447+ var titleCode = kTitleLabelCode.arg(titleAnchors).arg(color).arg(titleLabelVisibleExtra);
448+ var subtitleCode = "";
449+ if (hasSubtitle) {
450+ subtitleCode += kSubtitleLabelCode.arg(subtitleAnchors).arg(color);
451+ }
452+
453+ if (hasMascot && hasSubtitle) {
454+ // If using row + column wrap the code in the column
455+ titleSubtitleCode = kHeaderColumnCode.arg(titleCode).arg(subtitleCode);
456+ } else {
457+ titleSubtitleCode = titleCode + subtitleCode;
458+ }
459+ }
460+
461+ if (hasHeaderRow) {
462+ if (mascotShapeCode != "") {
463+ code += kHeaderRow3Code.arg(headerVerticalAnchors + headerLeftAnchor).arg(mascotShapeCode).arg(mascotCode).arg(titleSubtitleCode);
464+ } else {
465+ code += kHeaderRow2Code.arg(headerVerticalAnchors + headerLeftAnchor).arg(mascotCode).arg(titleSubtitleCode);
466+ }
467+ } else {
468+ code += mascotShapeCode + mascotCode + titleSubtitleCode;
469+ }
470+
471+ if (hasSummary) {
472+ var summaryTopAnchor;
473+ if (isHorizontal && hasArt) summaryTopAnchor = "artShapeHolder.bottom";
474+ else if (headerAsOverlay && hasArt) summaryTopAnchor = "artShapeHolder.bottom";
475+ else if (hasHeaderRow) summaryTopAnchor = "row.bottom";
476+ else if (hasMascot) summaryTopAnchor = "mascotImage.bottom";
477+ else if (hasSubtitle) summaryTopAnchor = "subtitleLabel.bottom";
478+ else if (hasTitle) summaryTopAnchor = "titleLabel.bottom";
479+ else if (hasArt) summaryTopAnchor = "artShapeHolder.bottom";
480+ else summaryTopAnchor = "parent.top";
481+
482+ var color;
483+ if (hasBackground) {
484+ color = summaryColorWithBackground;
485+ } else {
486+ color = '"grey"';
487+ }
488+
489+ var summaryTopMargin = (hasMascot || hasSubtitle ? 'anchors.margins' : '0');
490+
491+ code += kSummaryLabelCode.arg(summaryTopAnchor).arg(summaryTopMargin).arg(color);
492+ }
493+
494+ if (hasSummary) {
495+ code += 'implicitHeight: summary.y + summary.height + (summary.text ? units.gu(1) : 0);\n';
496+ } else if (hasHeaderRow) {
497+ code += 'implicitHeight: row.y + row.height + units.gu(1);\n';
498+ } else if (hasMascot) {
499+ code += 'implicitHeight: mascotImage.y + mascotImage.height;\n';
500+ } else if (hasSubtitle) {
501+ code += 'implicitHeight: subtitleLabel.y + subtitleLabel.height + units.gu(1);\n';
502+ } else if (hasTitle) {
503+ code += 'implicitHeight: titleLabel.y + titleLabel.height + units.gu(1);\n';
504+ }
505+ // Close the AbstractButton
506+ code += '}\n';
507+
508+ return code;
509+}
510+
511+function createCardComponent(parent, template, components) {
512+ var imports = 'import QtQuick 2.2; \n\
513+ import Ubuntu.Components 0.1; \n\
514+ import Ubuntu.Thumbnailer 0.1;\n';
515+ var card = cardString(template, components);
516+ var code = imports + 'Component {\n' + card + '}\n';
517+ return Qt.createQmlObject(code, parent, "createCardComponent");
518+}
plugins/Dash/CardCreatorCache.qmlnew file+40
@@ -0,0 +1,40 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+pragma Singleton
18+import QtQuick 2.2
19+import "CardCreator.js" as CardCreator
20+
21+QtObject {
22+ id: root
23+
24+ property var cache: new Object();
25+
26+ function getCardComponent(template, components) {
27+ if (template === undefined || components === undefined)
28+ return undefined;
29+
30+ var tString = JSON.stringify(template);
31+ var cString = JSON.stringify(components);
32+ var allString = tString + cString;
33+ var component = cache[allString];
34+ if (component === undefined) {
35+ component = CardCreator.createCardComponent(root, template, components);
36+ cache[allString] = component;
37+ }
38+ return component;
39+ }
40+}
plugins/DashViews/abstractdashview.cpp → plugins/Dash/abstractdashview.cpprenamed−34
@@ -46,17 +46,10 @@ void AbstractDashView::setModel(QAbstractItemModel *model)
4646 if (!m_delegateModel) {
4747 createDelegateModel();
4848 } else {
49−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
50− disconnect(m_delegateModel, SIGNAL(modelUpdated(QQuickChangeSet,bool)), this, SLOT(onModelUpdated(QQuickChangeSet,bool)));
51− }
52− m_delegateModel->setModel(QVariant::fromValue<QAbstractItemModel *>(model));
53− connect(m_delegateModel, SIGNAL(modelUpdated(QQuickChangeSet,bool)), this, SLOT(onModelUpdated(QQuickChangeSet,bool)));
54−#else
5549 disconnect(m_delegateModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)), this, SLOT(onModelUpdated(QQmlChangeSet,bool)));
5650 }
5751 m_delegateModel->setModel(QVariant::fromValue<QAbstractItemModel *>(model));
5852 connect(m_delegateModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)), this, SLOT(onModelUpdated(QQmlChangeSet,bool)));
59−#endif
6053
6154 cleanupExistingItems();
6255
@@ -181,13 +174,8 @@ void AbstractDashView::resetDelegateCreationEnd()
181174
182175void AbstractDashView::createDelegateModel()
183176{
184−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
185− m_delegateModel = new QQuickVisualDataModel(qmlContext(this), this);
186− connect(m_delegateModel, SIGNAL(createdItem(int,QQuickItem*)), this, SLOT(itemCreated(int,QQuickItem*)));
187−#else
188177 m_delegateModel = new QQmlDelegateModel(qmlContext(this), this);
189178 connect(m_delegateModel, SIGNAL(createdItem(int,QObject*)), this, SLOT(itemCreated(int,QObject*)));
190−#endif
191179 if (isComponentComplete())
192180 m_delegateModel->componentComplete();
193181}
@@ -253,16 +241,9 @@ QQuickItem *AbstractDashView::createItem(int modelIndex, bool asynchronous)
253241 return nullptr;
254242
255243 m_asyncRequestedIndex = -1;
256−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
257− QQuickItem *item = m_delegateModel->item(modelIndex, asynchronous);
258−#else
259244 QObject* object = m_delegateModel->object(modelIndex, asynchronous);
260245 QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
261−#endif
262246 if (!item) {
263−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
264− m_asyncRequestedIndex = modelIndex;
265−#else
266247 if (object) {
267248 m_delegateModel->release(object);
268249 if (!m_delegateValidated) {
@@ -273,7 +254,6 @@ QQuickItem *AbstractDashView::createItem(int modelIndex, bool asynchronous)
273254 } else {
274255 m_asyncRequestedIndex = modelIndex;
275256 }
276−#endif
277257 return nullptr;
278258 } else {
279259 addItemToView(modelIndex, item);
@@ -283,13 +263,8 @@ QQuickItem *AbstractDashView::createItem(int modelIndex, bool asynchronous)
283263
284264void AbstractDashView::releaseItem(QQuickItem *item)
285265{
286−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
287− QQuickVisualModel::ReleaseFlags flags = m_delegateModel->release(item);
288− if (flags & QQuickVisualModel::Destroyed) {
289−#else
290266 QQmlDelegateModel::ReleaseFlags flags = m_delegateModel->release(item);
291267 if (flags & QQmlDelegateModel::Destroyed) {
292−#endif
293268 item->setParentItem(nullptr);
294269 }
295270}
@@ -299,10 +274,6 @@ void AbstractDashView::setImplicitHeightDirty()
299274 m_implicitHeightDirty = true;
300275}
301276
302−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
303−void AbstractDashView::itemCreated(int modelIndex, QQuickItem *item)
304−{
305−#else
306277void AbstractDashView::itemCreated(int modelIndex, QObject *object)
307278{
308279 QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
@@ -310,7 +281,6 @@ void AbstractDashView::itemCreated(int modelIndex, QObject *object)
310281 qWarning() << "AbstractDashView::itemCreated got a non item for index" << modelIndex;
311282 return;
312283 }
313−#endif
314284 item->setParentItem(this);
315285
316286 // We only need to call createItem if we are here because of an asynchronous generation
@@ -325,11 +295,7 @@ void AbstractDashView::itemCreated(int modelIndex, QObject *object)
325295 }
326296}
327297
328−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
329−void AbstractDashView::onModelUpdated(const QQuickChangeSet &changeSet, bool reset)
330−#else
331298void AbstractDashView::onModelUpdated(const QQmlChangeSet &changeSet, bool reset)
332−#endif
333299{
334300 if (reset) {
335301 cleanupExistingItems();
plugins/DashViews/abstractdashview.h → plugins/Dash/abstractdashview.hrenamed−17
@@ -23,12 +23,8 @@ class QAbstractItemModel;
2323class QQmlComponent;
2424#pragma GCC diagnostic push
2525#pragma GCC diagnostic ignored "-pedantic"
26−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
27−#include <private/qquickvisualdatamodel_p.h>
28−#else
2926#include <private/qqmldelegatemodel_p.h>
3027#include <qqmlinfo.h>
31−#endif
3228#pragma GCC diagnostic pop
3329
3430class AbstractDashView : public QQuickItem
@@ -94,13 +90,8 @@ protected:
9490 void setImplicitHeightDirty();
9591
9692private Q_SLOTS:
97−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
98− void itemCreated(int modelIndex, QQuickItem *item);
99− void onModelUpdated(const QQuickChangeSet &changeSet, bool reset);
100−#else
10193 void itemCreated(int modelIndex, QObject *object);
10294 void onModelUpdated(const QQmlChangeSet &changeSet, bool reset);
103−#endif
10495 void onHeightChanged();
10596
10697private:
@@ -117,17 +108,9 @@ private:
117108 virtual void doRelayout() = 0;
118109 virtual void updateItemCulling(qreal visibleFromY, qreal visibleToY) = 0;
119110 virtual void calculateImplicitHeight() = 0;
120−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
121− virtual void processModelRemoves(const QVector<QQuickChangeSet::Remove> &removes) = 0;
122−#else
123111 virtual void processModelRemoves(const QVector<QQmlChangeSet::Remove> &removes) = 0;
124−#endif
125112
126−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
127− QQuickVisualDataModel *m_delegateModel;
128−#else
129113 QQmlDelegateModel *m_delegateModel;
130−#endif
131114
132115 // Index we are waiting because we requested it asynchronously
133116 int m_asyncRequestedIndex;
plugins/DashViews/horizontaljournal.cpp → plugins/Dash/horizontaljournal.cpprenamed−12
@@ -28,12 +28,8 @@
2828#include <qqmlengine.h>
2929#pragma GCC diagnostic push
3030#pragma GCC diagnostic ignored "-pedantic"
31−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
32−#include <private/qquickvisualdatamodel_p.h>
33−#else
3431#include <private/qqmldelegatemodel_p.h>
3532#include <qqmlinfo.h>
36−#endif
3733#include <private/qquickitem_p.h>
3834#pragma GCC diagnostic pop
3935
@@ -188,17 +184,9 @@ void HorizontalJournal::calculateImplicitHeight()
188184 }
189185}
190186
191−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
192−void HorizontalJournal::processModelRemoves(const QVector<QQuickChangeSet::Remove> &removes)
193−#else
194187void HorizontalJournal::processModelRemoves(const QVector<QQmlChangeSet::Remove> &removes)
195−#endif
196188{
197−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
198− Q_FOREACH(const QQuickChangeSet::Remove &remove, removes) {
199−#else
200189 Q_FOREACH(const QQmlChangeSet::Remove &remove, removes) {
201−#endif
202190 for (int i = remove.count - 1; i >= 0; --i) {
203191 const int indexToRemove = remove.index + i;
204192 // We only support removing from the end so
plugins/DashViews/horizontaljournal.h → plugins/Dash/horizontaljournal.hrenamed−4
@@ -52,11 +52,7 @@ private:
5252 void calculateImplicitHeight() override;
5353 void doRelayout() override;
5454 void updateItemCulling(qreal visibleFromY, qreal visibleToY) override;
55−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
56− void processModelRemoves(const QVector<QQuickChangeSet::Remove> &removes) override;
57−#else
5855 void processModelRemoves(const QVector<QQmlChangeSet::Remove> &removes) override;
59−#endif
6056
6157 int m_firstVisibleIndex;
6258 QList<QQuickItem*> m_visibleItems;
plugins/DashViews/listviewwithpageheader.cpp → plugins/Dash/listviewwithpageheader.cpprenamed−50
@@ -97,11 +97,7 @@
9797#include <qqmlengine.h>
9898#pragma GCC diagnostic push
9999#pragma GCC diagnostic ignored "-pedantic"
100−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
101−#include <private/qquickvisualdatamodel_p.h>
102−#else
103100#include <private/qqmldelegatemodel_p.h>
104−#endif
105101#include <private/qqmlglobal_p.h>
106102#include <private/qquickitem_p.h>
107103#include <private/qquickanimation_p.h>
@@ -191,17 +187,10 @@ void ListViewWithPageHeader::setModel(QAbstractItemModel *model)
191187 if (!m_delegateModel) {
192188 createDelegateModel();
193189 } else {
194−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
195− disconnect(m_delegateModel, SIGNAL(modelUpdated(QQuickChangeSet,bool)), this, SLOT(onModelUpdated(QQuickChangeSet,bool)));
196− }
197− m_delegateModel->setModel(QVariant::fromValue<QAbstractItemModel *>(model));
198− connect(m_delegateModel, SIGNAL(modelUpdated(QQuickChangeSet,bool)), this, SLOT(onModelUpdated(QQuickChangeSet,bool)));
199−#else
200190 disconnect(m_delegateModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)), this, SLOT(onModelUpdated(QQmlChangeSet,bool)));
201191 }
202192 m_delegateModel->setModel(QVariant::fromValue<QAbstractItemModel *>(model));
203193 connect(m_delegateModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)), this, SLOT(onModelUpdated(QQmlChangeSet,bool)));
204−#endif
205194 Q_EMIT modelChanged();
206195 polish();
207196 // TODO?
@@ -570,13 +559,8 @@ void ListViewWithPageHeader::viewportMoved(Qt::Orientations orient)
570559
571560void ListViewWithPageHeader::createDelegateModel()
572561{
573−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
574− m_delegateModel = new QQuickVisualDataModel(qmlContext(this), this);
575− connect(m_delegateModel, SIGNAL(createdItem(int,QQuickItem*)), this, SLOT(itemCreated(int,QQuickItem*)));
576−#else
577562 m_delegateModel = new QQmlDelegateModel(qmlContext(this), this);
578563 connect(m_delegateModel, SIGNAL(createdItem(int,QObject*)), this, SLOT(itemCreated(int,QObject*)));
579−#endif
580564 if (isComponentComplete())
581565 m_delegateModel->componentComplete();
582566 updateWatchedRoles();
@@ -657,13 +641,8 @@ bool ListViewWithPageHeader::addVisibleItems(qreal fillFrom, qreal fillTo, bool
657641void ListViewWithPageHeader::reallyReleaseItem(ListItem *listItem)
658642{
659643 QQuickItem *item = listItem->m_item;
660−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
661− QQuickVisualModel::ReleaseFlags flags = m_delegateModel->release(item);
662− if (flags & QQuickVisualModel::Destroyed) {
663−#else
664644 QQmlDelegateModel::ReleaseFlags flags = m_delegateModel->release(item);
665645 if (flags & QQmlDelegateModel::Destroyed) {
666−#endif
667646 item->setParentItem(nullptr);
668647 }
669648 delete listItem->m_sectionItem;
@@ -796,16 +775,9 @@ ListViewWithPageHeader::ListItem *ListViewWithPageHeader::createItem(int modelIn
796775 return nullptr;
797776
798777 m_asyncRequestedIndex = -1;
799−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
800− QQuickItem *item = m_delegateModel->item(modelIndex, asynchronous);
801−#else
802778 QObject* object = m_delegateModel->object(modelIndex, asynchronous);
803779 QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
804−#endif
805780 if (!item) {
806−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
807− m_asyncRequestedIndex = modelIndex;
808−#else
809781 if (object) {
810782 m_delegateModel->release(object);
811783 if (!m_delegateValidated) {
@@ -816,7 +788,6 @@ ListViewWithPageHeader::ListItem *ListViewWithPageHeader::createItem(int modelIn
816788 } else {
817789 m_asyncRequestedIndex = modelIndex;
818790 }
819−#endif
820791 return 0;
821792 } else {
822793// qDebug() << "ListViewWithPageHeader::createItem::We have the item" << modelIndex << item;
@@ -872,10 +843,6 @@ ListViewWithPageHeader::ListItem *ListViewWithPageHeader::createItem(int modelIn
872843 }
873844}
874845
875−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
876−void ListViewWithPageHeader::itemCreated(int modelIndex, QQuickItem *item)
877−{
878−#else
879846void ListViewWithPageHeader::itemCreated(int modelIndex, QObject *object)
880847{
881848 QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
@@ -883,7 +850,6 @@ void ListViewWithPageHeader::itemCreated(int modelIndex, QObject *object)
883850 qWarning() << "ListViewWithPageHeader::itemCreated got a non item for index" << modelIndex;
884851 return;
885852 }
886−#endif
887853// qDebug() << "ListViewWithPageHeader::itemCreated" << modelIndex << item;
888854 // Check we are not being taken down and don't paint anything
889855 // TODO Check if we still need this in 5.2
@@ -924,21 +890,13 @@ void ListViewWithPageHeader::onHeightChanged()
924890}
925891
926892
927−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
928−void ListViewWithPageHeader::onModelUpdated(const QQuickChangeSet &changeSet, bool /*reset*/)
929−#else
930893void ListViewWithPageHeader::onModelUpdated(const QQmlChangeSet &changeSet, bool /*reset*/)
931−#endif
932894{
933895 // TODO Do something with reset
934896// qDebug() << "ListViewWithPageHeader::onModelUpdated" << changeSet << reset;
935897 const auto oldFirstVisibleIndex = m_firstVisibleIndex;
936898
937−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
938− Q_FOREACH(const QQuickChangeSet::Remove &remove, changeSet.removes()) {
939−#else
940899 Q_FOREACH(const QQmlChangeSet::Remove &remove, changeSet.removes()) {
941−#endif
942900// qDebug() << "ListViewWithPageHeader::onModelUpdated Remove" << remove.index << remove.count;
943901 if (remove.index + remove.count > m_firstVisibleIndex && remove.index < m_firstVisibleIndex + m_visibleItems.count()) {
944902 const qreal oldFirstValidIndexPos = m_visibleItems.first()->y();
@@ -992,11 +950,7 @@ void ListViewWithPageHeader::onModelUpdated(const QQmlChangeSet &changeSet, bool
992950 }
993951 }
994952
995−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
996− Q_FOREACH(const QQuickChangeSet::Insert &insert, changeSet.inserts()) {
997−#else
998953 Q_FOREACH(const QQmlChangeSet::Insert &insert, changeSet.inserts()) {
999−#endif
1000954// qDebug() << "ListViewWithPageHeader::onModelUpdated Insert" << insert.index << insert.count;
1001955 const bool insertingInValidIndexes = insert.index > m_firstVisibleIndex && insert.index < m_firstVisibleIndex + m_visibleItems.count();
1002956 const bool firstItemWithViewOnTop = insert.index == 0 && m_firstVisibleIndex == 0 && m_visibleItems.first()->y() + m_clipItem->y() > contentY();
@@ -1054,11 +1008,7 @@ void ListViewWithPageHeader::onModelUpdated(const QQmlChangeSet &changeSet, bool
10541008 }
10551009 }
10561010
1057−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
1058− Q_FOREACH(const QQuickChangeSet::Change &change, changeSet.changes()) {
1059−#else
10601011 Q_FOREACH(const QQmlChangeSet::Change &change, changeSet.changes()) {
1061−#endif
10621012// qDebug() << "ListViewWithPageHeader::onModelUpdated Change" << change.index << change.count;
10631013 for (int i = change.index; i < change.count; ++i) {
10641014 ListItem *item = itemAtIndex(i);
plugins/DashViews/listviewwithpageheader.h → plugins/Dash/listviewwithpageheader.hrenamed−20
@@ -17,21 +17,13 @@
1717#ifndef LISTVIEWWITHPAGEHEADER_H
1818#define LISTVIEWWITHPAGEHEADER_H
1919
20−#pragma GCC diagnostic push
21−#pragma GCC diagnostic ignored "-pedantic"
2220#include <private/qquickitemchangelistener_p.h>
2321#include <private/qquickflickable_p.h>
24−#pragma GCC diagnostic pop
2522
2623class QAbstractItemModel;
2724class QQuickNumberAnimation;
28−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
29−class QQuickChangeSet;
30−class QQuickVisualDataModel;
31−#else
3225class QQmlChangeSet;
3326class QQmlDelegateModel;
34−#endif
3527
3628
3729/**
@@ -117,19 +109,11 @@ protected:
117109 void updatePolish();
118110
119111private Q_SLOTS:
120−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
121− void itemCreated(int modelIndex, QQuickItem *item);
122−#else
123112 void itemCreated(int modelIndex, QObject *object);
124−#endif
125113 void onContentHeightChanged();
126114 void onContentWidthChanged();
127115 void onHeightChanged();
128−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
129− void onModelUpdated(const QQuickChangeSet &changeSet, bool reset);
130−#else
131116 void onModelUpdated(const QQmlChangeSet &changeSet, bool reset);
132−#endif
133117 void onShowHeaderAnimationFinished();
134118
135119private:
@@ -168,11 +152,7 @@ private:
168152 QQuickItem *getSectionItem(int modelIndex, bool alreadyInserted);
169153 QQuickItem *getSectionItem(const QString &sectionText);
170154
171−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
172− QQuickVisualDataModel *m_delegateModel;
173−#else
174155 QQmlDelegateModel *m_delegateModel;
175−#endif
176156
177157 // Index we are waiting because we requested it asynchronously
178158 int m_asyncRequestedIndex;
plugins/DashViews/organicgrid.cpp → plugins/Dash/organicgrid.cpprenamed−8
@@ -237,17 +237,9 @@ void OrganicGrid::calculateImplicitHeight()
237237 }
238238}
239239
240−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
241−void OrganicGrid::processModelRemoves(const QVector<QQuickChangeSet::Remove> &removes)
242−#else
243240void OrganicGrid::processModelRemoves(const QVector<QQmlChangeSet::Remove> &removes)
244−#endif
245241{
246−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
247− Q_FOREACH(const QQuickChangeSet::Remove &remove, removes) {
248−#else
249242 Q_FOREACH(const QQmlChangeSet::Remove &remove, removes) {
250−#endif
251243 for (int i = remove.count - 1; i >= 0; --i) {
252244 const int indexToRemove = remove.index + i;
253245 // We only support removing from the end
plugins/DashViews/organicgrid.h → plugins/Dash/organicgrid.hrenamed−4
@@ -79,11 +79,7 @@ private:
7979 void doRelayout() override;
8080 void updateItemCulling(qreal visibleFromY, qreal visibleToY) override;
8181 void calculateImplicitHeight() override;
82−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
83− void processModelRemoves(const QVector<QQuickChangeSet::Remove> &removes) override;
84−#else
8582 void processModelRemoves(const QVector<QQmlChangeSet::Remove> &removes) override;
86−#endif
8783
8884 QSizeF m_smallDelegateSize;
8985 QSizeF m_bigDelegateSize;
plugins/DashViews/plugin.cpp → plugins/Dash/plugin.cpprenamed+2−2
@@ -24,9 +24,9 @@
2424
2525#include <QAbstractItemModel>
2626
27−void DashViewsPlugin::registerTypes(const char *uri)
27+void DashPlugin::registerTypes(const char *uri)
2828{
29− Q_ASSERT(uri == QLatin1String("DashViews"));
29+ Q_ASSERT(uri == QLatin1String("Dash"));
3030 qmlRegisterType<QAbstractItemModel>();
3131 qmlRegisterType<HorizontalJournal>(uri, 0, 1, "HorizontalJournal");
3232 qmlRegisterType<ListViewWithPageHeader>(uri, 0, 1, "ListViewWithPageHeader");
plugins/DashViews/plugin.h → plugins/Dash/plugin.hrenamed+3−3
@@ -15,13 +15,13 @@
1515 *
1616 */
1717
18−#ifndef DASHVIEWS_PLUGIN_H
19−#define DASHVIEWS_PLUGIN_H
18+#ifndef DASH_PLUGIN_H
19+#define DASH_PLUGIN_H
2020
2121#include <QtQml/QQmlEngine>
2222#include <QtQml/QQmlExtensionPlugin>
2323
24−class DashViewsPlugin : public QQmlExtensionPlugin
24+class DashPlugin : public QQmlExtensionPlugin
2525{
2626 Q_OBJECT
2727 Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
plugins/Dash/qmldirnew file+4
@@ -0,0 +1,4 @@
1+module Dash
2+plugin Dash-qml
3+typeinfo Dash.qmltypes
4+singleton CardCreatorCache 0.1 CardCreatorCache.qml
plugins/DashViews/verticaljournal.cpp → plugins/Dash/verticaljournal.cpprenamed−8
@@ -239,17 +239,9 @@ void VerticalJournal::updateItemCulling(qreal visibleFromY, qreal visibleToY)
239239 }
240240}
241241
242−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
243−void VerticalJournal::processModelRemoves(const QVector<QQuickChangeSet::Remove> &removes)
244−#else
245242void VerticalJournal::processModelRemoves(const QVector<QQmlChangeSet::Remove> &removes)
246−#endif
247243{
248−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
249− Q_FOREACH(const QQuickChangeSet::Remove &remove, removes) {
250−#else
251244 Q_FOREACH(const QQmlChangeSet::Remove &remove, removes) {
252−#endif
253245 for (int i = remove.count - 1; i >= 0; --i) {
254246 const int indexToRemove = remove.index + i;
255247 // Since we only support removing from the end, indexToRemove
plugins/DashViews/verticaljournal.h → plugins/Dash/verticaljournal.hrenamed−4
@@ -90,11 +90,7 @@ private:
9090 void calculateImplicitHeight() override;
9191 void doRelayout() override;
9292 void updateItemCulling(qreal visibleFromY, qreal visibleToY) override;
93−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
94− void processModelRemoves(const QVector<QQuickChangeSet::Remove> &removes) override;
95−#else
9693 void processModelRemoves(const QVector<QQmlChangeSet::Remove> &removes) override;
97−#endif
9894
9995 QVector<QList<ViewItem>> m_columnVisibleItems;
10096 QHash<int, int> m_indexColumnMap;
plugins/DashViews/qmldirdeleted−3
@@ -1,3 +0,0 @@
1−module DashViews
2−plugin DashViews-qml
3−typeinfo DashViews.qmltypes
plugins/Unity/Indicators/menucontentactivator.cpp−1
@@ -316,7 +316,6 @@ MenuContentState* MenuContentActivatorPrivate::content_at(QQmlListProperty<MenuC
316316 if (!d->m_content.contains(index)) {
317317 MenuContentState* content = new MenuContentState(false);
318318 d->m_content[index] = content;
319− Q_EMIT p->contentChanged();
320319 return content;
321320 }
322321
plugins/Unity/Launcher/backend/launcherbackend.cpp+266−16
@@ -34,10 +34,12 @@ public:
3434 QString desktopFile;
3535 QString displayName;
3636 QString icon;
37+ int count;
38+ bool countVisible;
3739};
3840
3941LauncherBackend::LauncherBackend(QObject *parent):
40− QObject(parent),
42+ QDBusVirtualObject(parent),
4143 m_accounts(nullptr)
4244{
4345#ifndef LAUNCHER_TESTING
@@ -45,10 +47,25 @@ LauncherBackend::LauncherBackend(QObject *parent):
4547#endif
4648 m_user = qgetenv("USER");
4749 syncFromAccounts();
50+
51+ /* Set up ourselves on DBus */
52+ QDBusConnection con = QDBusConnection::sessionBus();
53+ if (!con.registerService("com.canonical.Unity.Launcher")) {
54+ qDebug() << "Unable to register launcher name";
55+ }
56+ if (!con.registerVirtualObject("/com/canonical/Unity/Launcher", this, QDBusConnection::VirtualObjectRegisterOption::SubPath)) {
57+ qDebug() << "Unable to register launcher object";
58+ }
4859}
4960
5061LauncherBackend::~LauncherBackend()
5162{
63+ /* Remove oursevles from DBus */
64+ QDBusConnection con = QDBusConnection::sessionBus();
65+ con.unregisterService("com.canonical.Unity.Launcher");
66+ con.unregisterObject("/com/canonical/Unity/Launcher");
67+
68+ /* Clear data */
5269 m_storedApps.clear();
5370
5471 Q_FOREACH(LauncherBackendItem *item, m_itemCache) {
@@ -90,7 +107,7 @@ void LauncherBackend::setStoredApplications(const QStringList &appIds)
90107
91108QString LauncherBackend::desktopFile(const QString &appId) const
92109{
93− LauncherBackendItem *item = m_itemCache.value(appId);
110+ LauncherBackendItem *item = m_itemCache.value(appId, nullptr);
94111 if (item) {
95112 return item->desktopFile;
96113 }
@@ -100,7 +117,7 @@ QString LauncherBackend::desktopFile(const QString &appId) const
100117
101118QString LauncherBackend::displayName(const QString &appId) const
102119{
103− LauncherBackendItem *item = m_itemCache.value(appId);
120+ LauncherBackendItem *item = m_itemCache.value(appId, nullptr);
104121 if (item) {
105122 return item->displayName;
106123 }
@@ -118,16 +135,9 @@ QString LauncherBackend::displayName(const QString &appId) const
118135QString LauncherBackend::icon(const QString &appId) const
119136{
120137 QString iconName;
121− LauncherBackendItem *item = m_itemCache.value(appId);
138+ LauncherBackendItem *item = getItem(appId);
122139 if (item) {
123140 iconName = item->icon;
124− } else {
125− QString df = findDesktopFile(appId);
126− if (!df.isEmpty()) {
127− LauncherBackendItem *item = parseDesktopFile(df);
128− m_itemCache.insert(appId, item);
129− iconName = item->icon;
130− }
131141 }
132142
133143 return iconName;
@@ -153,10 +163,70 @@ int LauncherBackend::progress(const QString &appId) const
153163
154164int LauncherBackend::count(const QString &appId) const
155165{
156− // TODO: Return value for count emblem.
157− // TODO: emit countChanged() when this value changes.
158− Q_UNUSED(appId)
159− return 0;
166+ int count = -1;
167+ LauncherBackendItem *item = getItem(appId);
168+
169+ if (item) {
170+ if (item->countVisible) {
171+ count = item->count;
172+ }
173+ }
174+
175+ return count;
176+}
177+
178+void LauncherBackend::setCount(const QString &appId, int count) const
179+{
180+ LauncherBackendItem *item = getItem(appId);
181+
182+ bool emitchange = false;
183+ if (item) {
184+ emitchange = (item->count != count);
185+ item->count = count;
186+ }
187+
188+ if (emitchange) {
189+ /* TODO: This needs to use the accessor to handle the visibility
190+ correctly, but when we have the two properties we can just use
191+ the local value */
192+ Q_EMIT countChanged(appId, this->count(appId));
193+ QVariant vcount(item->count);
194+ emitPropChangedDbus(appId, "count", vcount);
195+ }
196+}
197+
198+bool LauncherBackend::countVisible(const QString &appId) const
199+{
200+ bool visible = false;
201+ LauncherBackendItem *item = getItem(appId);
202+
203+ if (item) {
204+ visible = item->countVisible;
205+ }
206+
207+ return visible;
208+}
209+
210+void LauncherBackend::setCountVisible(const QString &appId, bool visible) const
211+{
212+ LauncherBackendItem *item = getItem(appId);
213+
214+ bool emitchange = false;
215+ if (item) {
216+ emitchange = (item->countVisible != visible);
217+ item->countVisible = visible;
218+ } else {
219+ qDebug() << "Unable to find:" << appId;
220+ }
221+
222+ if (emitchange) {
223+ /* TODO: Because we're using visible in determining the
224+ count we need to emit a count changed as well */
225+ Q_EMIT countChanged(appId, this->count(appId));
226+ Q_EMIT countVisibleChanged(appId, item->countVisible);
227+ QVariant vCountVisible(item->countVisible);
228+ emitPropChangedDbus(appId, "countVisible", vCountVisible);
229+ }
160230}
161231
162232void LauncherBackend::setUser(const QString &username)
@@ -275,6 +345,35 @@ LauncherBackendItem* LauncherBackend::parseDesktopFile(const QString &desktopFil
275345 } else {
276346 item->icon = "image://theme/" + iconString;
277347 }
348+
349+ /* TODO: These should be looked up in a cache somewhere */
350+ item->count = 0;
351+ item->countVisible = false;
352+
353+ return item;
354+}
355+
356+/* Gets an item, and tries to create a new one if we need it to */
357+LauncherBackendItem* LauncherBackend::getItem(const QString &appId) const
358+{
359+ LauncherBackendItem *item = m_itemCache.value(appId, nullptr);
360+ if (!item) {
361+ QString df = findDesktopFile(appId);
362+ if (!df.isEmpty()) {
363+ item = parseDesktopFile(df);
364+ if (item) {
365+ m_itemCache[appId] = item;
366+ } else {
367+ qWarning() << "Unable to parse desktop file for" << appId << "path" << df;
368+ }
369+ } else {
370+ qDebug() << "Unable to find desktop file for:" << appId;
371+ }
372+ }
373+
374+ if (!item)
375+ qWarning() << "Unable to find item for: " << appId;
376+
278377 return item;
279378}
280379
@@ -285,7 +384,7 @@ void LauncherBackend::loadFromVariant(const QVariantMap &details)
285384 }
286385 QString appId = details.value("id").toString();
287386
288− LauncherBackendItem *item = m_itemCache.value(appId);
387+ LauncherBackendItem *item = m_itemCache.value(appId, nullptr);
289388 if (item) {
290389 delete item;
291390 }
@@ -295,6 +394,8 @@ void LauncherBackend::loadFromVariant(const QVariantMap &details)
295394 item->desktopFile = details.value("desktopFile").toString();
296395 item->displayName = details.value("name").toString();
297396 item->icon = details.value("icon").toString();
397+ item->count = details.value("count").toInt();
398+ item->countVisible = details.value("countVisible").toBool();
298399
299400 m_itemCache.insert(appId, item);
300401 m_storedApps.append(appId);
@@ -308,6 +409,8 @@ QVariantMap LauncherBackend::itemToVariant(const QString &appId) const
308409 details.insert("name", item->displayName);
309410 details.insert("icon", item->icon);
310411 details.insert("desktopFile", item->desktopFile);
412+ details.insert("count", item->count);
413+ details.insert("countVisible", item->countVisible);
311414 return details;
312415}
313416
@@ -318,3 +421,150 @@ bool LauncherBackend::isDefaultsItem(const QList<QVariantMap> &apps) const
318421 // list of one item with the 'defaults' field set to true.
319422 return (apps.size() == 1 && apps[0].value("defaults").toBool());
320423}
424+
425+bool LauncherBackend::handleMessage(const QDBusMessage& message, const QDBusConnection& connection)
426+{
427+ /* Check to make sure we're getting properties on our interface */
428+ if (message.type() != QDBusMessage::MessageType::MethodCallMessage) {
429+ return false;
430+ }
431+ if (message.interface() != "org.freedesktop.DBus.Properties") {
432+ return false;
433+ }
434+ if (message.arguments()[0].toString() != "com.canonical.Unity.Launcher.Item") {
435+ return false;
436+ }
437+
438+ /* Break down the path to just the app id */
439+ QString pathtemp = message.path();
440+ if (!pathtemp.startsWith("/com/canonical/Unity/Launcher/")) {
441+ return false;
442+ }
443+ pathtemp.remove("/com/canonical/Unity/Launcher/");
444+ if (pathtemp.indexOf('/') >= 0) {
445+ return false;
446+ }
447+
448+ /* Find ourselves an appid */
449+ QString appid = decodeAppId(pathtemp);
450+ QVariantList retval;
451+
452+ if (message.member() == "Get") {
453+ if (message.arguments()[1].toString() == "count") {
454+ retval.append(QVariant::fromValue(QDBusVariant(this->count(appid))));
455+ } else if (message.arguments()[1].toString() == "countVisible") {
456+ retval.append(QVariant::fromValue(QDBusVariant(this->countVisible(appid))));
457+ }
458+ } else if (message.member() == "Set") {
459+ if (message.arguments()[1].toString() == "count") {
460+ this->setCount(appid, message.arguments()[2].value<QDBusVariant>().variant().toInt());
461+ } else if (message.arguments()[1].toString() == "countVisible") {
462+ this->setCountVisible(appid, message.arguments()[2].value<QDBusVariant>().variant().toBool());
463+ }
464+ } else if (message.member() == "GetAll") {
465+ retval.append(this->itemToVariant(appid));
466+ } else {
467+ return false;
468+ }
469+
470+ QDBusMessage reply = message.createReply(retval);
471+ return connection.send(reply);
472+}
473+
474+QString LauncherBackend::introspect(const QString &path) const
475+{
476+ /* This case we should just list the nodes */
477+ if (path == "/com/canonical/Unity/Launcher/" || path == "/com/canonical/Unity/Launcher") {
478+ QString nodes;
479+
480+ Q_FOREACH(const QString &appId, m_itemCache.keys()) {
481+ nodes.append("<node name=\"");
482+ nodes.append(encodeAppId(appId));
483+ nodes.append("\"/>\n");
484+ }
485+
486+ return nodes;
487+ }
488+
489+ /* Should not happen, but let's handle it */
490+ if (!path.startsWith("/com/canonical/Unity/Launcher")) {
491+ return "";
492+ }
493+
494+ /* Now we should be looking at a node */
495+ QString nodeiface =
496+ "<interface name=\"com.canonical.Unity.Launcher.Item\">"
497+ "<property name=\"count\" type=\"i\" access=\"readwrite\" />"
498+ "<property name=\"countVisible\" type=\"b\" access=\"readwrite\" />"
499+ "</interface>";
500+ return nodeiface;
501+}
502+
503+QString LauncherBackend::decodeAppId(const QString& path)
504+{
505+ QByteArray bytes = path.toUtf8();
506+ QByteArray decoded;
507+
508+ for (int i = 0; i < bytes.size(); ++i) {
509+ char chr = bytes.at(i);
510+
511+ if (chr == '_') {
512+ QString number;
513+ number.append(bytes.at(i+1));
514+ number.append(bytes.at(i+2));
515+
516+ bool okay;
517+ char newchar = number.toUInt(&okay, 16);
518+ if (okay)
519+ decoded.append(newchar);
520+
521+ i += 2;
522+ } else {
523+ decoded.append(chr);
524+ }
525+ }
526+
527+ return QString::fromUtf8(decoded);
528+}
529+
530+QString LauncherBackend::encodeAppId(const QString& appId)
531+{
532+ QByteArray bytes = appId.toUtf8();
533+ QString encoded;
534+
535+ for (int i = 0; i < bytes.size(); ++i) {
536+ uchar chr = bytes.at(i);
537+
538+ if ((chr >= 'a' && chr <= 'z') ||
539+ (chr >= 'A' && chr <= 'Z') ||
540+ (chr >= '0' && chr <= '9'&& i != 0)) {
541+ encoded.append(chr);
542+ } else {
543+ QString hexval = QString("_%1").arg(chr, 2, 16, QChar('0'));
544+ encoded.append(hexval.toUpper());
545+ }
546+ }
547+
548+ return encoded;
549+}
550+
551+void LauncherBackend::emitPropChangedDbus(const QString& appId, const QString& property, QVariant &value) const
552+{
553+ QString path("/com/canonical/Unity/Launcher/");
554+ path.append(encodeAppId(appId));
555+
556+ QDBusMessage message = QDBusMessage::createSignal(path, "org.freedesktop.DBus.Properties", "PropertiesChanged");
557+
558+ QList<QVariant> arguments;
559+ QVariantHash changedprops;
560+ changedprops[property] = QVariant::fromValue(QDBusVariant(value));
561+ QVariantList deletedprops;
562+
563+ arguments.append(changedprops);
564+ arguments.append(deletedprops);
565+
566+ message.setArguments(arguments);
567+
568+ QDBusConnection con = QDBusConnection::sessionBus();
569+ con.send(message);
570+}
plugins/Unity/Launcher/backend/launcherbackend.h+52−4
@@ -24,6 +24,7 @@
2424#include <QObject>
2525#include <QSettings>
2626#include <QStringList>
27+#include <QDBusVirtualObject>
2728
2829class AccountsServiceDBusAdaptor;
2930
@@ -32,11 +33,13 @@ class AccountsServiceDBusAdaptor;
3233 */
3334
3435class LauncherBackendItem;
36+class LauncherBackendTest;
3537
36−class LauncherBackend : public QObject
38+class LauncherBackend : public QDBusVirtualObject
3739{
3840 Q_OBJECT
3941
42+ friend LauncherBackendTest;
4043
4144public:
4245 LauncherBackend(QObject *parent = 0);
@@ -116,16 +119,53 @@ public:
116119 */
117120 int count(const QString &appId) const;
118121
122+ /**
123+ * @brief Set the count on an item
124+ * @param appId The ID of the application
125+ * @param count Count to show on the application
126+ */
127+ void setCount(const QString &appId, int count) const;
128+
129+ /**
130+ * @brief Get whether the count should be visible
131+ * @param appId The ID of the application.
132+ * @returns Whether to show a count on the launcher
133+ */
134+ bool countVisible(const QString &appId) const;
135+
136+ /**
137+ * @brief Set the visibility of the count item
138+ * @param appId The ID of the application
139+ * @param visible Whether the count should be visible
140+ */
141+ void setCountVisible(const QString &appId, bool visible) const;
142+
119143 /**
120144 * @brief Sets the username for which to look up launcher items.
121145 * @param username The username to use.
122146 */
123147 void setUser(const QString &username);
124148
149+ /**
150+ * @brief Handle a message to an application node
151+ * @param message DBus message to handle
152+ * @param connection DBus connection that we're using
153+ * @returns whether the message was handled
154+ */
155+ virtual bool handleMessage(const QDBusMessage& message, const QDBusConnection& connection);
156+
157+ /**
158+ * @brief Get introspection information on the objects we're exporting
159+ * @param path the dbus path containing the appid
160+ * @returns Introspection information for that node in the tree
161+ */
162+ virtual QString introspect (const QString &path) const;
163+
125164Q_SIGNALS:
126− void quickListChanged(const QString &appId, const QList<QuickListEntry> &quickList);
127− void progressChanged(const QString &appId, int progress);
128− void countChanged(const QString &appId, int count);
165+ void quickListChanged(const QString &appId, const QList<QuickListEntry> &quickList) const;
166+ void progressChanged(const QString &appId, int progress) const;
167+ void countChanged(const QString &appId, int count) const;
168+ void countVisibleChanged(const QString &appId, bool visible) const;
129169
130170private:
131171 QString findDesktopFile(const QString &appId) const;
@@ -143,6 +183,14 @@ private:
143183
144184 AccountsServiceDBusAdaptor *m_accounts;
145185 QString m_user;
186+
187+ void emitPropChangedDbus(const QString& appId, const QString& property, QVariant &value) const;
188+
189+protected: /* Protected to allow testing */
190+ LauncherBackendItem* getItem(const QString& appId) const;
191+
192+ static QString decodeAppId(const QString& path);
193+ static QString encodeAppId(const QString& appId);
146194};
147195
148196#endif // LAUNCHERBACKEND_H
plugins/Utils/CMakeLists.txt−1
@@ -11,7 +11,6 @@ include_directories(
1111)
1212
1313set(QMLPLUGIN_SRC
14− bottombarvisibilitycommunicatorshell.cpp
1514 qlimitproxymodelqml.cpp
1615 qsortfilterproxymodelqml.cpp
1716 timeformatter.cpp
plugins/Utils/bottombarvisibilitycommunicatorshell.cppdeleted−49
@@ -1,49 +0,0 @@
1−/*
2− * Copyright (C) 2012 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 "bottombarvisibilitycommunicatorshell.h"
18−
19−BottomBarVisibilityCommunicatorShell::BottomBarVisibilityCommunicatorShell()
20− : m_forceHidden(false),
21− m_position(0)
22−{
23−}
24−
25−bool BottomBarVisibilityCommunicatorShell::forceHidden() const
26−{
27− return m_forceHidden;
28−}
29−
30−double BottomBarVisibilityCommunicatorShell::position() const
31−{
32− return m_position;
33−}
34−
35−void BottomBarVisibilityCommunicatorShell::setForceHidden(bool forceHidden)
36−{
37− if (forceHidden != m_forceHidden) {
38− m_forceHidden = forceHidden;
39− Q_EMIT forceHiddenChanged(forceHidden);
40− }
41−}
42−
43−void BottomBarVisibilityCommunicatorShell::setPosition(double position)
44−{
45− if (position != m_position) {
46− m_position = position;
47− Q_EMIT positionChanged(position);
48− }
49−}
plugins/Utils/bottombarvisibilitycommunicatorshell.hdeleted−59
@@ -1,59 +0,0 @@
1−/*
2− * Copyright (C) 2012 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 BOTTOMBARVISIBILITYCOMMUNICATORSHELL_H
18−#define BOTTOMBARVISIBILITYCOMMUNICATORSHELL_H
19−
20−#include <QObject>
21−
22−class QDBusInterface;
23−
24−/** This is an internal class used to talk with the bottom bar used by either
25− * applications or the SDK.
26− * There is a method to force the bottom bar to be hidden or otherwise behave as usual
27− */
28−
29−class BottomBarVisibilityCommunicatorShell : public QObject
30−{
31− Q_OBJECT
32− Q_PROPERTY(bool forceHidden READ forceHidden WRITE setForceHidden NOTIFY forceHiddenChanged)
33− Q_PROPERTY(double position READ position WRITE setPosition NOTIFY positionChanged)
34−
35− Q_CLASSINFO("D-Bus Interface", "com.canonical.Shell.BottomBarVisibilityCommunicator")
36−public:
37− static BottomBarVisibilityCommunicatorShell& instance() {
38− static BottomBarVisibilityCommunicatorShell instance;
39− return instance;
40− }
41−
42− bool forceHidden() const;
43− void setForceHidden(bool forceHidden);
44−
45− double position() const;
46− void setPosition(double position);
47−
48−Q_SIGNALS:
49− void forceHiddenChanged(bool forceHidden);
50− void positionChanged(double position);
51−
52−private:
53− BottomBarVisibilityCommunicatorShell();
54−
55− bool m_forceHidden;
56− double m_position;
57−};
58−
59−#endif
plugins/Utils/plugin.cpp−10
@@ -26,16 +26,12 @@
2626#include "plugin.h"
2727
2828// local
29−#include "bottombarvisibilitycommunicatorshell.h"
3029#include "qlimitproxymodelqml.h"
3130#include "qsortfilterproxymodelqml.h"
3231#include "timeformatter.h"
3332#include "unitymenumodelpaths.h"
3433#include "easingcurve.h"
3534
36−static const char* BOTTOM_BAR_VISIBILITY_COMMUNICATOR_DBUS_PATH = "/BottomBarVisibilityCommunicator";
37−static const char* DBUS_SERVICE = "com.canonical.Shell.BottomBarVisibilityCommunicator";
38−
3935void UtilsPlugin::registerTypes(const char *uri)
4036{
4137 Q_ASSERT(uri == QLatin1String("Utils"));
@@ -45,16 +41,10 @@ void UtilsPlugin::registerTypes(const char *uri)
4541 qmlRegisterType<UnityMenuModelPaths>(uri, 0, 1, "UnityMenuModelPaths");
4642 qmlRegisterType<TimeFormatter>(uri, 0, 1, "TimeFormatter");
4743 qmlRegisterType<GDateTimeFormatter>(uri, 0, 1, "GDateTimeFormatter");
48− qmlRegisterUncreatableType<BottomBarVisibilityCommunicatorShell>(uri, 0, 1, "BottomBarVisibilityCommunicatorShell", "Can't create BottomBarVisibilityCommunicatorShell");
4944 qmlRegisterType<EasingCurve>(uri, 0, 1, "EasingCurve");
5045}
5146
5247void UtilsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
5348{
5449 QQmlExtensionPlugin::initializeEngine(engine, uri);
55−
56− QDBusConnection::sessionBus().registerService(DBUS_SERVICE);
57− BottomBarVisibilityCommunicatorShell *bottomBarVisibilityCommunicator = &BottomBarVisibilityCommunicatorShell::instance();
58− QDBusConnection::sessionBus().registerObject(BOTTOM_BAR_VISIBILITY_COMMUNICATOR_DBUS_PATH, bottomBarVisibilityCommunicator, QDBusConnection::ExportAllContents);
59− engine->rootContext()->setContextProperty("bottomBarVisibilityCommunicatorShell", bottomBarVisibilityCommunicator);
6050}
po/fr.po+24−15
@@ -8,14 +8,14 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2014-04-10 11:13-0400\n"
11−"PO-Revision-Date: 2014-01-28 00:34+0000\n"
12−"Last-Translator: Pierre <Unknown>\n"
11+"PO-Revision-Date: 2014-05-08 17:29+0000\n"
12+"Last-Translator: Sébastien Maes <nautilussebastien@gmail.com>\n"
1313"Language-Team: French <fr@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-05-01 06:43+0000\n"
18−"X-Generator: Launchpad (build 16985)\n"
17+"X-Launchpad-Export-Date: 2014-05-10 08:01+0000\n"
18+"X-Generator: Launchpad (build 16996)\n"
1919
2020#: plugins/Unity/Launcher/launcheritem.cpp:43
2121#: plugins/Unity/Launcher/launcheritem.cpp:73
@@ -24,7 +24,7 @@ msgstr "Conserver dans le lanceur"
2424
2525#: plugins/Unity/Launcher/launcheritem.cpp:73
2626msgid "Unlock from Launcher"
27−msgstr ""
27+msgstr "Retirer du lanceur"
2828
2929#: qml/Components/EdgeDemoOverlay.qml:151
3030msgid "Skip intro"
@@ -37,6 +37,7 @@ msgstr "Bord droit"
3737#: qml/Components/EdgeDemo.qml:114
3838msgid "Try swiping from the right edge to unlock the phone"
3939msgstr ""
40+"Faites glisser votre doigt à partir du bord droit pour déverrouiller."
4041
4142#: qml/Components/EdgeDemo.qml:145
4243msgid "Top edge"
@@ -45,6 +46,8 @@ msgstr "Bord supérieur"
4546#: qml/Components/EdgeDemo.qml:146
4647msgid "Try swiping from the top edge to access the indicators"
4748msgstr ""
49+"Faites glisser votre doigt à partir du bord supérieur pour accéder aux "
50+"indicateurs."
4851
4952#: qml/Components/EdgeDemo.qml:171
5053msgid "Close"
@@ -53,6 +56,8 @@ msgstr "Fermer"
5356#: qml/Components/EdgeDemo.qml:172
5457msgid "Swipe up again to close the settings screen"
5558msgstr ""
59+"Faites glisser à nouveau votre doigt vers le bord supérieur pour fermer "
60+"l'écran des paramètres"
5661
5762#: qml/Components/EdgeDemo.qml:199
5863msgid "Left edge"
@@ -61,16 +66,20 @@ msgstr "Bord gauche"
6166#: qml/Components/EdgeDemo.qml:200
6267msgid "Swipe from the left to reveal the launcher for quick access to apps"
6368msgstr ""
69+"Faites glisser votre doigt à partir du bord gauche pour afficher le lanceur "
70+"d'applications."
6471
6572#: qml/Components/EdgeDemo.qml:227
6673msgid "Well done"
67−msgstr ""
74+msgstr "Félicitations"
6875
6976#: qml/Components/EdgeDemo.qml:228
7077msgid ""
7178"You have now mastered the edge gestures and can start using the "
7279"phone<br><br>Tap on the screen to start"
7380msgstr ""
81+"Vous maîtrisez maintenant les différents gestes et pouvez commencer à "
82+"utiliser le téléphone<br><br>Appuyez sur l'écran pour commencer"
7483
7584#: qml/Components/PageHeader.qml:365
7685msgid "Recent searches"
@@ -78,28 +87,28 @@ msgstr "Recherches récentes"
7887
7988#: qml/Components/SeeMore.qml:35
8089msgid "See more"
81−msgstr ""
90+msgstr "Voir plus"
8291
8392#: qml/Components/SeeMore.qml:58
8493msgid "See less"
85−msgstr ""
94+msgstr "Voir moins"
8695
8796#: qml/Dash/DashApps.qml:37
8897msgid "Recent"
89−msgstr ""
98+msgstr "Récent"
9099
91100#: qml/Dash/PreviewListView.qml:52
92101#, qt-format
93102msgid "%1 Preview"
94−msgstr ""
103+msgstr "%1 Aperçu"
95104
96105#: qml/Dash/Previews/PreviewActionCombo.qml:34
97106msgid "More..."
98−msgstr ""
107+msgstr "Plus..."
99108
100109#: qml/Dash/Previews/PreviewActionCombo.qml:34
101110msgid "Less..."
102−msgstr ""
111+msgstr "Moins..."
103112
104113#: qml/Dash/Previews/PreviewRatingInput.qml:81
105114msgid "Rate this"
@@ -115,7 +124,7 @@ msgstr "Envoyer"
115124
116125#: qml/Greeter/Greeter.qml:151
117126msgid "Swipe to unlock"
118−msgstr ""
127+msgstr "Faites glisser pour déverrouiller"
119128
120129#: qml/Greeter/Lockscreen.qml:149
121130msgid "Emergency Call"
@@ -148,7 +157,7 @@ msgstr "Parlez maintenant…"
148157
149158#: qml/Hud/Hud.qml:94
150159msgid "Speaking..."
151−msgstr ""
160+msgstr "En cours d'écoute..."
152161
153162#: qml/Notifications/NotificationMenuItemFactory.qml:91
154163msgid "Show password"
@@ -156,7 +165,7 @@ msgstr "Afficher le mot de passe"
156165
157166#: qml/Notifications/NotificationMenuItemFactory.qml:103
158167msgid "Please enter SIM PIN"
159−msgstr ""
168+msgstr "Veuillez entrer le code PIN de votre carte SIM"
160169
161170#: qml/Panel/SearchIndicator.qml:27
162171msgid "Search"
po/gl.po+4−4
@@ -8,14 +8,14 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2014-04-10 11:13-0400\n"
11−"PO-Revision-Date: 2014-04-08 07:04+0000\n"
11+"PO-Revision-Date: 2014-05-03 07:31+0000\n"
1212"Last-Translator: Marcos Lans <Unknown>\n"
1313"Language-Team: Galician <gl@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-05-01 06:43+0000\n"
18−"X-Generator: Launchpad (build 16985)\n"
17+"X-Launchpad-Export-Date: 2014-05-06 07:34+0000\n"
18+"X-Generator: Launchpad (build 16996)\n"
1919
2020#: plugins/Unity/Launcher/launcheritem.cpp:43
2121#: plugins/Unity/Launcher/launcheritem.cpp:73
@@ -117,7 +117,7 @@ msgstr "Enviar"
117117
118118#: qml/Greeter/Greeter.qml:151
119119msgid "Swipe to unlock"
120−msgstr ""
120+msgstr "Esvare para desbloquear"
121121
122122#: qml/Greeter/Lockscreen.qml:149
123123msgid "Emergency Call"
po/lv.po+21−21
@@ -8,27 +8,27 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2014-04-10 11:13-0400\n"
11−"PO-Revision-Date: 2014-01-11 16:48+0000\n"
11+"PO-Revision-Date: 2014-05-07 18:17+0000\n"
1212"Last-Translator: Jānis-Marks Gailis <jeanmarc.gailis@gmail.com>\n"
1313"Language-Team: Latvian <lv@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-05-01 06:43+0000\n"
18−"X-Generator: Launchpad (build 16985)\n"
17+"X-Launchpad-Export-Date: 2014-05-10 08:01+0000\n"
18+"X-Generator: Launchpad (build 16996)\n"
1919
2020#: plugins/Unity/Launcher/launcheritem.cpp:43
2121#: plugins/Unity/Launcher/launcheritem.cpp:73
2222msgid "Lock to Launcher"
23−msgstr ""
23+msgstr "Pieslēgt palaidējam"
2424
2525#: plugins/Unity/Launcher/launcheritem.cpp:73
2626msgid "Unlock from Launcher"
27−msgstr ""
27+msgstr "Atslēgt no palaidēja"
2828
2929#: qml/Components/EdgeDemoOverlay.qml:151
3030msgid "Skip intro"
31−msgstr ""
31+msgstr "Izlaist Intro"
3232
3333#: qml/Components/EdgeDemo.qml:113
3434msgid "Right edge"
@@ -36,7 +36,7 @@ msgstr "Labā mala"
3636
3737#: qml/Components/EdgeDemo.qml:114
3838msgid "Try swiping from the right edge to unlock the phone"
39−msgstr ""
39+msgstr "Mēģiniet iesvelt no labās malas, lai atbloķētu tālruni"
4040
4141#: qml/Components/EdgeDemo.qml:145
4242msgid "Top edge"
@@ -44,7 +44,7 @@ msgstr "Augšējā mala"
4444
4545#: qml/Components/EdgeDemo.qml:146
4646msgid "Try swiping from the top edge to access the indicators"
47−msgstr ""
47+msgstr "Mēģiniet iesvelt no augšējās malas, lai piekļūt pie indikatoriem"
4848
4949#: qml/Components/EdgeDemo.qml:171
5050msgid "Close"
@@ -78,15 +78,15 @@ msgstr "Nesen veiktās meklēšanas"
7878
7979#: qml/Components/SeeMore.qml:35
8080msgid "See more"
81−msgstr ""
81+msgstr "Redzēt vairāk"
8282
8383#: qml/Components/SeeMore.qml:58
8484msgid "See less"
85−msgstr ""
85+msgstr "Redzēt mazāk"
8686
8787#: qml/Dash/DashApps.qml:37
8888msgid "Recent"
89−msgstr ""
89+msgstr "Nesens"
9090
9191#: qml/Dash/PreviewListView.qml:52
9292#, qt-format
@@ -95,23 +95,23 @@ msgstr ""
9595
9696#: qml/Dash/Previews/PreviewActionCombo.qml:34
9797msgid "More..."
98−msgstr ""
98+msgstr "Vairāk..."
9999
100100#: qml/Dash/Previews/PreviewActionCombo.qml:34
101101msgid "Less..."
102−msgstr ""
102+msgstr "Mazāk..."
103103
104104#: qml/Dash/Previews/PreviewRatingInput.qml:81
105105msgid "Rate this"
106−msgstr ""
106+msgstr "Novērtēt šo"
107107
108108#: qml/Dash/Previews/PreviewRatingInput.qml:126
109109msgid "Add a review"
110−msgstr ""
110+msgstr "Komentēt"
111111
112112#: qml/Dash/Previews/PreviewRatingInput.qml:166
113113msgid "Send"
114−msgstr ""
114+msgstr "Sūtīt"
115115
116116#: qml/Greeter/Greeter.qml:151
117117msgid "Swipe to unlock"
@@ -119,16 +119,16 @@ msgstr ""
119119
120120#: qml/Greeter/Lockscreen.qml:149
121121msgid "Emergency Call"
122−msgstr ""
122+msgstr "Neatliekamās Palīdzības zvans"
123123
124124#: qml/Greeter/PassphraseLockscreen.qml:59
125125#, qt-format
126126msgid "Hello %1"
127−msgstr ""
127+msgstr "Sveiki, %1"
128128
129129#: qml/Greeter/PassphraseLockscreen.qml:59
130130msgid "Hello"
131−msgstr ""
131+msgstr "Sveiki"
132132
133133#: qml/Hud/HudParametrizedActionsPage.qml:132
134134msgid "Confirm"
@@ -152,7 +152,7 @@ msgstr "Runa..."
152152
153153#: qml/Notifications/NotificationMenuItemFactory.qml:91
154154msgid "Show password"
155−msgstr ""
155+msgstr "Rādīt paroli"
156156
157157#: qml/Notifications/NotificationMenuItemFactory.qml:103
158158msgid "Please enter SIM PIN"
@@ -165,4 +165,4 @@ msgstr "Meklēt"
165165#: qml/Shell.qml:348
166166#, qt-format
167167msgid "Please enter %1"
168−msgstr ""
168+msgstr "Lūdzu ievadiet %1"
qml/Bottombar/Bottombar.qmldeleted−227
@@ -1,227 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import "../Components"
19−import Utils 0.1
20−import Unity.Application 0.1
21−import Ubuntu.Gestures 0.1
22−import Ubuntu.Components 0.1
23−
24−Item {
25− id: bottombar
26−
27− // Whether there's an application on foreground (as opposed to have shell's Dash on foreground)
28− property bool applicationIsOnForeground
29−
30− property variant theHud
31− property bool enabled: false
32− property bool preventHiding: dragArea.dragging
33− readonly property real bottomEdgeButtonCenterDistance: units.gu(34)
34−
35− state: "hidden"
36−
37− function hide() {
38− dismissTimer.stop()
39− bottombar.state = "hidden"
40− }
41−
42− onApplicationIsOnForegroundChanged: {
43− if (!applicationIsOnForeground) {
44− hide()
45− }
46− }
47−
48− onStateChanged: {
49− if (state == "hidden") {
50− dismissTimer.stop()
51− bottomBarVisibilityCommunicatorShell.forceHidden = false
52− } else {
53− dismissTimer.restart()
54− }
55− }
56−
57− onPreventHidingChanged: {
58− if (!preventHiding) {
59− if (state == "hint" || state == "reveal")
60− hide()
61− }
62−
63− if (dismissTimer.running) {
64− dismissTimer.restart();
65− }
66− }
67−
68− Timer {
69− id: dismissTimer
70− interval: 1000
71− onTriggered: {
72− if (!bottombar.preventHiding) {
73− bottombar.state = "hidden"
74− } else {
75− dismissTimer.restart()
76− }
77− }
78− }
79−
80− HudButton {
81− id: hudButton
82−
83− readonly property bool centeredHud: parent.width < units.gu(68) // Nexus 7 has 67 gu width
84−
85− x: centeredHud ? parent.width / 2 - width / 2 : MathUtils.clamp(dragArea.touchStartX - (width / 2), 0, bottombar.width - width)
86− y: bottombar.height - bottomEdgeButtonCenterDistance - (height / 2) - bottomMargin
87− z: 1
88− visible: opacity != 0
89−
90− mouseOver: {
91− if (dragArea.status === DirectionalDragArea.Recognized) {
92− var touchLocal = mapFromItem(dragArea, dragArea.touchX, dragArea.touchY)
93− return touchLocal.x > 0 && touchLocal.x < width
94− && touchLocal.y > 0 && touchLocal.y < height
95− } else {
96− return false
97− }
98− }
99−
100− onClicked: theHud.show()
101−
102− Behavior on bottomMargin {
103− NumberAnimation{duration: hudButton.opacity < 0.01 ? 200 : 70; easing.type: Easing.OutQuart}
104− }
105−
106− Behavior on opacity {
107− NumberAnimation{duration: 200; easing.type: Easing.OutCubic}
108− }
109− }
110−
111− Connections {
112− target: theHud
113− onShownChanged: {
114− bottomBarVisibilityCommunicatorShell.forceHidden = theHud.shown
115− if (theHud.shown) {
116− bottombar.state = "hidden"
117− }
118− }
119− }
120−
121− EdgeDragArea {
122− id: dragArea
123− objectName: "hudDragArea"
124− width: parent.width
125− height: shell.edgeSize
126− anchors.bottom: parent.bottom
127−
128− distanceThreshold: units.gu(8)
129− enabled: !theHud.shown && bottombar.enabled && applicationIsOnForeground
130− direction: Direction.Upwards
131− maxSilenceTime: 2000
132−
133− property int previousStatus: -1
134− property real touchStartX: -1
135−
136− readonly property real distanceFromThreshold: (-distance) - distanceThreshold // distance is negative
137− readonly property real revealDistance: units.gu(2)
138− readonly property real commitDistance: units.gu(6)
139− readonly property real commitProgress: MathUtils.clamp(distanceFromThreshold / commitDistance, 0, 1)
140−
141− onStatusChanged: {
142− if (status === DirectionalDragArea.WaitingForTouch) {
143− if (previousStatus == DirectionalDragArea.Recognized) {
144− if (hudButton.mouseOver) {
145− hudButton.clicked()
146− }
147− }
148− } else if (status === DirectionalDragArea.Undecided) {
149− if (!hudButton.centeredHud) {
150− touchStartX = touchX
151− }
152− } else if (status === DirectionalDragArea.Recognized) {
153− bottombar.state = "hint"
154− }
155− previousStatus = status
156− }
157−
158− onDistanceChanged: {
159− if (status === DirectionalDragArea.Recognized) {
160− if (distanceFromThreshold > commitDistance)
161− bottombar.state = "shown"
162− else if (distanceFromThreshold > revealDistance)
163− bottombar.state = "reveal"
164− }
165− }
166− }
167−
168− Item {
169− id: dismissArea
170− anchors {
171− top: parent.top
172− left: parent.left
173− right: parent.right
174− }
175− height: parent.height - bottomBarVisibilityCommunicatorShell.position
176−
177− MouseArea {
178− anchors.fill: parent
179− enabled: bottombar.state == "shown"
180− onPressed: {
181− bottomBarVisibilityCommunicatorShell.forceHidden = true
182− bottombar.state = "hidden"
183− }
184− }
185−
186− InputFilterArea {
187− anchors.fill: parent
188− blockInput: (hudButton.opacity == 1)
189− }
190− }
191−
192− MouseArea {
193− anchors {
194− top: dismissArea.bottom
195− left: parent.left
196− right: parent.right
197− bottom: parent.bottom
198− }
199− enabled: bottombar.state == "shown" && bottomBarVisibilityCommunicatorShell.position > 0
200− onPressed: {
201− bottombar.state = "hidden"
202− }
203− }
204−
205− states: [
206− State {
207− name: "hidden"
208− PropertyChanges { target: hudButton; opacity: 0 }
209− PropertyChanges { target: hudButton; bottomMargin: units.gu(-2) }
210− },
211− State {
212− name: "hint"
213− extend: "hidden"
214− PropertyChanges { target: hudButton; opacity: 0.5 }
215− },
216− State {
217− name: "reveal"
218− extend: "hint"
219− PropertyChanges { target: hudButton; bottomMargin: units.gu(-2) + units.gu(2) * dragArea.commitProgress }
220− },
221− State {
222− name: "shown"
223− PropertyChanges { target: hudButton; opacity: 1 }
224− PropertyChanges { target: hudButton; bottomMargin: units.gu(0) }
225− }
226− ]
227−}
qml/Bottombar/HudButton.qmldeleted−64
@@ -1,64 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import Ubuntu.Components 0.1
19−
20−Item {
21− id: item
22−
23− property bool mouseOver
24−
25− property int bottomMargin: units.gu(2)
26−
27− signal clicked()
28−
29− readonly property real scaleOnMouseOver: 1.2
30−
31− width: hudButton.width * scaleOnMouseOver
32− height: hudButton.height * scaleOnMouseOver
33−
34− Item {
35− id: hudButton
36−
37− anchors.centerIn: parent
38− height: units.gu(12)
39− width: height
40− opacity: item.mouseOver || abstractButton.pressed ? 1 : 0.7
41− scale: item.mouseOver || abstractButton.pressed ? scaleOnMouseOver : 1
42− Behavior on opacity {NumberAnimation{duration: 200; easing.type: Easing.OutQuart}}
43− Behavior on scale {NumberAnimation{duration: 200; easing.type: Easing.OutQuart}}
44−
45− AbstractButton {
46− id: abstractButton
47− anchors.fill: parent
48− style: Image {
49− anchors.fill: parent
50− source: "graphics/hud_invoke_button_active.png"
51− }
52−
53− onClicked: item.clicked()
54− }
55−
56− Image {
57− width: units.gu(4)
58− height: width
59− source: "graphics/hud.png"
60− anchors.centerIn: parent
61− fillMode: Image.PreserveAspectFit
62− }
63− }
64−}
qml/Bottombar/graphics/hud@20.pngdeleted

Binary file not shown.

qml/Bottombar/graphics/hud_invoke_button_active@20.pngdeleted

Binary file not shown.

qml/CMakeLists.txt−1
@@ -5,7 +5,6 @@ install(FILES ${QML_JS_FILES}
55 )
66
77set(QML_DIRS
8− Bottombar
98 Components
109 Dash
1110 graphics
qml/Components/LazyImage.qml+1
@@ -36,6 +36,7 @@ Item {
3636 property alias cache: image.cache
3737 property alias horizontalAlignment: image.horizontalAlignment
3838 property alias verticalAlignment: image.verticalAlignment
39+ property alias sourceImage: image
3940
4041 state: "default"
4142
qml/Components/ZoomableImage.qmlnew file+153
@@ -0,0 +1,153 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import Ubuntu.Components 0.1
19+import "../Components"
20+
21+/*! \brief Zoomable for image.
22+
23+ This widget shows image contained in source,
24+ can be zoomable accordingly with zoomable.
25+ */
26+
27+Item {
28+ id: root
29+ property alias source: lazyImage.source
30+ property var zoomable: false
31+ property alias imageState: lazyImage.state
32+ property alias scaleTo: lazyImage.scaleTo
33+ property alias asynchronous: lazyImage.asynchronous
34+
35+ Flickable {
36+ id: flickable
37+ objectName: "flickable"
38+ clip: true
39+ contentHeight: imageContainer.height
40+ contentWidth: imageContainer.width
41+
42+ onHeightChanged: image.resetScale()
43+ onWidthChanged: image.resetScale()
44+ anchors.fill: parent
45+
46+ Item {
47+ id: imageContainer
48+ objectName: "imageContainer"
49+ width: Math.max(image.width * image.scale, flickable.width)
50+ height: Math.max(image.height * image.scale, flickable.height)
51+
52+ Item {
53+ id: image
54+ objectName: "image"
55+ property alias imageState: lazyImage.state
56+ property var prevScale
57+ anchors.centerIn: parent
58+
59+ signal imageReloaded
60+
61+ LazyImage {
62+ id: lazyImage
63+ objectName: "lazyImage"
64+ smooth: !flickable.movingVertically
65+ anchors.fill: parent
66+ fillMode: Image.PreserveAspectFit
67+ scaleTo: "fit"
68+
69+ onStateChanged: {
70+ if (state == "ready") {
71+ image.imageReloaded();
72+ }
73+ }
74+ }
75+
76+ onImageReloaded: {
77+ image.height = lazyImage.sourceImage.implicitHeight
78+ image.width = lazyImage.sourceImage.implicitWidth
79+ image.resetScale();
80+ }
81+
82+ function resetScale() {
83+ image.scale = Math.min(flickable.width / image.width, flickable.height / image.height);
84+ pinchArea.minScale = image.scale;
85+ prevScale = Math.min(image.scale, 1);
86+ }
87+
88+ onScaleChanged: {
89+ var currentWidth = width * scale
90+ var currentHeight = height * scale
91+ var scaleRatio = scale / prevScale
92+ if (currentWidth > flickable.width) {
93+ var xpos = flickable.width / 2 + flickable.contentX;
94+ var xoff = xpos * scaleRatio;
95+ flickable.contentX = xoff - flickable.width / 2;
96+ }
97+ if (currentHeight > flickable.height) {
98+ var ypos = flickable.height / 2 + flickable.contentY;
99+ var yoff = ypos * scaleRatio;
100+ flickable.contentY = yoff - flickable.height / 2;
101+ }
102+ prevScale = scale;
103+ }
104+ }
105+ }
106+
107+ PinchArea {
108+ id: pinchArea
109+ objectName: "pinchArea"
110+ property real minScale: 1.0
111+ anchors.fill: parent
112+ enabled: zoomable ? zoomable : false
113+
114+ pinch.target: image
115+ pinch.minimumScale: minScale
116+ pinch.maximumScale: 10
117+
118+ onPinchFinished: flickable.returnToBounds()
119+ }
120+
121+ MouseArea {
122+ id: mouseArea
123+ objectName: "mouseArea"
124+
125+ anchors.fill: parent
126+ enabled: zoomable ? zoomable : false
127+
128+ onWheel: {
129+ var startScale = image.scale;
130+ if (wheel.angleDelta.y > 0) {
131+ image.scale = startScale + 0.1;
132+ } else if (wheel.angleDelta.y < 0) {
133+ if (image.scale > 0.1 && image.scale > pinchArea.minScale) {
134+ image.scale = startScale - 0.1;
135+ }
136+ }
137+ wheel.accepted = true;
138+ }
139+
140+ onPressed: {
141+ mouse.accepted = false;
142+ }
143+
144+ onReleased: {
145+ mouse.accepted = false;
146+ }
147+
148+ onClicked: {
149+ mouse.accepted = false;
150+ }
151+ }
152+ }
153+}
qml/Dash/Card.qmldeleted−236
@@ -1,236 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import Ubuntu.Components 0.1
19−// For image://albumart and image://thumbnailer image providers
20−import Ubuntu.Thumbnailer 0.1
21−
22−AbstractButton {
23− id: root
24− property var template
25− property var components
26− property var cardData
27−
28− property real fontScale: 1.0
29− property int headerAlignment: Text.AlignLeft
30− readonly property int headerHeight: headerLoader.item ? headerLoader.item.height : 0
31− property int fixedHeaderHeight: -1
32− readonly property string title: cardData && cardData["title"] || ""
33−
34− property bool showHeader: true
35−
36− implicitWidth: childrenRect.width
37− implicitHeight: summary.y + summary.height + (summary.text && backgroundLoader.active ? units.gu(1) : 0)
38−
39− Loader {
40− id: backgroundLoader
41− objectName: "backgroundLoader"
42−
43− readonly property bool artAndSummary: components["art"]["field"] && components["summary"] || false
44− active: template["card-layout"] !== "horizontal" && (template["card-background"] || components["background"] || artAndSummary)
45− anchors.fill: parent
46−
47− sourceComponent: UbuntuShape {
48− objectName: "background"
49− radius: "medium"
50− color: getColor(0) || "white"
51− gradientColor: getColor(1) || color
52− anchors.fill: parent
53− image: backgroundImage.source ? backgroundImage : null
54−
55− property real luminance: 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b
56−
57− property Image backgroundImage: Image {
58− objectName: "backgroundImage"
59− source: {
60− if (cardData && typeof cardData["background"] === "string") return cardData["background"]
61− else if (template && typeof template["card-background"] === "string") return template["card-background"]
62− else return ""
63− }
64− }
65−
66− function getColor(index) {
67− if (cardData && typeof cardData["background"] === "object"
68− && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
69− return cardData["background"]["elements"][index];
70− } else if (template && typeof template["card-background"] === "object"
71− && (template["card-background"]["type"] === "color" || template["card-background"]["type"] === "gradient")) {
72− return template["card-background"]["elements"][index];
73− } else return undefined;
74− }
75− }
76− }
77−
78− UbuntuShape {
79− id: artShape
80− radius: "medium"
81− objectName: "artShape"
82− anchors.horizontalCenter: template && template["card-layout"] === "horizontal" ? undefined : parent.horizontalCenter
83− anchors.left: template && template["card-layout"] === "horizontal" ? parent.left : undefined
84− visible: cardData && cardData["art"] || false
85−
86− readonly property real aspect: components !== undefined ? components["art"]["aspect-ratio"] : 1
87− readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect
88−
89− Component.onCompleted: updateWidthHeightBindings();
90− onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings();
91−
92− function updateWidthHeightBindings() {
93− if (aspectSmallerThanImageAspect) {
94− width = Qt.binding(function() { return !visible ? 0 : image.width });
95− height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect });
96− } else {
97− width = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.width : height * image.aspect });
98− height = Qt.binding(function() { return !visible ? 0 : image.height });
99− }
100− }
101−
102− image: Image {
103− objectName: "artImage"
104− source: cardData && cardData["art"] || ""
105− cache: true
106− // FIXME uncomment when having investigated / fixed the crash
107− //sourceSize.width: width > height ? width : 0
108− //sourceSize.height: height > width ? height : 0
109− fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop
110−
111− readonly property real aspect: implicitWidth / implicitHeight
112− readonly property bool isHorizontal: template && template["card-layout"] === "horizontal"
113−
114− Component.onCompleted: updateWidthHeightBindings();
115− onIsHorizontalChanged: updateWidthHeightBindings();
116−
117− function updateWidthHeightBindings() {
118− if (isHorizontal) {
119− width = Qt.binding(function() { return height * artShape.aspect });
120− height = Qt.binding(function() { return root.headerHeight });
121− } else {
122− width = Qt.binding(function() { return root.width });
123− height = Qt.binding(function() { return width / artShape.aspect });
124− }
125− }
126− }
127− }
128−
129− Loader {
130− id: overlayLoader
131− anchors {
132− left: artShape.left
133− right: artShape.right
134− bottom: artShape.bottom
135− }
136− active: template && template["overlay"] && artShape.visible && artShape.image.status === Image.Ready || false
137−
138− sourceComponent: ShaderEffect {
139− id: overlay
140−
141− height: headerLoader.item ? headerLoader.item.height : 0
142− opacity: headerLoader.item ? headerLoader.item.opacity * 0.6 : 0
143−
144− property var source: ShaderEffectSource {
145− id: shaderSource
146− sourceItem: artShape
147− onVisibleChanged: if (visible) scheduleUpdate()
148− live: false
149− sourceRect: Qt.rect(0, artShape.height - overlay.height, artShape.width, overlay.height)
150− }
151−
152− vertexShader: "
153− uniform highp mat4 qt_Matrix;
154− attribute highp vec4 qt_Vertex;
155− attribute highp vec2 qt_MultiTexCoord0;
156− varying highp vec2 coord;
157− void main() {
158− coord = qt_MultiTexCoord0;
159− gl_Position = qt_Matrix * qt_Vertex;
160− }"
161−
162− fragmentShader: "
163− varying highp vec2 coord;
164− uniform sampler2D source;
165− uniform lowp float qt_Opacity;
166− void main() {
167− lowp vec4 tex = texture2D(source, coord);
168− gl_FragColor = vec4(0, 0, 0, tex.a) * qt_Opacity;
169− }"
170− }
171− }
172−
173− Loader {
174− id: headerLoader
175− objectName: "cardHeaderLoader"
176−
177− anchors {
178− top: {
179− if (template) {
180− if (template["overlay"]) return overlayLoader.top;
181− if (template["card-layout"] === "horizontal") return artShape.top;
182− }
183− return artShape.bottom;
184− }
185− left: {
186− if (template) {
187− if (!template["overlay"] && template["card-layout"] === "horizontal") return artShape.right;
188− }
189− return parent.left;
190− }
191− right: parent.right
192− }
193− active: cardData && cardData["title"] || cardData && cardData["mascot"] || false
194−
195− sourceComponent: CardHeader {
196− id: header
197− objectName: "cardHeader"
198−
199− mascot: cardData && cardData["mascot"] || ""
200− title: root.title
201− subtitle: cardData && cardData["subtitle"] || ""
202−
203− titleWeight: components && components["subtitle"] ? Font.DemiBold : Font.Normal
204−
205− opacity: showHeader ? 1 : 0
206− inOverlay: root.template && root.template["overlay"] === true
207− fontColor: inOverlay ? "white" : summary.color
208− useMascotShape: !backgroundLoader.active && !inOverlay
209− headerAlignment: root.headerAlignment
210− height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight
211− fontScale: root.fontScale
212−
213− Behavior on opacity { NumberAnimation { duration: UbuntuAnimation.SnapDuration } }
214− }
215− }
216−
217− Label {
218− id: summary
219− objectName: "summaryLabel"
220− anchors {
221− top: headerLoader.active ? headerLoader.bottom : artShape.bottom
222− left: parent.left
223− right: parent.right
224− margins: backgroundLoader.active ? units.gu(1) : 0
225− topMargin: 0
226− }
227− wrapMode: Text.Wrap
228− maximumLineCount: 5
229− elide: Text.ElideRight
230− text: cardData && cardData["summary"] || ""
231− height: text ? implicitHeight : 0
232− fontSize: "small"
233− // TODO karni: Change "grey" to Ubuntu.Components.Palette color once updated.
234− color: backgroundLoader.active && backgroundLoader.item.luminance < 0.7 ? "white" : "grey"
235− }
236−}
qml/Dash/CardCarousel.qml+13−10
@@ -49,21 +49,24 @@ DashRenderer {
4949 property real fontScale: 1 / selectedItemScaleFactor
5050 property real headerHeight: cardTool.headerHeight / selectedItemScaleFactor
5151
52− itemComponent: Card {
53− id: card
54− objectName: "carouselDelegate" + index
55− fixedHeaderHeight: carousel.headerHeight
56− cardData: model
57− template: cardTool.template
58− components: cardTool.components
52+ itemComponent: Loader {
53+ id: loader
5954
6055 property bool explicitlyScaled
6156 property var model
62−
6357 enabled: false
64− showHeader: explicitlyScaled
6558
66− fontScale: carousel.fontScale
59+ sourceComponent: cardTool.cardComponent
60+ onLoaded: {
61+ item.objectName = "carouselDelegate" + index;
62+ item.fixedHeaderHeight = Qt.binding(function() { return carousel.headerHeight; });
63+ item.height = Qt.binding(function() { return cardTool.cardHeight; });
64+ item.cardData = Qt.binding(function() { return model; });
65+ item.template = Qt.binding(function() { return cardTool.template; });
66+ item.components = Qt.binding(function() { return cardTool.components; });
67+ item.fontScale = Qt.binding(function() { return carousel.fontScale; });
68+ item.showHeader = Qt.binding(function() { return loader.explicitlyScaled; });
69+ }
6770 }
6871 }
6972}
qml/Dash/CardFilterGrid.qml+19−16
@@ -45,25 +45,28 @@ DashRenderer {
4545 collapsedRowCount: Math.min(2, cardTool && cardTool.template && cardTool.template["collapsed-rows"] || 2)
4646 delegateCreationBegin: genericFilterGrid.delegateCreationBegin
4747 delegateCreationEnd: genericFilterGrid.delegateCreationEnd
48− delegate: Loader {
49− asynchronous: true
48+ delegate: Item {
5049 width: filterGrid.cellWidth
5150 height: filterGrid.cellHeight
52− Card {
53− id: card
54− width: cardTool.cardWidth
55− height: cardTool.cardHeight
56− fixedHeaderHeight: cardTool.headerHeight
51+ Loader {
52+ id: loader
53+ sourceComponent: cardTool.cardComponent
5754 anchors.horizontalCenter: parent.horizontalCenter
58− objectName: "delegate" + index
59− cardData: model
60− template: cardTool.template
61− components: cardTool.components
62−
63− headerAlignment: cardTool.headerAlignment
64−
65− onClicked: genericFilterGrid.clicked(index, card.y)
66− onPressAndHold: genericFilterGrid.pressAndHold(index, card.y)
55+ onLoaded: {
56+ item.objectName = "delegate" + index;
57+ item.width = Qt.binding(function() { return cardTool.cardWidth; });
58+ item.height = Qt.binding(function() { return cardTool.cardHeight; });
59+ item.fixedArtShapeSize = Qt.binding(function() { return cardTool.artShapeSize; });
60+ item.cardData = Qt.binding(function() { return model; });
61+ item.template = Qt.binding(function() { return cardTool.template; });
62+ item.components = Qt.binding(function() { return cardTool.components; });
63+ item.headerAlignment = Qt.binding(function() { return cardTool.headerAlignment; });
64+ }
65+ Connections {
66+ target: loader.item
67+ onClicked: genericFilterGrid.clicked(index, item.y)
68+ onPressAndHold: genericFilterGrid.pressAndHold(index, item.y)
69+ }
6770 }
6871 }
6972 }
qml/Dash/CardHeader.qmldeleted−126
@@ -1,126 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import Ubuntu.Components 0.1
19−
20−Item {
21− id: root
22− property url mascot: ""
23− property alias title: titleLabel.text
24− property var subtitle
25−
26− property alias titleWeight: titleLabel.font.weight
27− property alias titleSize: titleLabel.fontSize
28−
29− // FIXME: Saviq, used to scale fonts down in Carousel
30− property real fontScale: 1.0
31−
32− property alias headerAlignment: titleLabel.horizontalAlignment
33−
34− property bool inOverlay: false
35− property bool useMascotShape: true
36− property color fontColor: Theme.palette.selected.backgroundText
37−
38− visible: mascot != "" || title
39− implicitHeight: row.height > 0 ? row.height + row.margins * 2 : 0
40−
41− Row {
42− id: row
43− objectName: "outerRow"
44−
45− property real margins: units.gu(1)
46−
47− spacing: mascotShapeLoader.active || mascotImageLoader.active || inOverlay ? margins : 0
48− anchors {
49− top: parent.top; left: parent.left; right: parent.right
50− margins: margins
51− leftMargin: spacing
52− rightMargin: spacing
53− }
54−
55− Loader {
56− id: mascotShapeLoader
57− objectName: "mascotShapeLoader"
58−
59− active: useMascotShape && mascotImageLoader.item && mascotImageLoader.item.status === Image.Ready
60− visible: active
61− anchors.verticalCenter: parent.verticalCenter
62− // TODO karni: Icon aspect-ratio is 8:7.5. Revisit these values to avoid fraction of pixels.
63− width: units.gu(6)
64− height: units.gu(5.625)
65− readonly property int maxSize: Math.max(width, height) * 4
66−
67− sourceComponent: UbuntuShape {
68− image: mascotImageLoader.item
69− }
70− }
71−
72− Loader {
73− id: mascotImageLoader
74− active: root.mascot != ""
75− visible: active && !useMascotShape && item.status === Image.Ready
76− anchors.verticalCenter: parent.verticalCenter
77− sourceComponent: Image {
78− objectName: "mascotImage"
79−
80− source: root.mascot
81− width: source ? mascotShapeLoader.width : 0
82− height: mascotShapeLoader.height
83−
84− sourceSize { width: mascotShapeLoader.maxSize; height: mascotShapeLoader.maxSize }
85− fillMode: Image.PreserveAspectCrop
86− horizontalAlignment: Image.AlignHCenter
87− verticalAlignment: Image.AlignVCenter
88− }
89− }
90−
91− Column {
92− objectName: "column"
93− width: parent.width - x
94− spacing: units.dp(2)
95− anchors.verticalCenter: parent.verticalCenter
96−
97− Label {
98− id: titleLabel
99− objectName: "titleLabel"
100− anchors { left: parent.left; right: parent.right }
101− elide: Text.ElideRight
102− font.weight: Font.Normal
103− fontSize: "small"
104− wrapMode: Text.Wrap
105− maximumLineCount: 2
106− font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale)
107− color: fontColor
108− }
109−
110− Loader {
111− active: titleLabel.text && root.subtitle
112− anchors { left: parent.left; right: parent.right }
113− sourceComponent: Label {
114− id: subtitleLabel
115− objectName: "subtitleLabel"
116− elide: Text.ElideRight
117− fontSize: "small"
118− font.weight: Font.Light
119− font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale)
120− color: fontColor
121− text: root.subtitle
122− }
123− }
124− }
125− }
126−}
qml/Dash/CardTool.qml+30−22
@@ -15,6 +15,7 @@
1515 */
1616
1717import QtQuick 2.0
18+import Dash 0.1
1819
1920/*!
2021 \brief Tool for introspecting Card properties.
@@ -65,6 +66,8 @@ Item {
6566 return layout;
6667 }
6768
69+ property var cardComponent: CardCreatorCache.getCardComponent(cardTool.template, cardTool.components);
70+
6871 // FIXME: Saviq
6972 // Only way for the card below to actually be laid out completely.
7073 // If invisible or in "data" array, some components are not taken into account.
@@ -108,7 +111,7 @@ Item {
108111 if (template["card-size"] >= 12 && template["card-size"] <= 38) return units.gu(template["card-size"]);
109112 return units.gu(18.5);
110113 case "grid":
111− return card.implicitHeight
114+ return cardLoader.item ? cardLoader.item.implicitHeight : 0
112115 case "carousel":
113116 return cardWidth / (components ? components["art"]["aspect-ratio"] : 1)
114117 case undefined:
@@ -122,7 +125,8 @@ Item {
122125 /*!
123126 type:real \brief Height of the card's header.
124127 */
125− readonly property alias headerHeight: card.headerHeight
128+ readonly property int headerHeight: cardLoader.item ? cardLoader.item.headerHeight : 0
129+ readonly property size artShapeSize: cardLoader.item ? cardLoader.item.artShapeSize : 0
126130
127131 /*!
128132 \brief Desired alignment of header components.
@@ -161,15 +165,8 @@ Item {
161165 }
162166 }
163167
164− Card {
165− id: card
166− objectName: "cardToolCard"
167− template: cardTool.template
168− components: cardTool.components
169−
170− width: cardTool.cardWidth || implicitWidth
171− height: cardTool.cardHeight || implicitHeight
172−
168+ Loader {
169+ id: cardLoader
173170 property var fields: ["art", "mascot", "title", "subtitle", "summary"]
174171 property var maxData: {
175172 "art": Qt.resolvedUrl("graphics/checkers.png"),
@@ -178,19 +175,30 @@ Item {
178175 "subtitle": "—",
179176 "summary": "—\n—\n—\n—\n—"
180177 }
181−
182− onComponentsChanged: {
183− var data = {};
184− for (var k in fields) {
185− var component = components[fields[k]];
186− var key = fields[k];
187− if ((typeof component === "string" && component.length > 0) ||
188− (typeof component === "object" && component !== null
189− && typeof component["field"] === "string" && component["field"].length > 0)) {
190− data[key] = maxData[key];
178+ sourceComponent: cardTool.cardComponent
179+ onLoaded: {
180+ item.objectName = "cardToolCard";
181+ item.asynchronous = false;
182+ item.template = Qt.binding(function() { return cardTool.template; });
183+ item.components = Qt.binding(function() { return cardTool.components; });
184+ item.width = Qt.binding(function() { return cardTool.cardWidth || item.implicitWidth; });
185+ item.height = Qt.binding(function() { return cardTool.cardHeight || item.implicitHeight; });
186+ }
187+ Connections {
188+ target: cardLoader.item
189+ onComponentsChanged: {
190+ var data = {};
191+ for (var k in cardLoader.fields) {
192+ var component = cardLoader.item.components[cardLoader.fields[k]];
193+ var key = cardLoader.fields[k];
194+ if ((typeof component === "string" && component.length > 0) ||
195+ (typeof component === "object" && component !== null
196+ && typeof component["field"] === "string" && component["field"].length > 0)) {
197+ data[key] = cardLoader.maxData[key];
198+ }
191199 }
200+ cardLoader.item.cardData = data;
192201 }
193− cardData = data;
194202 }
195203 }
196204}
qml/Dash/DashContent.qml+18−10
@@ -17,6 +17,7 @@
1717import QtQuick 2.0
1818import Ubuntu.Components 0.1
1919import Unity 0.2
20+import Utils 0.1
2021import "../Components"
2122
2223Item {
@@ -183,23 +184,30 @@ Item {
183184 width: parent.width
184185 style: DashContentTabBarStyle {}
185186
186− model: dashContentList.model
187+ SortFilterProxyModel {
188+ id: tabBarModel
187189
188− onSelectedIndexChanged: {
189− if (dashContentList.currentIndex == -1 && tabBar.selectedIndex != -1) {
190− // TODO This together with the Timer below
191− // are a workaround for the first tab sometimes not showing the text.
192− // But Tabs are going away in the future so not sure if makes
193− // sense invetigating what's the problem at this stage
194− selectionModeTimer.restart();
190+ model: dashContentList.model
191+
192+ property int selectedIndex: -1
193+ onSelectedIndexChanged: {
194+ if (dashContentList.currentIndex == -1 && tabBar.selectedIndex != -1) {
195+ // TODO This together with the Timer below
196+ // are a workaround for the first tab sometimes not showing the text.
197+ // But Tabs are going away in the future so not sure if makes
198+ // sense invetigating what's the problem at this stage
199+ selectionModeTimer.restart();
200+ }
201+ dashContentList.currentIndex = selectedIndex;
195202 }
196− dashContentList.currentIndex = selectedIndex;
197203 }
198204
205+ model: tabBarModel.count > 0 ? tabBarModel : null
206+
199207 Connections {
200208 target: dashContentList
201209 onCurrentIndexChanged: {
202− tabBar.selectedIndex = dashContentList.currentIndex
210+ tabBarModel.selectedIndex = dashContentList.currentIndex
203211 }
204212 }
205213
qml/Dash/Previews/PreviewHeader.qml+86−9
@@ -15,9 +15,10 @@
1515 */
1616
1717import QtQuick 2.0
18+import Ubuntu.Components 0.1
1819import "../"
1920
20−/*! This preview widget shows a header that is the same as the card header
21+/*! This preview widget shows a header
2122 * The title comes in widgetData["title"]
2223 * The mascot comes in widgetData["mascot"]
2324 * The subtitle comes in widgetData["subtitle"]
@@ -28,15 +29,91 @@ PreviewWidget {
2829
2930 height: childrenRect.height
3031
31− CardHeader {
32− objectName: "cardHeader"
33− mascot: root.widgetData["mascot"] || ""
34− title: root.widgetData["title"] || ""
35− subtitle: root.widgetData["subtitle"] || ""
32+ Item {
33+ id: headerRoot
34+ objectName: "innerPreviewHeader"
35+ readonly property url mascot: root.widgetData["mascot"] || ""
36+ readonly property string title: root.widgetData["title"] || ""
37+ readonly property string subtitle: root.widgetData["subtitle"] || ""
38+ readonly property color fontColor: "grey"
39+
40+ implicitHeight: row.height + row.margins * 2
3641 width: parent.width
3742
38− titleSize: "large"
39− // TODO Change "grey" to Ubuntu.Components.Palette color once updated.
40− fontColor: "grey"
43+ Row {
44+ id: row
45+ objectName: "outerRow"
46+
47+ property real margins: units.gu(1)
48+
49+ spacing: mascotShapeLoader.active ? margins : 0
50+ anchors {
51+ top: parent.top; left: parent.left; right: parent.right
52+ margins: margins
53+ leftMargin: spacing
54+ rightMargin: spacing
55+ }
56+
57+ Loader {
58+ id: mascotShapeLoader
59+
60+ anchors.verticalCenter: parent.verticalCenter
61+ // TODO karni: Icon aspect-ratio is 8:7.5. Revisit these values to avoid fraction of pixels.
62+ width: units.gu(6)
63+ height: units.gu(5.625)
64+ readonly property int maxSize: Math.max(width, height) * 4
65+ asynchronous: true
66+
67+ sourceComponent: UbuntuShape {
68+ objectName: "mascotShape"
69+ visible: image.status === Image.Ready
70+ image: Image {
71+ source: headerRoot.mascot
72+ width: source ? mascotShapeLoader.width : 0
73+ height: mascotShapeLoader.height
74+
75+ sourceSize { width: mascotShapeLoader.maxSize; height: mascotShapeLoader.maxSize }
76+ fillMode: Image.PreserveAspectCrop
77+ horizontalAlignment: Image.AlignHCenter
78+ verticalAlignment: Image.AlignVCenter
79+ }
80+ }
81+ }
82+
83+ Column {
84+ objectName: "column"
85+ width: parent.width - x
86+ spacing: units.dp(2)
87+ anchors.verticalCenter: parent.verticalCenter
88+
89+ Label {
90+ id: titleLabel
91+ objectName: "titleLabel"
92+ anchors { left: parent.left; right: parent.right }
93+ elide: Text.ElideRight
94+ font.weight: Font.Normal
95+ fontSize: "large"
96+ wrapMode: Text.Wrap
97+ maximumLineCount: 2
98+ color: headerRoot.fontColor
99+ text: headerRoot.title
100+ }
101+
102+ Loader {
103+ active: titleLabel.text && headerRoot.subtitle
104+ anchors { left: parent.left; right: parent.right }
105+ sourceComponent: Label {
106+ id: subtitleLabel
107+ objectName: "subtitleLabel"
108+ elide: Text.ElideRight
109+ fontSize: "small"
110+ font.weight: Font.Light
111+ color: headerRoot.fontColor
112+ text: headerRoot.subtitle
113+ }
114+ }
115+ }
116+ }
41117 }
118+
42119}
qml/Dash/Previews/PreviewProgress.qml+3−2
@@ -34,11 +34,12 @@ PreviewWidget {
3434 ProgressBar {
3535 id: progressBar
3636 objectName: "progressBar"
37+ anchors.right: parent.right
3738 value: 0
3839 maximumValue: 100
39− implicitHeight: units.gu(5)
40+ implicitHeight: units.gu(4)
4041 height: parent.height
41− width: parent.width
42+ width: (root.width - units.gu(1)) / 2
4243
4344 property var source: widgetData["source"]
4445 // TODO Eventually we will need to support more sources other
qml/Dash/Previews/PreviewTextSummary.qml+4−2
@@ -37,6 +37,7 @@ PreviewWidget {
3737 left: parent.left
3838 right: parent.right
3939 }
40+ height: visible ? implicitHeight : 0
4041 fontSize: "large"
4142 // TODO karni: Yet another fix requiring Palette update.
4243 color: "grey" //Theme.palette.selected.backgroundText
@@ -68,7 +69,7 @@ PreviewWidget {
6869 wrapMode: Text.Wrap
6970
7071 Behavior on height {
71− UbuntuNumberAnimation {}
72+ UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration }
7273 }
7374 }
7475
@@ -79,8 +80,9 @@ PreviewWidget {
7980 left: parent.left
8081 right: parent.right
8182 top: textLabel.bottom
82− topMargin: units.gu(1)
83+ topMargin: visible ? units.gu(1) : 0
8384 }
85+ height: visible ? implicitHeight : 0
8486 visible: textLabel.lineCount > textLabel.maximumCollapsedLineCount
8587 }
8688}
qml/Dash/Previews/PreviewZoomableImage.qml+4−7
@@ -28,14 +28,11 @@ PreviewWidget {
2828 id: root
2929 implicitHeight: units.gu(22)
3030
31− LazyImage {
31+ ZoomableImage {
32+ id: image
3233 objectName: "image"
33− anchors {
34− top: parent.top
35− bottom: parent.bottom
36− }
37− scaleTo: "height"
3834 source: widgetData["source"]
39− initialWidth: units.gu(13)
35+ zoomable: widgetData["zoomable"] ? widgetData["zoomable"] : false
36+ anchors.fill: parent
4037 }
4138}
qml/Dash/ScopeListView.qml+1−1
@@ -15,7 +15,7 @@
1515 */
1616
1717import QtQuick 2.0
18−import DashViews 0.1
18+import Dash 0.1
1919
2020ListViewWithPageHeader {
2121 maximumFlickVelocity: height * 10
qml/Notifications/Notifications.qml+11
@@ -16,6 +16,8 @@
1616
1717import QtQuick 2.0
1818import Ubuntu.Components 0.1
19+import Unity.Notifications 1.0 as UnityNotifications
20+import Utils 0.1
1921import "../Components"
2022
2123ListView {
@@ -25,6 +27,15 @@ ListView {
2527 interactive: false
2628
2729 property real margin
30+ property bool useModal: snapDecisionProxyModel.count > 0
31+
32+ SortFilterProxyModel {
33+ id: snapDecisionProxyModel
34+
35+ model: notificationList.model
36+ filterRole: UnityNotifications.ModelInterface.RoleType
37+ filterRegExp: RegExp(UnityNotifications.Notification.SnapDecision)
38+ }
2839 spacing: delegate.fullscreen ? 0 : units.gu(.5)
2940
3041 currentIndex: (currentIndex < 1 && count > 1) ? 1 : -1
qml/Shell.qml+21−48
@@ -28,9 +28,7 @@ import "Dash"
2828import "Greeter"
2929import "Launcher"
3030import "Panel"
31−import "Hud"
3231import "Components"
33−import "Bottombar"
3432import "Notifications"
3533import Unity.Notifications 1.0 as NotificationBackend
3634
@@ -308,7 +306,7 @@ FocusScope {
308306
309307 readonly property int backgroundTopMargin: -panel.panelHeight
310308
311− hides: [launcher, panel.indicators, hud]
309+ hides: [launcher, panel.indicators]
312310 shown: false
313311 enabled: true
314312 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
@@ -381,7 +379,7 @@ FocusScope {
381379 objectName: "greeter"
382380
383381 available: true
384− hides: [launcher, panel.indicators, hud]
382+ hides: [launcher, panel.indicators]
385383 shown: true
386384
387385 defaultBackground: shell.background
@@ -424,7 +422,7 @@ FocusScope {
424422 InputFilterArea {
425423 anchors.fill: parent
426424 blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
427− || panel.indicators.shown || hud.shown
425+ || panel.indicators.shown
428426 }
429427
430428 Connections {
@@ -488,47 +486,6 @@ FocusScope {
488486 }
489487 }
490488
491− Hud {
492− id: hud
493−
494− width: parent.width > units.gu(60) ? units.gu(40) : parent.width
495− height: parent.height
496−
497− available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
498− shown: false
499− showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
500− hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
501−
502− Connections {
503− target: ApplicationManager
504− onFocusedApplicationIdChanged: hud.hide()
505− }
506− }
507−
508− Revealer {
509− id: hudRevealer
510−
511− enabled: hud.shown
512− width: hud.width
513− anchors.left: hud.left
514− height: parent.height
515− target: hud.revealerTarget
516− closedValue: height
517− openedValue: 0
518− direction: Qt.RightToLeft
519− orientation: Qt.Vertical
520− handleSize: hud.handleHeight
521− onCloseClicked: target.hide()
522− }
523−
524− Bottombar {
525− id: bottombar
526− theHud: hud
527− anchors.fill: parent
528− enabled: hud.available
529− applicationIsOnForeground: ApplicationManager.focusedApplicationId
530− }
531−
532489 InputFilterArea {
533490 blockInput: launcher.shown
534491 anchors {
@@ -576,12 +533,28 @@ FocusScope {
576533 onShownChanged: {
577534 if (shown) {
578535 panel.indicators.hide()
579− hud.hide()
580− bottombar.hide()
581536 }
582537 }
583538 }
584539
540+ Rectangle {
541+ id: modalNotificationBackground
542+
543+ visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
544+ color: "#000000"
545+ anchors.fill: parent
546+ opacity: 0.5
547+
548+ MouseArea {
549+ anchors.fill: parent
550+ }
551+
552+ InputFilterArea {
553+ anchors.fill: parent
554+ blockInput: modalNotificationBackground.visible
555+ }
556+ }
557+
585558 Notifications {
586559 id: notifications
587560
run → run.shrenamed+1−1
@@ -84,6 +84,6 @@ else
8484 echo "Unity8 is already running, please stop it first"
8585 exit 1
8686 fi
87− /sbin/initctl start unity8 BINARY="`readlink -f $QML_PHONE_SHELL_PATH` $QML_PHONE_SHELL_ARGS $@"
87+ /sbin/initctl start unity8 BINARY="`readlink -f $QML_PHONE_SHELL_PATH` $QML_PHONE_SHELL_ARGS $@" QML2_IMPORT_PATH=$QML2_IMPORT_PATH LD_LIBRARY_PATH=$LD_LIBRARY_PATH
8888 tailf -n 0 ~/.cache/upstart/unity8.log
8989fi
run_on_device → run_on_device.shrenamed+2−2
@@ -88,7 +88,7 @@ sync_code() {
8888}
8989
9090build() {
91− exec_with_ssh PATH=/usr/lib/ccache:$PATH "cd $CODE_DIR/ && PATH=/usr/lib/ccache:$PATH ./build"
91+ exec_with_ssh PATH=/usr/lib/ccache:$PATH "cd $CODE_DIR/ && PATH=/usr/lib/ccache:$PATH ./build.sh"
9292}
9393
9494run() {
@@ -105,7 +105,7 @@ run() {
105105
106106 exec_with_ssh "stop unity8"
107107 exec_with_ssh "start maliit-server"
108− exec_with_ssh "cd $CODE_DIR/ && ./run $ARGS -- $RUN_OPTIONS"
108+ exec_with_ssh "cd $CODE_DIR/ && ./run.sh $ARGS -- $RUN_OPTIONS"
109109 exec_with_ssh "stop maliit-server"
110110 exec_with_ssh "start unity8"
111111}
src/ApplicationArguments.h+4−8
@@ -28,14 +28,10 @@ class ApplicationArguments : public QObject
2828{
2929 Q_OBJECT
3030public:
31− ApplicationArguments(const QStringList& args) {
32− if (args.contains(QLatin1String("-windowgeometry")) && args.size() > args.indexOf(QLatin1String("-windowgeometry")) + 1) {
33− QStringList geometryArg = args.at(args.indexOf(QLatin1String("-windowgeometry")) + 1).split('x');
34− if (geometryArg.size() == 2) {
35− m_size.rwidth() = geometryArg.at(0).toInt();
36− m_size.rheight() = geometryArg.at(1).toInt();
37− }
38− }
31+ // Not exposed to the app as setSize isn't invokable
32+ void setSize(int width, int height) {
33+ m_size.rwidth() = width;
34+ m_size.rheight() = height;
3935 }
4036
4137 Q_INVOKABLE bool hasGeometry() const { return m_size.isValid(); }
src/main.cpp+43−6
@@ -18,6 +18,7 @@
1818 */
1919
2020// Qt
21+#include <QCommandLineParser>
2122#include <QtQuick/QQuickView>
2223#include <QtGui/QGuiApplication>
2324#include <QtQml/QQmlEngine>
@@ -43,6 +44,32 @@ int startShell(int argc, const char** argv, void* server)
4344
4445 QGuiApplication::setApplicationName("Unity 8");
4546 QGuiApplication *application;
47+
48+ QCommandLineParser parser;
49+ parser.setApplicationDescription("Description: Unity 8 Shell");
50+ parser.addHelpOption();
51+
52+ QCommandLineOption fullscreenOption("fullscreen",
53+ "Run in fullscreen");
54+ parser.addOption(fullscreenOption);
55+
56+ QCommandLineOption framelessOption("frameless",
57+ "Run without window borders");
58+ parser.addOption(framelessOption);
59+
60+ QCommandLineOption mousetouchOption("mousetouch",
61+ "Allow the mouse to provide touch input");
62+ parser.addOption(mousetouchOption);
63+
64+ QCommandLineOption windowGeometryOption(QStringList() << "windowgeometry",
65+ "Specify the window geometry as [<width>x<height>]", "windowgeometry", "1");
66+ parser.addOption(windowGeometryOption);
67+
68+ QCommandLineOption testabilityOption("testability",
69+ "DISCOURAGED: Please set QT_LOAD_TESTABILITY instead. \n \
70+Load the testability driver");
71+ parser.addOption(testabilityOption);
72+
4673 if (isUbuntuMirServer) {
4774 QLibrary unityMir("unity-mir", 1);
4875 unityMir.load();
@@ -59,6 +86,11 @@ int startShell(int argc, const char** argv, void* server)
5986 application = new QGuiApplication(argc, (char**)argv);
6087 }
6188
89+ // Treat args with single dashes the same as arguments with two dashes
90+ // Ex: -fullscreen == --fullscreen
91+ parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
92+ parser.process(*application);
93+
6294 QString indicatorProfile = qgetenv("UNITY_INDICATOR_PROFILE");
6395 if (indicatorProfile.isEmpty()) {
6496 indicatorProfile = "phone";
@@ -66,12 +98,17 @@ int startShell(int argc, const char** argv, void* server)
6698
6799 resolveIconTheme();
68100
69− QStringList args = application->arguments();
70− ApplicationArguments qmlArgs(args);
101+ ApplicationArguments qmlArgs;
102+ if (parser.isSet(windowGeometryOption) &&
103+ parser.value(windowGeometryOption).split('x').size() == 2)
104+ {
105+ QStringList geom = parser.value(windowGeometryOption).split('x');
106+ qmlArgs.setSize(geom.at(0).toInt(), geom.at(1).toInt());
107+ }
71108
72109 // The testability driver is only loaded by QApplication but not by QGuiApplication.
73110 // However, QApplication depends on QWidget which would add some unneeded overhead => Let's load the testability driver on our own.
74− if (args.contains(QLatin1String("-testability")) || getenv("QT_LOAD_TESTABILITY")) {
111+ if (parser.isSet(testabilityOption) || getenv("QT_LOAD_TESTABILITY")) {
75112 QLibrary testLib(QLatin1String("qttestability"));
76113 if (testLib.load()) {
77114 typedef void (*TasInitialize)(void);
@@ -94,14 +131,14 @@ int startShell(int argc, const char** argv, void* server)
94131 view->engine()->setBaseUrl(QUrl::fromLocalFile(::qmlDirectory()));
95132 view->rootContext()->setContextProperty("applicationArguments", &qmlArgs);
96133 view->rootContext()->setContextProperty("indicatorProfile", indicatorProfile);
97− if (args.contains(QLatin1String("-frameless"))) {
134+ if (parser.isSet(framelessOption)) {
98135 view->setFlags(Qt::FramelessWindowHint);
99136 }
100137
101138 // You will need this if you want to interact with touch-only components using a mouse
102139 // Needed only when manually testing on a desktop.
103140 MouseTouchAdaptor *mouseTouchAdaptor = 0;
104− if (args.contains(QLatin1String("-mousetouch"))) {
141+ if (parser.isSet(mousetouchOption)) {
105142 mouseTouchAdaptor = new MouseTouchAdaptor;
106143 application->installNativeEventFilter(mouseTouchAdaptor);
107144 }
@@ -125,7 +162,7 @@ int startShell(int argc, const char** argv, void* server)
125162 view->setSource(source);
126163 view->setColor("transparent");
127164
128− if (qgetenv("QT_QPA_PLATFORM") == "ubuntu" || isUbuntuMirServer || args.contains(QLatin1String("-fullscreen"))) {
165+ if (qgetenv("QT_QPA_PLATFORM") == "ubuntu" || isUbuntuMirServer || parser.isSet(fullscreenOption)) {
129166 view->showFullScreen();
130167 } else {
131168 view->show();
tests/autopilot/unity8/shell/emulators/dash.py+3−4
@@ -161,7 +161,7 @@ class GenericScopeView(emulators.UnityEmulatorBase):
161161
162162 """
163163 category_element = self._get_category_element(category)
164− icon = category_element.select_single('Card', title=app_name)
164+ icon = category_element.select_single('AbstractButton', title=app_name)
165165 # FIXME some categories need a long press in order to see the preview.
166166 # Some categories do not show previews, like recent apps.
167167 # --elopio - 2014-1-14
@@ -191,7 +191,7 @@ class DashApps(GenericScopeView):
191191
192192 """
193193 category_element = self._get_category_element(category)
194− application_cards = category_element.select_many('Card')
194+ application_cards = category_element.select_many('AbstractButton')
195195
196196 # sort by y, x
197197 application_cards = sorted(
@@ -201,8 +201,7 @@ class DashApps(GenericScopeView):
201201 result = []
202202 for card in application_cards:
203203 if card.objectName != 'cardToolCard':
204− card_header = card.select_single('CardHeader')
205− result.append(card_header.title)
204+ result.append(card.title)
206205 return result
207206
208207
tests/autopilot/unity8/shell/tests/test_notifications.py+83
@@ -211,6 +211,89 @@ class InteractiveNotificationBase(NotificationsBase):
211211 self.touch.tap_object(notification.select_single(objectName="button4"))
212212 self.assert_notification_action_id_was_called("action_decline_4")
213213
214+ def test_modal_sd_without_greeter (self):
215+ """A snap-decision on a phone should block input to shell beneath it when there's no greeter."""
216+ unity_proxy = self.launch_unity()
217+ unlock_unity(unity_proxy)
218+
219+ summary = "Incoming file"
220+ body = "Frank would like to send you the file: essay.pdf"
221+ icon_path = "sync-idle"
222+ hints = [
223+ ("x-canonical-snap-decisions", "true"),
224+ ("x-canonical-non-shaped-icon", "true"),
225+ ]
226+
227+ actions = [
228+ ('action_accept', 'Accept'),
229+ ('action_decline_1', 'Decline'),
230+ ]
231+
232+ self._create_interactive_notification(
233+ summary,
234+ body,
235+ icon_path,
236+ "NORMAL",
237+ actions,
238+ hints
239+ )
240+
241+ # verify that we cannot reveal the launcher (no longer interact with the shell)
242+ time.sleep(1)
243+ self.main_window.show_dash_swiping()
244+ launcher = self.main_window.get_launcher()
245+ self.assertThat(launcher.shown, Eventually(Equals(False)))
246+
247+ # verify and interact with the triggered snap-decision notification
248+ notify_list = self._get_notifications_list()
249+ get_notification = lambda: notify_list.wait_select_single(
250+ 'Notification', objectName='notification1')
251+ notification = get_notification()
252+ self._assert_notification(notification, summary, body, True, False, 1.0)
253+ self.touch.tap_object(notification.select_single(objectName="button0"))
254+ self.assert_notification_action_id_was_called("action_accept")
255+
256+ def test_modal_sd_with_greeter (self):
257+ """A snap-decision on a phone should not block input to the greeter beneath it."""
258+ unity_proxy = self.launch_unity()
259+
260+ summary = "Incoming file"
261+ body = "Frank would like to send you the file: essay.pdf"
262+ icon_path = "sync-idle"
263+ hints = [
264+ ("x-canonical-snap-decisions", "true"),
265+ ("x-canonical-non-shaped-icon", "true"),
266+ ]
267+
268+ actions = [
269+ ('action_accept', 'Accept'),
270+ ('action_decline_1', 'Decline'),
271+ ]
272+
273+ self._create_interactive_notification(
274+ summary,
275+ body,
276+ icon_path,
277+ "NORMAL",
278+ actions,
279+ hints
280+ )
281+
282+ # verify that we can swipe away the greeter (interact with the "shell")
283+ time.sleep(1)
284+ self.main_window.show_dash_swiping()
285+ greeter = self.main_window.get_greeter()
286+ self.assertThat(greeter.shown, Eventually(Equals(False)))
287+
288+ # verify and interact with the triggered snap-decision notification
289+ notify_list = self._get_notifications_list()
290+ get_notification = lambda: notify_list.wait_select_single(
291+ 'Notification', objectName='notification1')
292+ notification = get_notification()
293+ self._assert_notification(notification, summary, body, True, False, 1.0)
294+ self.touch.tap_object(notification.select_single(objectName="button0"))
295+ self.assert_notification_action_id_was_called("action_accept")
296+
214297 def _create_interactive_notification(
215298 self,
216299 summary="",
tests/mocks/Unity/fake_categories.cpp+15−2
@@ -108,7 +108,12 @@ Categories::data(const QModelIndex& index, int role) const
108108 case RoleRenderer:
109109 {
110110 QVariantMap map;
111− map["category-layout"] = index.row() % 2 == 0 ? "grid" : "carousel";
111+ if (index.row() % 2 == 0) {
112+ map["category-layout"] = "grid";
113+ } else {
114+ map["category-layout"] = "carousel";
115+ map["overlay"] = true;
116+ }
112117 map["card-size"] = "small";
113118 return map;
114119 }
@@ -146,7 +151,14 @@ Categories::data(const QModelIndex& index, int role) const
146151 case RoleRenderer:
147152 {
148153 QVariantMap map;
149− map["category-layout"] = index.row() % 2 == 0 ? "grid" : "carousel";
154+ if (index.row() % 2 == 0) {
155+ map["category-layout"] = "grid";
156+ } else {
157+ map["category-layout"] = "carousel";
158+ map["card-size"] = "medium";
159+ map["collapsed-rows"] = 2;
160+ map["overlay"] = true;
161+ }
150162 map["card-size"] = "small";
151163 return map;
152164 }
@@ -157,6 +169,7 @@ Categories::data(const QModelIndex& index, int role) const
157169 artMap["field"] = "art";
158170 map["art"] = artMap;
159171 map["title"] = "HOLA";
172+ map["subtitle"] = "HOLA";
160173 return map;
161174 }
162175 case RoleProgressSource:
tests/mocks/Unity/fake_resultsmodel.cpp+1
@@ -103,6 +103,7 @@ ResultsModel::data(const QModelIndex& index, int role) const
103103 case RoleArt:
104104 return qmlDirectory() + "graphics/applicationIcons/dash.png";
105105 case RoleSubtitle:
106+ return QString("Subtitle.%1.%2").arg(m_categoryId).arg(index.row());
106107 case RoleMascot:
107108 case RoleEmblem:
108109 case RoleOldPrice:
tests/plugins/CMakeLists.txt+1−1
@@ -1,6 +1,6 @@
11add_subdirectory(AccountsService)
22add_subdirectory(LightDM)
3−add_subdirectory(DashViews)
3+add_subdirectory(Dash)
44add_subdirectory(Ubuntu)
55add_subdirectory(Unity)
66add_subdirectory(Utils)
tests/plugins/DashViews/CMakeLists.txt → tests/plugins/Dash/CMakeLists.txtrenamed+16−9
@@ -16,7 +16,7 @@ include_directories(
1616 ${Qt5Quick_INCLUDE_DIRS}
1717 ${Qt5Quick_PRIVATE_INCLUDE_DIRS}
1818 ${Qt5V8_PRIVATE_INCLUDE_DIR}
19− ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/DashViews
19+ ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/Dash
2020 ${CMAKE_CURRENT_BINARY_DIR}
2121 )
2222
@@ -28,11 +28,11 @@ add_definitions(-DBUILT_PLUGINS_DIR="${CMAKE_BINARY_DIR}/plugins")
2828macro(add_lvwph_test FILENAME TESTNAME)
2929 add_executable(${TESTNAME}TestExec
3030 ${FILENAME}test.cpp
31− ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/DashViews/listviewwithpageheader.cpp)
31+ ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/Dash/listviewwithpageheader.cpp)
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} "" "DashViews-qml")
35+ add_binary_qml_test(${TESTNAME} "" "Dash-qml")
3636endmacro()
3737
3838add_lvwph_test(listviewwithpageheader ListViewWithPageHeader)
@@ -42,24 +42,24 @@ add_lvwph_test(listviewwithpageheadersectionexternalmodel ListViewWithPageHeader
4242macro(add_dashview_try_test FILENAME TESTNAME)
4343 add_executable(${TESTNAME}TestExec
4444 ${FILENAME}test.cpp
45− ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/DashViews/${FILENAME}.cpp
46− ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/DashViews/abstractdashview.cpp
45+ ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/Dash/${FILENAME}.cpp
46+ ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/Dash/abstractdashview.cpp
4747 )
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} "" "DashViews-qml")
51+ add_binary_qml_test(${TESTNAME} "" "Dash-qml")
5252
5353 add_executable(${TESTNAME}tryExec
5454 ${FILENAME}try.cpp
55− ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/DashViews/${FILENAME}.cpp
56− ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/DashViews/abstractdashview.cpp
55+ ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/Dash/${FILENAME}.cpp
56+ ${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/Dash/abstractdashview.cpp
5757 )
5858 qt5_use_modules(${TESTNAME}tryExec Test Core Qml)
5959 target_link_libraries(${TESTNAME}tryExec ${Qt5Gui_LIBRARIES} ${Qt5Quick_LIBRARIES})
6060 add_custom_target(try${TESTNAME}
6161 ${CMAKE_CURRENT_BINARY_DIR}/${TESTNAME}tryExec
62− DEPENDS ${TESTNAME}tryExec DashViews-qml
62+ DEPENDS ${TESTNAME}tryExec Dash-qml
6363 )
6464endmacro()
6565
@@ -67,6 +67,13 @@ add_dashview_try_test(verticaljournal VerticalJournal)
6767add_dashview_try_test(horizontaljournal HorizontalJournal)
6868add_dashview_try_test(organicgrid OrganicGrid)
6969
70+# CardCreator test
71+add_executable(CardCreatorTestExec cardcreatortest.cpp)
72+qt5_use_modules(CardCreatorTestExec Test Core Qml)
73+target_link_libraries(CardCreatorTestExec ${Qt5Gui_LIBRARIES} ${Qt5Quick_LIBRARIES})
74+add_binary_qml_test(CardCreator "" "Dash-qml")
75+
76+# plain qml test
7077set(qmltest_DEFAULT_TARGETS qmluitests)
7178set(qmltest_DEFAULT_NO_ADD_TEST TRUE)
7279add_qml_test(. ListViewWithPageHeaderQML IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
tests/plugins/Dash/cardcreator/1.resnew file+77
@@ -0,0 +1,77 @@
1+AbstractButton {
2+ id: root;
3+ property var template;
4+ property var components;
5+ property var cardData;
6+ property real fontScale: 1.0;
7+ property int headerAlignment: Text.AlignLeft;
8+ property int fixedHeaderHeight: -1;
9+ property size fixedArtShapeSize: Qt.size(-1, -1);
10+ readonly property string title: cardData && cardData["title"] || "";
11+ property bool asynchronous: true;
12+ property bool showHeader: true;
13+ implicitWidth: childrenRect.width;
14+readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
15+Item {
16+ id: artShapeHolder;
17+ height: root.fixedArtShapeSize.height != -1 ? root.fixedArtShapeSize.height : artShapeLoader.height;
18+ width: root.fixedArtShapeSize.width != -1 ? root.fixedArtShapeSize.width : artShapeLoader.width;
19+ anchors { horizontalCenter: parent.horizontalCenter; }
20+ Loader {
21+ id: artShapeLoader;
22+ objectName: "artShapeLoader";
23+ active: cardData && cardData["art"] || false;
24+ asynchronous: root.asynchronous;
25+ visible: status == Loader.Ready;
26+ sourceComponent: UbuntuShape {
27+ id: artShape;
28+ objectName: "artShape";
29+ radius: "medium";
30+ readonly property real aspect: components !== undefined ? components["art"]["aspect-ratio"] : 1;
31+ readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect;
32+ Component.onCompleted: updateWidthHeightBindings();
33+ onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings();
34+ visible: image.status == Image.Ready;
35+ function updateWidthHeightBindings() {
36+ if (aspectSmallerThanImageAspect) {
37+ width = Qt.binding(function() { return !visible ? 0 : image.width });
38+ height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect });
39+ } else {
40+ width = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.width : height * image.aspect });
41+ height = Qt.binding(function() { return !visible ? 0 : image.height });
42+ }
43+ }
44+ image: Image {
45+ objectName: "artImage";
46+ source: cardData && cardData["art"] || "";
47+ cache: true;
48+ asynchronous: root.asynchronous;
49+ fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop;
50+ readonly property real aspect: implicitWidth / implicitHeight;
51+ width: root.width;
52+ height: width / artShape.aspect;
53+ }
54+ }
55+ }
56+ }
57+readonly property int headerHeight: titleLabel.height + titleLabel.anchors.topMargin * 2;
58+Label {
59+ id: titleLabel;
60+ objectName: "titleLabel";
61+ anchors { right: parent.right;left: parent.left;
62+top: artShapeHolder.bottom;
63+ topMargin: units.gu(1);
64+ }
65+ elide: Text.ElideRight;
66+ fontSize: "small";
67+ wrapMode: Text.Wrap;
68+ maximumLineCount: 2;
69+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
70+ color: "grey";
71+ visible: showHeader ;
72+ text: root.title;
73+ font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
74+ horizontalAlignment: root.headerAlignment;
75+ }
76+implicitHeight: titleLabel.y + titleLabel.height + units.gu(1);
77+}
tests/plugins/Dash/cardcreator/1.tstnew file+3
@@ -0,0 +1,3 @@
1+template: {"card-layout":"vertical","card-size":"small","category-layout":"grid","collapsed-rows":2}
2+components: {"art":{"aspect-ratio":1.6,"field":"art","fill-mode":"fit"},"title":{"field":"title"}}
3+result: 1.res
tests/plugins/Dash/cardcreator/2.resnew file+115
@@ -0,0 +1,115 @@
1+AbstractButton {
2+ id: root;
3+ property var template;
4+ property var components;
5+ property var cardData;
6+ property real fontScale: 1.0;
7+ property int headerAlignment: Text.AlignLeft;
8+ property int fixedHeaderHeight: -1;
9+ property size fixedArtShapeSize: Qt.size(-1, -1);
10+ readonly property string title: cardData && cardData["title"] || "";
11+ property bool asynchronous: true;
12+ property bool showHeader: true;
13+ implicitWidth: childrenRect.width;
14+Loader {
15+ id: backgroundLoader;
16+ objectName: "backgroundLoader";
17+ anchors.fill: parent;
18+ asynchronous: root.asynchronous;
19+ visible: status == Loader.Ready;
20+ sourceComponent: UbuntuShape {
21+ objectName: "background";
22+ radius: "medium";
23+ color: getColor(0) || "white";
24+ gradientColor: getColor(1) || color;
25+ anchors.fill: parent;
26+ image: backgroundImage.source ? backgroundImage : null;
27+ property real luminance: 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
28+ property Image backgroundImage: Image {
29+ objectName: "backgroundImage";
30+ source: {
31+ if (cardData && typeof cardData["background"] === "string") return cardData["background"];
32+ else if (template && typeof template["card-background"] === "string") return template["card-background"];
33+ else return "";
34+ }
35+ }
36+ function getColor(index) {
37+ if (cardData && typeof cardData["background"] === "object"
38+ && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
39+ return cardData["background"]["elements"][index];
40+ } else if (template && typeof template["card-background"] === "object"
41+ && (template["card-background"]["type"] === "color" || template["card-background"]["type"] === "gradient")) {
42+ return template["card-background"]["elements"][index];
43+ } else return undefined;
44+ }
45+ }
46+ }
47+readonly property size artShapeSize: Qt.size(-1, -1);
48+readonly property int headerHeight: row.height + row.margins * 2;
49+Row {
50+ id: row;
51+ objectName: "outerRow";
52+ property real margins: units.gu(1);
53+ spacing: margins;
54+ anchors { top: parent.top;
55+ topMargin: units.gu(1);
56+ left: parent.left;
57+}
58+ anchors.right: parent.right;
59+ anchors.margins: margins;
60+data: [ Image {
61+ id: mascotImage;
62+ objectName: "mascotImage";
63+ anchors { verticalCenter: parent.verticalCenter; }
64+ readonly property int maxSize: Math.max(width, height) * 4;
65+ source: cardData && cardData["mascot"];
66+ width: units.gu(6);
67+ height: units.gu(5.625);
68+ sourceSize { width: maxSize; height: maxSize }
69+ fillMode: Image.PreserveAspectCrop;
70+ horizontalAlignment: Image.AlignHCenter;
71+ verticalAlignment: Image.AlignVCenter;
72+ visible: showHeader;
73+ }
74+,
75+Column {
76+ anchors.verticalCenter: parent.verticalCenter;
77+ spacing: units.dp(2);
78+ width: parent.width - x;
79+ data: [
80+Label {
81+ id: titleLabel;
82+ objectName: "titleLabel";
83+ anchors { left: parent.left; right: parent.right }
84+ elide: Text.ElideRight;
85+ fontSize: "small";
86+ wrapMode: Text.Wrap;
87+ maximumLineCount: 2;
88+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
89+ color: backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : "grey";
90+ visible: showHeader ;
91+ text: root.title;
92+ font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
93+ horizontalAlignment: root.headerAlignment;
94+ }
95+,
96+Label {
97+ id: subtitleLabel;
98+ objectName: "subtitleLabel";
99+ anchors { left: parent.left; right: parent.right }
100+
101+ elide: Text.ElideRight;
102+ fontSize: "small";
103+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
104+ color: backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : "grey";
105+ visible: titleLabel.visible && titleLabel.text;
106+ text: cardData && cardData["subtitle"] || "";
107+ font.weight: Font.Light;
108+ horizontalAlignment: root.headerAlignment;
109+ }
110+]
111+}
112+]
113+}
114+implicitHeight: row.y + row.height + units.gu(1);
115+}
tests/plugins/Dash/cardcreator/2.tstnew file+3
@@ -0,0 +1,3 @@
1+template: {"card-background":{"elements":["#E9E9E9"],"type":"color"},"card-layout":"vertical","card-size":"medium","category-layout":"grid","collapsed-rows":2}
2+components: {"art":{"aspect-ratio":1,"fill-mode":"crop"},"background":{"field":"background"},"mascot":{"field":"icon"},"subtitle":{"field":"author"},"title":{"field":"title"}}
3+result: 2.res
tests/plugins/Dash/cardcreator/3.resnew file+94
@@ -0,0 +1,94 @@
1+AbstractButton {
2+ id: root;
3+ property var template;
4+ property var components;
5+ property var cardData;
6+ property real fontScale: 1.0;
7+ property int headerAlignment: Text.AlignLeft;
8+ property int fixedHeaderHeight: -1;
9+ property size fixedArtShapeSize: Qt.size(-1, -1);
10+ readonly property string title: cardData && cardData["title"] || "";
11+ property bool asynchronous: true;
12+ property bool showHeader: true;
13+ implicitWidth: childrenRect.width;
14+readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
15+Item {
16+ id: artShapeHolder;
17+ height: root.fixedArtShapeSize.height != -1 ? root.fixedArtShapeSize.height : artShapeLoader.height;
18+ width: root.fixedArtShapeSize.width != -1 ? root.fixedArtShapeSize.width : artShapeLoader.width;
19+ anchors { horizontalCenter: parent.horizontalCenter; }
20+ Loader {
21+ id: artShapeLoader;
22+ objectName: "artShapeLoader";
23+ active: cardData && cardData["art"] || false;
24+ asynchronous: root.asynchronous;
25+ visible: status == Loader.Ready;
26+ sourceComponent: UbuntuShape {
27+ id: artShape;
28+ objectName: "artShape";
29+ radius: "medium";
30+ readonly property real aspect: components !== undefined ? components["art"]["aspect-ratio"] : 1;
31+ readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect;
32+ Component.onCompleted: updateWidthHeightBindings();
33+ onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings();
34+ visible: image.status == Image.Ready;
35+ function updateWidthHeightBindings() {
36+ if (aspectSmallerThanImageAspect) {
37+ width = Qt.binding(function() { return !visible ? 0 : image.width });
38+ height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect });
39+ } else {
40+ width = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.width : height * image.aspect });
41+ height = Qt.binding(function() { return !visible ? 0 : image.height });
42+ }
43+ }
44+ image: Image {
45+ objectName: "artImage";
46+ source: cardData && cardData["art"] || "";
47+ cache: true;
48+ asynchronous: root.asynchronous;
49+ fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop;
50+ readonly property real aspect: implicitWidth / implicitHeight;
51+ width: root.width;
52+ height: width / artShape.aspect;
53+ }
54+ }
55+ }
56+ }
57+readonly property int headerHeight: titleLabel.height + titleLabel.anchors.topMargin * 2 + subtitleLabel.height + subtitleLabel.anchors.topMargin;
58+Label {
59+ id: titleLabel;
60+ objectName: "titleLabel";
61+ anchors { right: parent.right;left: parent.left;
62+top: artShapeHolder.bottom;
63+ topMargin: units.gu(1);
64+}
65+ elide: Text.ElideRight;
66+ fontSize: "small";
67+ wrapMode: Text.Wrap;
68+ maximumLineCount: 2;
69+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
70+ color: "grey";
71+ visible: showHeader ;
72+ text: root.title;
73+ font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
74+ horizontalAlignment: root.headerAlignment;
75+ }
76+Label {
77+ id: subtitleLabel;
78+ objectName: "subtitleLabel";
79+ anchors { left: titleLabel.left;
80+ leftMargin: titleLabel.leftMargin;
81+ right: titleLabel.right;
82+ top: titleLabel.bottom;
83+ topMargin: units.dp(2); }
84+ elide: Text.ElideRight;
85+ fontSize: "small";
86+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
87+ color: "grey";
88+ visible: titleLabel.visible && titleLabel.text;
89+ text: cardData && cardData["subtitle"] || "";
90+ font.weight: Font.Light;
91+ horizontalAlignment: root.headerAlignment;
92+ }
93+implicitHeight: subtitleLabel.y + subtitleLabel.height + units.gu(1);
94+}
tests/plugins/Dash/cardcreator/3.tstnew file+3
@@ -0,0 +1,3 @@
1+template: {"card-layout":"vertical","card-size":"small","category-layout":"grid","collapsed-rows":2}
2+components: {"art":{"aspect-ratio":0.75,"field":"art","fill-mode":"crop"},"subtitle":{"field":"price"},"title":{"field":"title"}}
3+result: 3.res
tests/plugins/Dash/cardcreator/4.resnew file+95
@@ -0,0 +1,95 @@
1+AbstractButton {
2+ id: root;
3+ property var template;
4+ property var components;
5+ property var cardData;
6+ property real fontScale: 1.0;
7+ property int headerAlignment: Text.AlignLeft;
8+ property int fixedHeaderHeight: -1;
9+ property size fixedArtShapeSize: Qt.size(-1, -1);
10+ readonly property string title: cardData && cardData["title"] || "";
11+ property bool asynchronous: true;
12+ property bool showHeader: true;
13+ implicitWidth: childrenRect.width;
14+readonly property size artShapeSize: Qt.size(-1, -1);
15+readonly property int headerHeight: row.height + row.margins * 2;
16+Row {
17+ id: row;
18+ objectName: "outerRow";
19+ property real margins: units.gu(1);
20+ spacing: margins;
21+ anchors { top: parent.top;
22+ topMargin: units.gu(1);
23+ left: parent.left;
24+}
25+ anchors.right: parent.right;
26+ anchors.margins: margins;
27+data: [ Loader {
28+ id: mascotShapeLoader;
29+ objectName: "mascotShapeLoader";
30+ asynchronous: root.asynchronous;
31+ active: mascotImage.status === Image.Ready;
32+ visible: showHeader && active && status == Loader.Ready;
33+ width: units.gu(6);
34+ height: units.gu(5.625);
35+ sourceComponent: UbuntuShape { image: mascotImage }
36+ anchors { verticalCenter: parent.verticalCenter; }
37+ }
38+
39+,
40+Image {
41+ id: mascotImage;
42+ objectName: "mascotImage";
43+ anchors { verticalCenter: parent.verticalCenter; }
44+ readonly property int maxSize: Math.max(width, height) * 4;
45+ source: cardData && cardData["mascot"];
46+ width: units.gu(6);
47+ height: units.gu(5.625);
48+ sourceSize { width: maxSize; height: maxSize }
49+ fillMode: Image.PreserveAspectCrop;
50+ horizontalAlignment: Image.AlignHCenter;
51+ verticalAlignment: Image.AlignVCenter;
52+ visible: false;
53+ }
54+
55+,
56+Column {
57+ anchors.verticalCenter: parent.verticalCenter;
58+ spacing: units.dp(2);
59+ width: parent.width - x;
60+data: [ Label {
61+ id: titleLabel;
62+ objectName: "titleLabel";
63+ anchors { left: parent.left; right: parent.right }
64+ elide: Text.ElideRight;
65+ fontSize: "small";
66+ wrapMode: Text.Wrap;
67+ maximumLineCount: 2;
68+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
69+ color: "grey";
70+ visible: showHeader ;
71+ text: root.title;
72+ font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
73+ horizontalAlignment: root.headerAlignment;
74+ }
75+,
76+Label {
77+ id: subtitleLabel;
78+ objectName: "subtitleLabel";
79+ anchors { left: parent.left; right: parent.right }
80+
81+ elide: Text.ElideRight;
82+ fontSize: "small";
83+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
84+ color: "grey";
85+ visible: titleLabel.visible && titleLabel.text;
86+ text: cardData && cardData["subtitle"] || "";
87+ font.weight: Font.Light;
88+ horizontalAlignment: root.headerAlignment;
89+ }
90+]
91+}
92+]
93+}
94+implicitHeight: row.y + row.height + units.gu(1);
95+}
tests/plugins/Dash/cardcreator/4.tstnew file+3
@@ -0,0 +1,3 @@
1+template: {"card-layout":"horizontal","card-size":"large","category-layout":"grid","collapsed-rows":2}
2+components: {"art":{"aspect-ratio":1,"fill-mode":"crop"},"mascot":{"field":"mascot"},"subtitle":{"field":"domain"},"title":{"field":"title"}}
3+result: 4.res
tests/plugins/Dash/cardcreator/5.resnew file+137
@@ -0,0 +1,137 @@
1+AbstractButton {
2+ id: root;
3+ property var template;
4+ property var components;
5+ property var cardData;
6+ property real fontScale: 1.0;
7+ property int headerAlignment: Text.AlignLeft;
8+ property int fixedHeaderHeight: -1;
9+ property size fixedArtShapeSize: Qt.size(-1, -1);
10+ readonly property string title: cardData && cardData["title"] || "";
11+ property bool asynchronous: true;
12+ property bool showHeader: true;
13+ implicitWidth: childrenRect.width;
14+readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
15+Item {
16+ id: artShapeHolder;
17+ height: root.fixedArtShapeSize.height != -1 ? root.fixedArtShapeSize.height : artShapeLoader.height;
18+ width: root.fixedArtShapeSize.width != -1 ? root.fixedArtShapeSize.width : artShapeLoader.width;
19+ anchors { horizontalCenter: parent.horizontalCenter; }
20+ Loader {
21+ id: artShapeLoader;
22+ objectName: "artShapeLoader";
23+ active: cardData && cardData["art"] || false;
24+ asynchronous: root.asynchronous;
25+ visible: status == Loader.Ready;
26+ sourceComponent: UbuntuShape {
27+ id: artShape;
28+ objectName: "artShape";
29+ radius: "medium";
30+ readonly property real aspect: components !== undefined ? components["art"]["aspect-ratio"] : 1;
31+ readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect;
32+ Component.onCompleted: updateWidthHeightBindings();
33+ onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings();
34+ visible: image.status == Image.Ready;
35+ function updateWidthHeightBindings() {
36+ if (aspectSmallerThanImageAspect) {
37+ width = Qt.binding(function() { return !visible ? 0 : image.width });
38+ height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect });
39+ } else {
40+ width = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.width : height * image.aspect });
41+ height = Qt.binding(function() { return !visible ? 0 : image.height });
42+ }
43+ }
44+ image: Image {
45+ objectName: "artImage";
46+ source: cardData && cardData["art"] || "";
47+ cache: true;
48+ asynchronous: root.asynchronous;
49+ fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop;
50+ readonly property real aspect: implicitWidth / implicitHeight;
51+ width: root.width;
52+ height: width / artShape.aspect;
53+ }
54+ }
55+ }
56+ }
57+Loader {
58+ id: overlayLoader;
59+ anchors {
60+ left: artShapeHolder.left;
61+ right: artShapeHolder.right;
62+ bottom: artShapeHolder.bottom;
63+ }
64+ active: artShapeLoader.active && artShapeLoader.item && artShapeLoader.item.image.status === Image.Ready || false;
65+ asynchronous: root.asynchronous;
66+ visible: showHeader && status == Loader.Ready;
67+ sourceComponent: ShaderEffect {
68+ id: overlay;
69+ height: fixedHeaderHeight != -1 ? fixedHeaderHeight : headerHeight;
70+ opacity: 0.6;
71+ property var source: ShaderEffectSource {
72+ id: shaderSource;
73+ sourceItem: artShapeLoader.item;
74+ onVisibleChanged: if (visible) scheduleUpdate();
75+ live: false;
76+ sourceRect: Qt.rect(0, artShapeLoader.height - overlay.height, artShapeLoader.width, overlay.height);
77+ }
78+ vertexShader: "
79+ uniform highp mat4 qt_Matrix;
80+ attribute highp vec4 qt_Vertex;
81+ attribute highp vec2 qt_MultiTexCoord0;
82+ varying highp vec2 coord;
83+ void main() {
84+ coord = qt_MultiTexCoord0;
85+ gl_Position = qt_Matrix * qt_Vertex;
86+ }";
87+ fragmentShader: "
88+ varying highp vec2 coord;
89+ uniform sampler2D source;
90+ uniform lowp float qt_Opacity;
91+ void main() {
92+ lowp vec4 tex = texture2D(source, coord);
93+ gl_FragColor = vec4(0, 0, 0, tex.a) * qt_Opacity;
94+ }";
95+ }
96+ }
97+readonly property int headerHeight: titleLabel.height + titleLabel.anchors.topMargin * 2 + subtitleLabel.height + subtitleLabel.anchors.topMargin;
98+Label {
99+ id: titleLabel;
100+ objectName: "titleLabel";
101+ anchors { left: parent.left;
102+ leftMargin: units.gu(1);
103+ right: parent.right;
104+ top: overlayLoader.top;
105+ topMargin: units.gu(1);
106+}
107+ elide: Text.ElideRight;
108+ fontSize: "small";
109+ wrapMode: Text.Wrap;
110+ maximumLineCount: 2;
111+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
112+ color: "white";
113+ visible: showHeader && overlayLoader.active;
114+ text: root.title;
115+ font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
116+ horizontalAlignment: root.headerAlignment;
117+ }
118+Label {
119+ id: subtitleLabel;
120+ objectName: "subtitleLabel";
121+ anchors { left: titleLabel.left;
122+ leftMargin: titleLabel.leftMargin;
123+ right: titleLabel.right;
124+ top: titleLabel.bottom;
125+ topMargin: units.dp(2);
126+ }
127+ elide: Text.ElideRight;
128+ fontSize: "small";
129+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
130+ color: "white";
131+ visible: titleLabel.visible && titleLabel.text;
132+ text: cardData && cardData["subtitle"] || "";
133+ font.weight: Font.Light;
134+ horizontalAlignment: root.headerAlignment;
135+ }
136+implicitHeight: subtitleLabel.y + subtitleLabel.height + units.gu(1);
137+}
tests/plugins/Dash/cardcreator/5.tstnew file+3
@@ -0,0 +1,3 @@
1+template: {"card-layout":"vertical","card-size":"medium","category-layout":"carousel","collapsed-rows":2,"overlay":true}
2+components: {"art":{"aspect-ratio":1,"field":"art","fill-mode":"crop"},"subtitle":{"field":"artist"},"title":{"field":"title"}}
3+result: 5.res
tests/plugins/Dash/cardcreatortest.cppnew file+91
@@ -0,0 +1,91 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include <QDir>
18+#include <QQmlEngine>
19+#include <QQuickItem>
20+#include <QQuickView>
21+#include <QtTestGui>
22+
23+class CardCreatorTest : public QObject
24+{
25+ Q_OBJECT
26+
27+private Q_SLOTS:
28+
29+ void initTestCase()
30+ {
31+ }
32+
33+ void init()
34+ {
35+ view = new QQuickView();
36+ view->engine()->addImportPath(BUILT_PLUGINS_DIR);
37+ view->setSource(QUrl::fromLocalFile(DASHVIEWSTEST_FOLDER "/cardcreatortest.qml"));
38+ view->show();
39+ QTest::qWaitForWindowExposed(view);
40+ }
41+
42+ void cleanup()
43+ {
44+ delete view;
45+ }
46+
47+ void testKnownCases()
48+ {
49+ const QString templateString("template: ");
50+ const QString componentsString("components: ");
51+ const QString resultString("result: ");
52+
53+ const QString testDirPath = DASHVIEWSTEST_FOLDER "/cardcreator/";
54+ QDir d(testDirPath);
55+ const QStringList testFiles = d.entryList(QStringList() << "*.tst");
56+ foreach(const QString &testFileName, testFiles) {
57+ qDebug() << testFileName;
58+ QFile testFile(testDirPath + testFileName);
59+ QVERIFY(testFile.open(QIODevice::ReadOnly));
60+ QTextStream ts(&testFile);
61+ const QStringList lines = ts.readAll().split("\n");
62+
63+ QVERIFY(lines[0].startsWith(templateString));
64+ QVERIFY(lines[1].startsWith(componentsString));
65+ QVERIFY(lines[2].startsWith(resultString));
66+ const QString templateJSON = lines[0].mid(templateString.length());
67+ const QString componentsJSON = lines[1].mid(componentsString.length());
68+ const QString resultFileName = lines[2].mid(resultString.length());
69+
70+ QVariant cardStringResult;
71+ QMetaObject::invokeMethod(view->rootObject(), "cardString", Q_RETURN_ARG(QVariant, cardStringResult), Q_ARG(QVariant, templateJSON), Q_ARG(QVariant, componentsJSON));
72+
73+ QFile testResultFile(testDirPath + resultFileName);
74+ QVERIFY(testResultFile.open(QIODevice::ReadOnly));
75+ QTextStream ts2(&testResultFile);
76+ const QString expectedResult = ts2.readAll();
77+ QCOMPARE(cardStringResult.toString().simplified(), expectedResult.simplified());
78+
79+ QVariant createCardComponentResult;
80+ QMetaObject::invokeMethod(view->rootObject(), "createCardComponent", Q_RETURN_ARG(QVariant, createCardComponentResult), Q_ARG(QVariant, templateJSON), Q_ARG(QVariant, componentsJSON));
81+ QVERIFY(createCardComponentResult.toBool());
82+ }
83+ }
84+
85+private:
86+ QQuickView *view;
87+};
88+
89+QTEST_MAIN(CardCreatorTest)
90+
91+#include "cardcreatortest.moc"
tests/plugins/Dash/cardcreatortest.qmlnew file+29
@@ -0,0 +1,29 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.2
18+import "../../../plugins/Dash/CardCreator.js" as CardCreator
19+
20+Item {
21+ id: root
22+ function cardString(template, components) {
23+ return CardCreator.cardString(JSON.parse(template), JSON.parse(components));
24+ }
25+
26+ function createCardComponent(template, components) {
27+ return CardCreator.createCardComponent(root, JSON.parse(template), JSON.parse(components)) !== null;
28+ }
29+}
tests/plugins/DashViews/horizontaljournaltest.cpp → tests/plugins/Dash/horizontaljournaltest.cpprenamed

The content is the same.

tests/plugins/DashViews/horizontaljournaltest.qml → tests/plugins/Dash/horizontaljournaltest.qmlrenamed+1−1
@@ -15,7 +15,7 @@
1515 */
1616
1717import QtQuick 2.1
18−import DashViews 0.1
18+import Dash 0.1
1919
2020Item {
2121
tests/plugins/DashViews/horizontaljournaltry.cpp → tests/plugins/Dash/horizontaljournaltry.cpprenamed

The content is the same.

tests/plugins/DashViews/horizontaljournaltry.qml → tests/plugins/Dash/horizontaljournaltry.qmlrenamed+1−1
@@ -15,7 +15,7 @@
1515 */
1616
1717import QtQuick 2.1
18−import DashViews 0.1
18+import Dash 0.1
1919
2020Item {
2121 id: root
tests/plugins/DashViews/listviewwithpageheadersectionexternalmodeltest.cpp → tests/plugins/Dash/listviewwithpageheadersectionexternalmodeltest.cpprenamed−4
@@ -24,11 +24,7 @@
2424#include <QtTestGui>
2525#pragma GCC diagnostic push
2626#pragma GCC diagnostic ignored "-pedantic"
27−#if QT_VERSION < QT_VERSION_CHECK(5, 1, 0)
28−#include <private/qquicklistmodel_p.h>
29−#else
3027#include <private/qqmllistmodel_p.h>
31−#endif
3228#include <private/qquickanimation_p.h>
3329#include <private/qquickitem_p.h>
3430#pragma GCC diagnostic pop
tests/plugins/DashViews/listviewwithpageheadersectiontest.cpp → tests/plugins/Dash/listviewwithpageheadersectiontest.cpprenamed−12
@@ -23,11 +23,7 @@
2323#include <QtTestGui>
2424#pragma GCC diagnostic push
2525#pragma GCC diagnostic ignored "-pedantic"
26−#if QT_VERSION < QT_VERSION_CHECK(5, 1, 0)
27−#include <private/qquicklistmodel_p.h>
28−#else
2926#include <private/qqmllistmodel_p.h>
30−#endif
3127#include <private/qquickanimation_p.h>
3228#include <private/qquickitem_p.h>
3329#pragma GCC diagnostic pop
@@ -135,11 +131,7 @@ private Q_SLOTS:
135131 view->engine()->addImportPath(BUILT_PLUGINS_DIR);
136132 view->setSource(QUrl::fromLocalFile(DASHVIEWSTEST_FOLDER "/listviewwithpageheadertestsection.qml"));
137133 lvwph = dynamic_cast<ListViewWithPageHeader*>(view->rootObject()->findChild<QQuickFlickable*>());
138−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
139− model = view->rootObject()->findChild<QQuickListModel*>();
140−#else
141134 model = view->rootObject()->findChild<QQmlListModel*>();
142−#endif
143135 otherDelegate = view->rootObject()->findChild<QQmlComponent*>();
144136 QVERIFY(lvwph);
145137 QVERIFY(model);
@@ -2156,11 +2148,7 @@ private Q_SLOTS:
21562148private:
21572149 QQuickView *view;
21582150 ListViewWithPageHeader *lvwph;
2159−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
2160− QQuickListModel *model;
2161−#else
21622151 QQmlListModel *model;
2163−#endif
21642152 QQmlComponent *otherDelegate;
21652153};
21662154
tests/plugins/DashViews/listviewwithpageheadertest.cpp → tests/plugins/Dash/listviewwithpageheadertest.cpprenamed−15
@@ -23,11 +23,7 @@
2323#include <QtTestGui>
2424#pragma GCC diagnostic push
2525#pragma GCC diagnostic ignored "-pedantic"
26−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
27−#include <private/qquicklistmodel_p.h>
28−#else
2926#include <private/qqmllistmodel_p.h>
30−#endif
3127#include <private/qquickanimation_p.h>
3228#include <private/qquickitem_p.h>
3329#pragma GCC diagnostic pop
@@ -124,11 +120,7 @@ private Q_SLOTS:
124120 view->engine()->addImportPath(BUILT_PLUGINS_DIR);
125121 view->setSource(QUrl::fromLocalFile(DASHVIEWSTEST_FOLDER "/listviewwithpageheadertest.qml"));
126122 lvwph = dynamic_cast<ListViewWithPageHeader*>(view->rootObject()->findChild<QQuickFlickable*>());
127−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
128− model = view->rootObject()->findChild<QQuickListModel*>();
129−#else
130123 model = view->rootObject()->findChild<QQmlListModel*>();
131−#endif
132124 otherDelegate = view->rootObject()->findChild<QQmlComponent*>();
133125 QVERIFY(lvwph);
134126 QVERIFY(model);
@@ -1747,9 +1739,6 @@ private Q_SLOTS:
17471739
17481740 void testMaximizeVisibleAreaMoveUpAndShowHeader()
17491741 {
1750−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
1751− QSKIP("This test is extremely unstable in 5.0.x");
1752−#endif
17531742 model->setProperty(0, "size", 800);
17541743 verifyItem(0, 50., 800., false);
17551744
@@ -1913,11 +1902,7 @@ private Q_SLOTS:
19131902private:
19141903 QQuickView *view;
19151904 ListViewWithPageHeader *lvwph;
1916−#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
1917− QQuickListModel *model;
1918−#else
19191905 QQmlListModel *model;
1920−#endif
19211906 QQmlComponent *otherDelegate;
19221907};
19231908
tests/plugins/DashViews/listviewwithpageheadertest.qml → tests/plugins/Dash/listviewwithpageheadertest.qmlrenamed+1−1
@@ -15,7 +15,7 @@
1515 */
1616
1717import QtQuick 2.0
18−import DashViews 0.1
18+import Dash 0.1
1919
2020Rectangle {
2121 width: 300
tests/plugins/DashViews/listviewwithpageheadertestsection.qml → tests/plugins/Dash/listviewwithpageheadertestsection.qmlrenamed+1−1
@@ -15,7 +15,7 @@
1515 */
1616
1717import QtQuick 2.0
18−import DashViews 0.1
18+import Dash 0.1
1919
2020Rectangle {
2121 width: 300
tests/plugins/DashViews/listviewwithpageheadertestsectionexternalmodel.qml → tests/plugins/Dash/listviewwithpageheadertestsectionexternalmodel.qmlrenamed+1−1
@@ -15,7 +15,7 @@
1515 */
1616
1717import QtQuick 2.0
18−import DashViews 0.1
18+import Dash 0.1
1919
2020Rectangle {
2121 width: 300
tests/plugins/DashViews/organicgridtest.cpp → tests/plugins/Dash/organicgridtest.cpprenamed

The content is the same.

tests/plugins/DashViews/organicgridtest.qml → tests/plugins/Dash/organicgridtest.qmlrenamed+1−1
@@ -15,7 +15,7 @@
1515 */
1616
1717import QtQuick 2.1
18−import DashViews 0.1
18+import Dash 0.1
1919
2020Item {
2121
tests/plugins/DashViews/organicgridtry.cpp → tests/plugins/Dash/organicgridtry.cpprenamed

The content is the same.

tests/plugins/DashViews/organicgridtry.qml → tests/plugins/Dash/organicgridtry.qmlrenamed+1−1
@@ -15,7 +15,7 @@
1515 */
1616
1717import QtQuick 2.1
18−import DashViews 0.1
18+import Dash 0.1
1919
2020Item {
2121 id: root
tests/plugins/DashViews/tst_ListViewWithPageHeaderQML.qml → tests/plugins/Dash/tst_ListViewWithPageHeaderQML.qmlrenamed+1−1
@@ -16,7 +16,7 @@
1616
1717import QtQuick 2.0
1818import QtTest 1.0
19−import DashViews 0.1
19+import Dash 0.1
2020
2121Rectangle {
2222 width: 300
tests/plugins/DashViews/verticaljournaltest.cpp → tests/plugins/Dash/verticaljournaltest.cpprenamed

The content is the same.

tests/plugins/DashViews/verticaljournaltest.qml → tests/plugins/Dash/verticaljournaltest.qmlrenamed+1−1
@@ -15,7 +15,7 @@
1515 */
1616
1717import QtQuick 2.1
18−import DashViews 0.1
18+import Dash 0.1
1919
2020Item {
2121
tests/plugins/DashViews/verticaljournaltry.cpp → tests/plugins/Dash/verticaljournaltry.cpprenamed

The content is the same.

tests/plugins/DashViews/verticaljournaltry.qml → tests/plugins/Dash/verticaljournaltry.qmlrenamed+1−1
@@ -15,7 +15,7 @@
1515 */
1616
1717import QtQuick 2.1
18−import DashViews 0.1
18+import Dash 0.1
1919
2020Item {
2121 id: root
tests/plugins/Unity/Launcher/CMakeLists.txt+3−3
@@ -15,9 +15,9 @@ add_definitions(-DSRCDIR="${CMAKE_CURRENT_SOURCE_DIR}")
1515add_definitions(-DLAUNCHER_TESTING)
1616
1717### LauncherBackendTest
18−set(testBackendCommand ${CMAKE_CURRENT_BINARY_DIR}/launcherbackendtestExec
19− -o ${CMAKE_BINARY_DIR}/launcherbackendtest.xml,xunitxml
20− -o -,txt)
18+set(testBackendCommand dbus-test-runner --task ${CMAKE_CURRENT_BINARY_DIR}/launcherbackendtestExec
19+ --parameter -o --parameter ${CMAKE_BINARY_DIR}/launcherbackendtest.xml,xunitxml
20+ --parameter -o --parameter -,txt)
2121add_test(NAME launcherbackendtest COMMAND ${testBackendCommand})
2222add_custom_target(launcherbackendtest ${testBackendCommand})
2323add_executable(launcherbackendtestExec
tests/plugins/Unity/Launcher/launcherbackendtest.cpp+167
@@ -20,6 +20,10 @@
2020#include "launcherbackend.h"
2121
2222#include <QtTest>
23+#include <QDBusConnection>
24+#include <QDBusMessage>
25+#include <QDBusVariant>
26+
2327
2428class LauncherBackendTest : public QObject
2529{
@@ -66,6 +70,169 @@ private Q_SLOTS:
6670
6771 QCOMPARE(backend.icon(appId), expectedIcon);
6872 }
73+
74+ void testGetItem_data() {
75+ QTest::addColumn<QString>("appId");
76+ QTest::addColumn<bool>("exists");
77+
78+ QTest::newRow("Exists") << "rel-icon" << true;
79+ QTest::newRow("Doesn't Exist") << "does-not-exist" << false;
80+ }
81+
82+ void testGetItem() {
83+ QFETCH(QString, appId);
84+ QFETCH(bool, exists);
85+
86+ LauncherBackend backend;
87+ auto item = backend.getItem(appId);
88+
89+ if (exists) {
90+ QVERIFY(item != nullptr);
91+ } else {
92+ QVERIFY(item == nullptr);
93+ }
94+ }
95+
96+ void testCount_data() {
97+ QTest::addColumn<QString>("appId");
98+ QTest::addColumn<bool>("setCount");
99+ QTest::addColumn<int>("inCount");
100+ QTest::addColumn<bool>("countVisible");
101+ QTest::addColumn<int>("expectedCount");
102+
103+ /* Get baseline data on things working */
104+ QTest::newRow("Baseline") << "rel-icon" << false << 0 << false << -1;
105+
106+ /* Valid count, but not visible */
107+ QTest::newRow("Not visible") << "rel-icon" << true << 42 << false << -1;
108+
109+ /* Turn it on */
110+ QTest::newRow("Visible Count") << "rel-icon" << true << 42 << true << 42;
111+
112+ /* Invalide app to load */
113+ QTest::newRow("Invalid App ID") << "this-app-doesnt-exist" << true << 42 << true << -1;
114+ }
115+
116+ void testCount() {
117+ QFETCH(QString, appId);
118+ QFETCH(bool, setCount);
119+ QFETCH(int, inCount);
120+ QFETCH(bool, countVisible);
121+ QFETCH(int, expectedCount);
122+
123+ LauncherBackend backend;
124+
125+ if (setCount)
126+ backend.setCount(appId, inCount);
127+ backend.setCountVisible(appId, countVisible);
128+
129+ QCOMPARE(backend.count(appId), expectedCount);
130+ }
131+
132+ void testDbusName_data() {
133+ QTest::addColumn<QString>("decoded");
134+ QTest::addColumn<QString>("encoded");
135+
136+ /* Passthrough test */
137+ QTest::newRow("Passthrough") << "fine" << "fine";
138+
139+ /* Number as first characeter */
140+ QTest::newRow("Number first") << "31337" << "_331337";
141+
142+ /* Underscore test */
143+ QTest::newRow("Underscore test") << "this_is_c_style_namespacing" << "this_5Fis_5Fc_5Fstyle_5Fnamespacing";
144+
145+ /* Hyphen test */
146+ QTest::newRow("Hyphen test") << "typical-application" << "typical_2Dapplication";
147+
148+ /* Japanese test */
149+ QTest::newRow("日本語 test") << "日本語" << "_E6_97_A5_E6_9C_AC_E8_AA_9E";
150+ }
151+
152+ void testDbusName() {
153+ QFETCH(QString, decoded);
154+ QFETCH(QString, encoded);
155+
156+ QString encodeOut = LauncherBackend::encodeAppId(decoded);
157+ QCOMPARE(encoded, encodeOut);
158+
159+ QString decodeOut = LauncherBackend::decodeAppId(encoded);
160+ QCOMPARE(decoded, decodeOut);
161+ }
162+
163+ void testDbusIface_data() {
164+ QTest::addColumn<QString>("appId");
165+ QTest::addColumn<bool>("setCount");
166+ QTest::addColumn<int>("inCount");
167+ QTest::addColumn<bool>("countVisible");
168+ QTest::addColumn<int>("expectedCount");
169+
170+ /* Get baseline data on things working */
171+ QTest::newRow("Baseline") << "rel-icon" << false << 0 << false << -1;
172+
173+ /* Turn it on */
174+ QTest::newRow("Visible Count") << "rel-icon" << true << 42 << true << 42;
175+
176+ /* Invalide app to load */
177+ QTest::newRow("Invalid App ID") << "this-app-doesnt-exist" << true << 42 << true << -1;
178+ }
179+
180+ void testDbusIface() {
181+ QFETCH(QString, appId);
182+ QFETCH(bool, setCount);
183+ QFETCH(int, inCount);
184+ QFETCH(bool, countVisible);
185+ QFETCH(int, expectedCount);
186+
187+ QDBusConnection con = QDBusConnection::sessionBus();
188+ QDBusMessage message;
189+ QDBusMessage reply;
190+
191+ LauncherBackend backend;
192+
193+ if (setCount) {
194+ message = QDBusMessage::createMethodCall("com.canonical.Unity.Launcher",
195+ "/com/canonical/Unity/Launcher/" + LauncherBackend::encodeAppId(appId),
196+ "org.freedesktop.DBus.Properties",
197+ "Set");
198+ QVariantList cargs;
199+ cargs.append(QString("com.canonical.Unity.Launcher.Item"));
200+ cargs.append(QString("count"));
201+ cargs.append(QVariant::fromValue(QDBusVariant(inCount)));
202+
203+ message.setArguments(cargs);
204+ reply = con.call(message);
205+ QCOMPARE(reply.type(), QDBusMessage::ReplyMessage);
206+ }
207+
208+ /* Set countVisible */
209+ message = QDBusMessage::createMethodCall("com.canonical.Unity.Launcher",
210+ "/com/canonical/Unity/Launcher/" + LauncherBackend::encodeAppId(appId),
211+ "org.freedesktop.DBus.Properties",
212+ "Set");
213+ QVariantList cvargs;
214+ cvargs.append(QString("com.canonical.Unity.Launcher.Item"));
215+ cvargs.append(QString("countVisible"));
216+ cvargs.append(QVariant::fromValue(QDBusVariant(countVisible)));
217+
218+ message.setArguments(cvargs);
219+ reply = con.call(message);
220+ QCOMPARE(reply.type(), QDBusMessage::ReplyMessage);
221+
222+ /* Get value */
223+ message = QDBusMessage::createMethodCall("com.canonical.Unity.Launcher",
224+ "/com/canonical/Unity/Launcher/" + LauncherBackend::encodeAppId(appId),
225+ "org.freedesktop.DBus.Properties",
226+ "Get");
227+ QVariantList getargs;
228+ getargs.append(QString("com.canonical.Unity.Launcher.Item"));
229+ getargs.append(QString("count"));
230+
231+ message.setArguments(getargs);
232+ reply = con.call(message);
233+ QCOMPARE(reply.type(), QDBusMessage::ReplyMessage);
234+ QCOMPARE(reply.arguments()[0].value<QDBusVariant>().variant().toInt(), expectedCount);
235+ }
69236};
70237
71238QTEST_GUILESS_MAIN(LauncherBackendTest)
tests/qmltests/CMakeLists.txt+4−3
@@ -32,12 +32,13 @@ add_qml_test(Components Revealer)
3232add_qml_test(Components SeeMore)
3333add_qml_test(Components Showable)
3434add_qml_test(Components PageHeaderLabel)
35+add_qml_test(Components ZoomableImage)
3536add_qml_test(Dash Dash IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
3637add_qml_test(Dash DashContent IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
37−add_qml_test(Dash Card)
38−add_qml_benchmark(Dash CardBenchmark 1000)
38+add_qml_test(Dash Card IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
39+add_qml_benchmark(Dash CardBenchmark 1000 IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
3940add_qml_test(Dash CardHeader)
40−add_qml_test(Dash CardTool)
41+add_qml_test(Dash CardTool IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
4142add_qml_test(Dash GenericScopeView IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
4243add_qml_test(Dash/Apps RunningApplicationsGrid IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS}
4344 ${CMAKE_BINARY_DIR}/tests/mocks
tests/qmltests/Components/tst_ZoomableImage.qmlnew file+192
@@ -0,0 +1,192 @@
1+/*
2+ * Copyright 2014 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import QtTest 1.0
19+import "../../../qml/Components"
20+import Unity.Test 0.1 as UT
21+
22+Rectangle {
23+ id: root
24+ width: units.gu(30)
25+ height: units.gu(60)
26+ color: "lightgrey"
27+
28+ property var widgetData0: {
29+ "source": "",
30+ "zoomable": false
31+ }
32+
33+ property var widgetData1: {
34+ "source": "../../../qml/graphics/phone_background.jpg",
35+ "zoomable": false
36+ }
37+
38+ property var widgetData2: {
39+ "source": "../../../qml/Dash/graphics/phone/screenshots/gallery@12.png",
40+ "zoomable": true
41+ }
42+
43+ ZoomableImage {
44+ id: zoomableImage
45+ width: parent.width
46+ anchors.fill: parent
47+ asynchronous: false
48+ }
49+
50+ SignalSpy {
51+ id: signalSpy
52+ }
53+
54+ UT.UnityTestCase {
55+ name: "ZoomableImageTest"
56+ when: windowShown
57+
58+ function test_loadImage() {
59+ var lazyImage = findChild(zoomableImage, "lazyImage");
60+
61+ zoomableImage.source = widgetData0["source"];
62+ zoomableImage.zoomable = widgetData0["zoomable"];
63+ waitForRendering(zoomableImage);
64+ tryCompare(zoomableImage, "imageState", "default");
65+
66+ signalSpy.signalName = "onStateChanged";
67+ signalSpy.target = lazyImage;
68+ signalSpy.clear();
69+
70+ zoomableImage.source = widgetData1["source"];
71+ zoomableImage.zoomable = widgetData1["zoomable"];
72+ waitForRendering(lazyImage);
73+ tryCompareFunction(function() { return get_filename(lazyImage.source.toString()) === get_filename(widgetData1["source"]); }, true);
74+ waitForRendering(zoomableImage);
75+ tryCompare(zoomableImage, "imageState", "ready");
76+ compare (signalSpy.count, 1);
77+ }
78+
79+ function get_filename(a) {
80+ var wordsA = a.split("/");
81+ var filenameA = wordsA[wordsA.length-1];
82+ return filenameA;
83+ }
84+
85+ function test_mousewheel() {
86+ var image = findChild(zoomableImage, "image");
87+ var lazyImage = findChild(zoomableImage, "lazyImage");
88+ var flickable = findChild(zoomableImage, "flickable");
89+
90+ zoomableImage.source = widgetData2["source"];
91+ zoomableImage.zoomable = true;
92+ waitForRendering(zoomableImage);
93+
94+ tryCompare(zoomableImage, "imageState", "ready");
95+ tryCompareFunction(function() { return get_filename(lazyImage.source.toString()) === get_filename(widgetData2["source"]); }, true);
96+ waitForRendering(image);
97+
98+ // move mouse to center
99+ mouseMove(zoomableImage, zoomableImage.width / 2, zoomableImage.height / 2);
100+
101+ // Test Zoom-in Zoom-out twice.
102+ for (var c=0; c<2; c++) {
103+ // zoom in
104+ for (var i=0; i<10; i++) {
105+ mouseWheel(zoomableImage, zoomableImage.width / 2, zoomableImage.height / 2, 0, 10);
106+ tryCompare(image, "scale", 1.0 + (i + 1) * 0.1);
107+ compare(flickable.contentWidth, lazyImage.width * image.scale);
108+ compare(flickable.contentHeight, lazyImage.height * image.scale);
109+ }
110+
111+ // zoom out
112+ for (var i=0; i<10; i++) {
113+ mouseWheel(zoomableImage, zoomableImage.width / 2, zoomableImage.height / 2, 0, -10);
114+ tryCompare(image, "scale", 2.0 - (i + 1) * 0.1);
115+ compare(flickable.contentWidth, lazyImage.width * image.scale);
116+ compare(flickable.contentHeight, lazyImage.height * image.scale);
117+ }
118+ }
119+ }
120+
121+ function test_pinch_data() {
122+ return [ { source:widgetData2["source"],
123+ zoomable:false,
124+ answer1: true,
125+ answer2: false,
126+ answer3: true,
127+ answer4: 1.0,
128+ answer5: 1.0 },
129+ { source:widgetData2["source"],
130+ zoomable:true,
131+ answer1: false,
132+ answer2: true,
133+ answer3: false,
134+ answer4: 1.7740461882048026,
135+ answer5: 1.0 }
136+ ]
137+ }
138+
139+ function test_pinch(data) {
140+ var image = findChild(zoomableImage, "image");
141+ var lazyImage = findChild(zoomableImage, "lazyImage");
142+ var flickable = findChild(zoomableImage, "flickable");
143+
144+ signalSpy.signalName = "onScaleChanged";
145+ signalSpy.target = image;
146+ signalSpy.clear();
147+
148+ zoomableImage.source = data.source;
149+ zoomableImage.zoomable = data.zoomable;
150+ waitForRendering(zoomableImage);
151+
152+ tryCompare(zoomableImage, "imageState", "ready");
153+ tryCompareFunction(function() { return get_filename(lazyImage.source.toString()) === get_filename(data.source); }, true);
154+ waitForRendering(image);
155+
156+ var x1Start = zoomableImage.width * 2 / 6;
157+ var y1Start = zoomableImage.height * 2 / 6;
158+ var x1End = zoomableImage.width * 1 / 6;
159+ var y1End = zoomableImage.height * 1 / 6;
160+ var x2Start = zoomableImage.width * 4 / 6;
161+ var y2Start = zoomableImage.height * 4 / 6;
162+ var x2End = zoomableImage.width * 5 / 6;
163+ var y2End = zoomableImage.height * 5 / 6;
164+
165+ var oldScale = image.scale;
166+
167+ // move mouse to center
168+ mouseMove(zoomableImage, zoomableImage.width / 2, zoomableImage.height / 2);
169+
170+ // Test Zoom-in Zoom-out twice.
171+ for (var c=0; c<2; c++) {
172+ wait(3000); // have to delay between two consequent pinch event.
173+ // pinch zoom-in
174+ touchPinch(zoomableImage, x1Start, y1Start, x1End, y1End, x2Start, y2Start, x2End, y2End);
175+
176+ tryCompare(image, "scale", data.answer4);
177+ var newScale = image.scale;
178+ compare(newScale == oldScale, data.answer1, "scale factor not equal: "+ oldScale + "=?" + newScale);
179+ compare(newScale > oldScale, data.answer2, "scale factor didn't changed");
180+ compare(signalSpy.count == 0, data.answer3, "scale signal count error");
181+ compare(newScale, data.answer4, "scale factor error");
182+ compare(flickable.contentWidth, lazyImage.width * image.scale);
183+ compare(flickable.contentHeight, lazyImage.height * image.scale);
184+
185+ wait(3000); // have to delay between two consequent pinch event.
186+ // pinch zoom-out
187+ touchPinch(zoomableImage, x1End, y1End, x1Start, y1Start, x2End, y2End, x2Start, y2Start);
188+ tryCompare(image, "scale", data.answer5);
189+ }
190+ }
191+ }
192+}
tests/qmltests/Dash/Previews/tst_Preview.qml+1−1
@@ -23,7 +23,7 @@ import Unity 0.2 as Unity
2323Rectangle {
2424 id: root
2525 width: units.gu(60)
26− height: units.gu(80)
26+ height: units.gu(60)
2727 color: Theme.palette.selected.background
2828
2929 Item {
tests/qmltests/Dash/Previews/tst_PreviewHeader.qml+66−4
@@ -24,6 +24,12 @@ Rectangle {
2424 width: units.gu(60)
2525 height: units.gu(80)
2626
27+ property var origHeaderjson: {
28+ "title": "THE TITLE",
29+ "subtitle": "Something catchy",
30+ "mascot": "../graphics/play_button.png"
31+ }
32+
2733 property var headerjson: {
2834 "title": "THE TITLE",
2935 "subtitle": "Something catchy",
@@ -43,14 +49,70 @@ Rectangle {
4349 }
4450
4551 UT.UnityTestCase {
52+ id: testCase
4653 name: "PreviewHeaderTest"
4754 when: windowShown
4855
56+ property Item mascot: findChild(previewHeader, "mascotShape")
57+ property Item outerRow: findChild(previewHeader, "outerRow")
58+ property Item column: findChild(previewHeader, "column")
59+
60+ function initTestCase() {
61+ verify(typeof testCase.mascot === "object", "Couldn't find mascot object.");
62+ verify(typeof testCase.outerRow === "object", "Couldn't find outerRow object.");
63+ verify(typeof testCase.column === "object", "Couldn't find column object.");
64+
65+ headerjson.mascot = "";
66+ headerjson.title = "";
67+ headerjson.subtitle = "";
68+ previewHeader.widgetData = headerjson;
69+ }
70+
71+ function test_mascot_data() {
72+ return [
73+ { tag: "Empty", source: "", visible: false },
74+ { tag: "Invalid", source: "bad_path", visible: false },
75+ { tag: "Valid", source: "../graphics/play_button.png", visible: true },
76+ ]
77+ }
78+
79+ function test_mascot(data) {
80+ headerjson.mascot = data.source;
81+ previewHeader.widgetData = headerjson;
82+
83+ tryCompare(testCase.mascot, "visible", data.visible);
84+ }
85+
86+ function test_dimensions_data() {
87+ return [
88+ { tag: "Column width with mascot", object: column, width: previewHeader.width - mascot.width - outerRow.margins * 3, mascot: "artwork/avatar.png" },
89+ { tag: "Header height", object: previewHeader, height: function() { return outerRow.height + outerRow.margins * 2 } },
90+ ]
91+ }
92+
93+ function test_dimensions(data) {
94+ if (data.hasOwnProperty("mascot")) {
95+ headerjson.mascot = data.mascot;
96+ }
97+ previewHeader.widgetData = headerjson;
98+
99+ if (data.hasOwnProperty("width")) {
100+ tryCompare(data.object, "width", data.width);
101+ }
102+
103+ if (data.hasOwnProperty("height")) {
104+ tryCompareFunction(function() { return data.object.height === data.height() }, true);
105+ }
106+ }
107+
49108 function test_json() {
50− var cardHeader = findChild(previewHeader, "cardHeader");
51− compare(cardHeader.title, "THE TITLE");
52− compare(cardHeader.subtitle, "Something catchy");
53− compare(cardHeader.mascot.toString().slice(-24), "graphics/play_button.png");
109+ headerjson = origHeaderjson;
110+ previewHeader.widgetData = headerjson;
111+
112+ var innerPreviewHeader = findChild(previewHeader, "innerPreviewHeader");
113+ compare(innerPreviewHeader.title, "THE TITLE");
114+ compare(innerPreviewHeader.subtitle, "Something catchy");
115+ compare(innerPreviewHeader.mascot.toString().slice(-24), "graphics/play_button.png");
54116 }
55117 }
56118}
tests/qmltests/Dash/Previews/tst_PreviewTextSummary.qml+5−5
@@ -42,7 +42,7 @@ Rectangle {
4242
4343 PreviewTextSummary {
4444 id: previewTextSummary
45− anchors.fill: parent
45+ anchors { left: parent.left; right: parent.right }
4646 widgetData: widgetDataComplete
4747 }
4848
@@ -51,6 +51,7 @@ Rectangle {
5151 when: windowShown
5252
5353 property var textLabel: findChild(previewTextSummary, "textLabel")
54+ property var titleLabel: findChild(previewTextSummary, "titleLabel")
5455
5556 function init() {
5657 verify(typeof textLabel === "object", "TextLabel object could not be found.")
@@ -61,16 +62,14 @@ Rectangle {
6162 }
6263
6364 function test_optional_title() {
64− var titleLabel = findChild(previewTextSummary, "titleLabel")
65−
6665 // verify titleLabel is visible and textLabel is anchored below it
6766 compare(titleLabel.visible, true)
68− compare(textLabel.y, titleLabel.height)
67+ tryCompare(textLabel, "y", titleLabel.height)
6968
7069 // verify titleLabel disappears and textLabel moves up
7170 previewTextSummary.widgetData = widgetDataNoTitle
7271 compare(titleLabel.visible, false)
73− compare(textLabel.y, 0)
72+ tryCompare(textLabel, "y", 0)
7473 }
7574
7675 function test_see_more() {
@@ -96,6 +95,7 @@ Rectangle {
9695 verify(textLabel.lineCount <= textLabel.maximumCollapsedLineCount)
9796 compare(seeMore.visible, false)
9897 tryCompare(textLabel, "height", textLabel.contentHeight)
98+ tryCompare(previewTextSummary, "height", titleLabel.height + textLabel.height)
9999 }
100100 }
101101}
tests/qmltests/Dash/Previews/tst_PreviewZoomableImage.qml+6−3
@@ -36,7 +36,8 @@ Rectangle {
3636
3737 PreviewZoomableImage {
3838 id: zoomableImage
39− width: parent.width
39+ anchors.fill: parent
40+ widgetData: widgetData1
4041 }
4142
4243 UT.UnityTestCase {
@@ -47,10 +48,12 @@ Rectangle {
4748 var image = findChild(zoomableImage, "image");
4849
4950 zoomableImage.widgetData = widgetData0;
50− tryCompare(image, "state", "default");
51+ waitForRendering(zoomableImage);
52+ tryCompare(image, "imageState", "default");
5153
5254 zoomableImage.widgetData = widgetData1;
53− tryCompare(image, "state", "ready");
55+ waitForRendering(zoomableImage);
56+ tryCompare(image, "imageState", "ready");
5457 }
5558 }
5659}
tests/qmltests/Dash/tst_Card.qml+78−92
@@ -29,8 +29,8 @@ Rectangle {
2929
3030 property string cardData: '
3131 {
32− "art": "../../tests/qmltests/Dash/artwork/music-player-design.png",
33− "mascot": "../../tests/qmltests/Dash/artwork/avatar.png",
32+ "art": "../../../tests/qmltests/Dash/artwork/music-player-design.png",
33+ "mascot": "../../../tests/qmltests/Dash/artwork/avatar.png",
3434 "title": "foo",
3535 "subtitle": "bar",
3636 "summary": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
@@ -98,21 +98,26 @@ Rectangle {
9898
9999 CardTool {
100100 id: cardTool
101− template: card.template
102− components: card.components
101+ template: Helpers.update(JSON.parse(Helpers.defaultLayout), Helpers.tryParse(layoutArea.text, layoutError))['template'];
102+ components: Helpers.update(JSON.parse(Helpers.defaultLayout), Helpers.tryParse(layoutArea.text, layoutError))['components'];
103103 viewWidth: units.gu(48)
104104 }
105105
106− Card {
107− id: card
108− anchors { top: parent.top; left: parent.left; margins: units.gu(1) }
106+ readonly property var card: loader.item
109107
110− width: cardTool.cardWidth || implicitWidth
111− height: cardTool.cardHeight || implicitHeight
108+ Loader {
109+ id: loader
110+ anchors { top: parent.top; left: parent.left; margins: units.gu(1) }
112111
113− template: Helpers.update(JSON.parse(Helpers.defaultLayout), Helpers.tryParse(layoutArea.text, layoutError))['template'];
114− components: Helpers.update(JSON.parse(Helpers.defaultLayout), Helpers.tryParse(layoutArea.text, layoutError))['components'];
115− cardData: Helpers.mapData(dataArea.text, components, dataError)
112+ sourceComponent: cardTool.cardComponent
113+ onLoaded: {
114+ item.template = Qt.binding(function() { return cardTool.template; });
115+ item.components = Qt.binding(function() { return cardTool.components; });
116+ item.cardData = Qt.binding(function() { return Helpers.mapData(dataArea.text, cardTool.components, dataError); });
117+ item.width = Qt.binding(function() { return cardTool.cardWidth || item.implicitWidth; });
118+ item.height = Qt.binding(function() { return cardTool.cardHeight || item.implicitHeight; });
119+ item.fixedHeaderHeight = Qt.binding(function() { return cardTool.headerHeight; });
120+ }
116121 }
117122
118123 Rectangle {
@@ -181,60 +186,45 @@ Rectangle {
181186
182187 when: windowShown
183188
184− property Item header: findChild(card, "cardHeader")
185− property Item headerLoader: findChild(card, "cardHeaderLoader")
186− property Item title: findChild(header, "titleLabel")
187− property Item art: findChild(card, "artShape")
189+ property Item title: findChild(card, "titleLabel")
190+ property Item subtitle: findChild(card, "subtitleLabel")
191+ property var headerRow: findChild(card, "outerRow")
192+ property var art: findChild(card, "artShape")
188193 property Item artImage: findChild(card, "artImage")
189194 property Item summary: findChild(card, "summaryLabel")
190195 property Item background: findChild(card, "background")
191196 property Item backgroundLoader: findChild(card, "backgroundLoader")
192197 property Item backgroundImage: findChild(card, "backgroundImage")
193−
194− function initTestCase() {
195− verify(typeof testCase.header === "object", "Couldn't find header object.");
196− verify(typeof testCase.art === "object", "Couldn't find art object.");
197− verify(typeof testCase.artImage === "object", "Couldn't find artImage object.");
198− verify(typeof testCase.summary === "object", "Couldn't find summary object.");
199− verify(typeof testCase.artImage === "object", "Couldn't find background object.");
200− verify(typeof testCase.summary === "object", "Couldn't find backgroundImage object.");
201− }
198+ property Item mascotImage: findChild(card, "mascotImage");
202199
203200 function cleanup() {
204201 selector.selectedIndex = -1;
205202 }
206203
207−
208− function test_header_binding_data() {
209− return [
210− { tag: "Mascot", property: "mascot", value: Qt.resolvedUrl("artwork/avatar.png"), index: 0 },
211− { tag: "Title", property: "title", value: "foo", index: 0 },
212− { tag: "Subtitle", property: "subtitle", value: "bar", index: 0 },
213− ];
214− }
215−
216− function test_header_binding(data) {
217− selector.selectedIndex = data.index;
218− tryCompare(testCase.header, data.property, data.value);
219− }
220−
221204 function test_card_binding_data() {
222205 return [
223− { tag: "Art", object: artImage, property: "source", value: Qt.resolvedUrl("artwork/music-player-design.png"), index: 0 },
224− { tag: "Summary", object: summary, property: "text", field: "summary", index: 0 },
225− { tag: "Fit", object: art, fill: Image.PreserveAspectFit, index: 4 },
206+ { tag: "Art", object: "artImage", property: "source", value: Qt.resolvedUrl("artwork/music-player-design.png"), index: 0 },
207+ { tag: "Summary", object: "summary", property: "text", field: "summary", index: 0 },
208+ { tag: "Fit", object: "art", fill: Image.PreserveAspectFit, index: 4 },
209+ { tag: "Mascot", object: "mascotImage", property: "source", value: Qt.resolvedUrl("artwork/avatar.png"), index: 0 },
210+ { tag: "Title", object: "title", property: "text", value: "foo", index: 0 },
211+ { tag: "Subtitle", object: "subtitle", property: "text", value: "bar", index: 0 },
226212 ];
227213 }
228214
229215 function test_card_binding(data) {
230216 selector.selectedIndex = data.index;
217+ waitForRendering(card);
218+
219+ tryCompareFunction(function() { return testCase[data.object] !== null }, true);
220+ var object = testCase[data.object];
231221
232222 if (data.hasOwnProperty('value')) {
233− tryCompare(data.object, data.property, data.value);
223+ tryCompare(object, data.property, data.value);
234224 }
235225
236226 if (data.hasOwnProperty('field')) {
237− tryCompare(data.object, data.property, card.cardData[data.field]);
227+ tryCompare(object, data.property, card.cardData[data.field]);
238228 }
239229 }
240230
@@ -243,31 +233,21 @@ Rectangle {
243233 { tag: "Medium", width: units.gu(18.5), index: 0 },
244234 { tag: "Small", width: units.gu(12), index: 1 },
245235 { tag: "Large", width: units.gu(38), index: 2 },
246− { tag: "Wide", width: units.gu(18.5), aspect: 0.5, index: 0 },
236+ { tag: "Wide", width: units.gu(18.5), index: 0 },
247237 { tag: "Horizontal", width: units.gu(38), index: 5 },
248238 // Make sure card ends with header when there's no summary
249− { tag: "NoSummary", height: function() { return headerLoader.y + headerLoader.height }, index: 6 },
250− { tag: "HorizontalNoSummary", height: function() { return headerLoader.height }, card_layout: "horizontal", index: 6 },
239+ { tag: "NoSummary", height: function() { var cardToolRow = findChild(cardTool, "outerRow");
240+ return cardToolRow.y + cardToolRow.height + units.gu(1) }, index: 6 },
241+ { tag: "HorizontalNoSummary", height: function() { return headerRow.height + units.gu(2) }, card_layout: "horizontal", index: 6 },
251242 ]
252243 }
253244
254245 function test_card_size(data) {
255− waitForRendering(card);
256246 selector.selectedIndex = data.index;
257247
258− if (data.hasOwnProperty("size")) {
259− card.template['card-size'] = data.size;
260− card.templateChanged();
261− }
262−
263248 if (data.hasOwnProperty("card_layout")) {
264− card.template['card-layout'] = data.card_layout;
265− card.templateChanged();
266− }
267−
268− if (data.hasOwnProperty("aspect")) {
269− card.components['art']['aspect-ratio'] = data.aspect;
270− card.componentsChanged();
249+ cardTool.template['card-layout'] = data.card_layout;
250+ cardTool.templateChanged();
271251 }
272252
273253 if (data.hasOwnProperty("width")) {
@@ -288,25 +268,26 @@ Rectangle {
288268 { tag: "Large", width: units.gu(38), index: 2 },
289269 { tag: "Wide", height: units.gu(19), size: "large", index: 3 },
290270 { tag: "Fit", height: units.gu(38), size: "large", width: units.gu(19), index: 4 },
291− { tag: "VerticalWidth", width: function() { return header.width }, index: 0 },
292− { tag: "HorizontalHeight", height: function() { return header.height }, index: 5 },
293− { tag: "HorizontalWidth", width: function() { return headerLoader.x }, index: 5 },
271+ { tag: "VerticalWidth", width: function() { return headerRow.width + units.gu(1) * 2 }, index: 0 },
272+ { tag: "HorizontalHeight", height: function() { return headerRow.height + units.gu(1) * 2 }, index: 5 },
273+ { tag: "HorizontalWidth", width: function() { return headerRow.x - units.gu(1) }, index: 5 },
294274 ]
295275 }
296276
297277 function test_art_size(data) {
298278 selector.selectedIndex = data.index;
299−
300279 if (data.hasOwnProperty("size")) {
301− card.template['card-size'] = data.size;
302− card.templateChanged();
280+ cardTool.template['card-size'] = data.size;
281+ cardTool.templateChanged();
303282 }
304283
305284 if (data.hasOwnProperty("aspect")) {
306− card.components['art']['aspect-ratio'] = data.aspect;
307− card.componentsChanged();
285+ cardTool.components['art']['aspect-ratio'] = data.aspect;
286+ cardTool.componentsChanged();
308287 }
309288
289+ waitForRendering(card);
290+
310291 if (data.hasOwnProperty("width")) {
311292 if (typeof data.width === "function") {
312293 tryCompareFunction(function() { return art.width === data.width() }, true);
@@ -326,38 +307,40 @@ Rectangle {
326307
327308 function test_art_layout_data() {
328309 return [
329− { tag: "Vertical", left: function() { return 0 }, index: 0},
330− { tag: "Horizontal", left: function() { return art.width }, index: 5 },
310+ { tag: "Vertical", left: function() { return units.gu(1); }, index: 0},
311+ { tag: "Horizontal", left: function() { return art.width + units.gu(1); }, index: 5 },
331312 ];
332313 }
333314
334315 function test_art_layout(data) {
335316 selector.selectedIndex = data.index;
317+ waitForRendering(card);
336318
337− tryCompare(testCase.headerLoader, "x", data.left());
319+ tryCompare(headerRow, "x", data.left());
338320 }
339321
340322 function test_header_layout_data() {
341323 return [
342− { tag: "Vertical", top: function() { return art.y + art.height },
343− left: function() { return art.x }, index: 0 },
344− { tag: "Horizontal", top: function() { return art.y },
345− left: function() { return art.x + art.width }, index: 5 },
346− { tag: "Overlay", top: function() { return art.y + art.height - header.height },
347− left: function() { return art.x }, index: 9 },
324+ { tag: "Vertical", top: function() { return art.y + art.height + units.gu(1) },
325+ left: function() { return art.x + units.gu(1) }, index: 0 },
326+ { tag: "Horizontal", top: function() { return art.y + units.gu(1) },
327+ left: function() { return art.x + art.width + units.gu(1) }, index: 5 },
328+ { tag: "Overlay", top: function() { return art.y + art.height - headerRow.height - units.gu(1) },
329+ left: function() { return art.x + units.gu(1) }, index: 9 },
348330 ]
349331 }
350332
351333 function test_header_layout(data) {
352334 selector.selectedIndex = data.index;
335+ waitForRendering(card);
353336
354− tryCompareFunction(function() { return testCase.headerLoader.y === data.top() }, true);
355− tryCompareFunction(function() { return testCase.headerLoader.x === data.left() }, true);
337+ tryCompareFunction(function() { return testCase.headerRow.y === data.top() }, true);
338+ tryCompareFunction(function() { return testCase.headerRow.x === data.left() }, true);
356339 }
357340
358341 function test_summary_layout_data() {
359342 return [
360− { tag: "With header", top: function() { return headerLoader.y + headerLoader.height }, index: 0 },
343+ { tag: "With header", top: function() { return headerRow.y + headerRow.height + units.gu(1); }, index: 0 },
361344 { tag: "Without header", top: function() { return art.y + art.height }, index: 7 },
362345 ]
363346 }
@@ -365,16 +348,16 @@ Rectangle {
365348 function test_summary_layout(data) {
366349 selector.selectedIndex = data.index;
367350
351+ waitForRendering(card);
352+
368353 tryCompareFunction(function() { return testCase.summary.y === data.top() }, true);
369354 }
370355
371356 function test_art_visibility() {
372357 selector.selectedIndex = 8;
373358
374− tryCompare(testCase.artImage, "source", "");
375− compare(testCase.art.visible, false);
376− compare(testCase.art.height, 0);
377− compare(testCase.art.width, 0);
359+ compare(testCase.artImage, null);
360+ compare(testCase.art, null);
378361 }
379362
380363 function test_background_data() {
@@ -402,7 +385,11 @@ Rectangle {
402385
403386 waitForRendering(card);
404387
405− tryCompare(backgroundLoader, "active", data.visible);
388+ if (data.visible) {
389+ tryCompare(backgroundLoader, "active", true);
390+ } else {
391+ compare(backgroundLoader, null);
392+ }
406393
407394 if (data.hasOwnProperty("color")) {
408395 tryCompare(background, "color", data.color);
@@ -449,13 +436,15 @@ Rectangle {
449436
450437 function test_fontColor(data) {
451438 selector.selectedIndex = 10;
439+ waitForRendering(card);
452440
453441 background.color = data.tag;
454442
455443 var fontColor = data.dark ? "grey" : "white";
456444
457445 tryCompareFunction(function() { return Qt.colorEqual(summary.color, fontColor); }, true);
458− tryCompareFunction(function() { return Qt.colorEqual(header.fontColor, fontColor); }, true);
446+ tryCompareFunction(function() { return Qt.colorEqual(title.color, fontColor); }, true);
447+ tryCompareFunction(function() { return Qt.colorEqual(subtitle.color, fontColor); }, true);
459448 }
460449
461450 function test_mascotShape_data() {
@@ -468,15 +457,12 @@ Rectangle {
468457
469458 function test_mascotShape(data) {
470459 selector.selectedIndex = data.index;
460+ waitForRendering(card);
471461
472462 var shape = findChild(card, "mascotShapeLoader");
473− var image = findChild(card, "mascotImage");
474−
475− verify(shape, "Could not find shape.");
476− verify(image, "Could not find image.");
477463
478− tryCompare(shape, "active", data.shape);
479− tryCompare(image, "visible", !data.shape);
464+ compare(shape !== null, data.shape);
465+ tryCompare(mascotImage, "visible", !data.shape);
480466 }
481467 }
482468}
tests/qmltests/Dash/tst_CardBenchmark.qml+10−7
@@ -54,13 +54,16 @@ Rectangle {
5454 Repeater {
5555 id: cardRepeater
5656 model: 0
57− Card {
58− width: cardTool.cardWidth || implicitWidth
59− height: cardTool.cardHeight || implicitHeight
60−
61− template: cardTool.template
62− components: cardTool.components
63− cardData: Helpers.mapData(root.cardData, components)
57+ Loader {
58+ sourceComponent: cardTool.cardComponent
59+ onLoaded: {
60+ item.objectName = "delegate" + index;
61+ item.width = Qt.binding(function() { return cardTool.cardWidth || implicitWidth; });
62+ item.height = Qt.binding(function() { return cardTool.cardHeight || implicitHeight; });
63+ item.cardData = Qt.binding(function() { return Helpers.mapData(root.cardData, cardTool.components); });
64+ item.template = Qt.binding(function() { return cardTool.template; });
65+ item.components = Qt.binding(function() { return cardTool.components; });
66+ }
6467 }
6568 }
6669
tests/qmltests/Dash/tst_CardHeader.qmldeleted−116
@@ -1,116 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import QtTest 1.0
19−import Ubuntu.Components 0.1
20−import Unity.Test 0.1 as UT
21−import "../../../qml/Dash"
22−
23−
24−Rectangle {
25− width: units.gu(40)
26− height: units.gu(72)
27− color: "lightgrey"
28−
29− CardHeader {
30− id: cardHeader
31− anchors { left: parent.left; right: parent.right }
32− }
33−
34− Rectangle {
35− anchors.fill: cardHeader
36− color: "lightblue"
37− opacity: 0.5
38− }
39−
40− UT.UnityTestCase {
41− id: testCase
42− name: "CardHeader"
43−
44− when: windowShown
45−
46− property Item mascot: findChild(cardHeader, "mascotShapeLoader")
47− property Item titleLabel: findChild(cardHeader, "titleLabel")
48− property Item subtitleLabel: findChild(cardHeader, "subtitleLabel")
49− property Item oldPriceLabel: findChild(cardHeader, "oldPriceLabel")
50− property Item outerRow: findChild(cardHeader, "outerRow")
51− property Item column: findChild(cardHeader, "column")
52−
53− function initTestCase() {
54− verify(typeof testCase.mascot === "object", "Couldn't find mascot object.");
55− verify(typeof testCase.titleLabel === "object", "Couldn't find titleLabel object.");
56− verify(typeof testCase.subtitleLabel === "object", "Couldn't find subtitleLabel object.");
57− verify(typeof testCase.oldPriceLabel === "object", "Couldn't find oldPriceLabel object.");
58− }
59−
60− function cleanup() {
61− cardHeader.mascot = "";
62− cardHeader.title = "";
63− cardHeader.subtitle = "";
64− }
65−
66− function test_mascot_data() {
67− return [
68− { tag: "Empty", source: "", visible: false },
69− { tag: "Invalid", source: "bad_path", visible: false },
70− { tag: "Valid", source: Qt.resolvedUrl("artwork/avatar.png"), visible: true },
71− ]
72− }
73−
74− function test_mascot(data) {
75− cardHeader.mascot = data.source;
76− tryCompare(testCase.mascot, "visible", data.visible);
77− }
78−
79− function test_labels_data() {
80− return [
81− { tag: "Empty", visible: false },
82− { tag: "Title only", title: "Foo", visible: true },
83− { tag: "Subtitle only", subtitle: "Bar", visible: false },
84− { tag: "Both", title: "Foo", subtitle: "Bar", visible: true },
85− ]
86− }
87−
88− function test_labels(data) {
89− cardHeader.title = data.title !== undefined ? data.title : "";
90− cardHeader.subtitle = data.subtitle !== undefined ? data.subtitle : "";
91− tryCompare(cardHeader, "visible", data.visible);
92− }
93−
94− function test_dimensions_data() {
95− return [
96− { tag: "Column width", object: column, width: cardHeader.width },
97− { tag: "Column width with mascot", object: column, width: cardHeader.width - mascot.width - outerRow.margins * 3, mascot: "artwork/avatar.png" },
98− { tag: "Header height", object: cardHeader, height: function() { return outerRow.height + outerRow.margins * 2 } },
99− ]
100− }
101−
102− function test_dimensions(data) {
103− if (data.hasOwnProperty("mascot")) {
104− cardHeader.mascot = data.mascot;
105− }
106−
107− if (data.hasOwnProperty("width")) {
108− tryCompare(data.object, "width", data.width);
109− }
110−
111− if (data.hasOwnProperty("height")) {
112− tryCompareFunction(function() { return data.object.height === data.height() }, true);
113− }
114− }
115− }
116−}
tests/qmltests/Dash/tst_CardTool.qml+11−31
@@ -127,26 +127,6 @@ Rectangle {
127127 }
128128 }
129129 }
130−
131− Card {
132− id: card
133− template: cardTool.template;
134− components: cardTool.components;
135− cardData: Helpers.mapData(dataArea.text, components, dataError)
136−
137− width: cardTool.cardWidth || implicitWidth
138− height: cardTool.cardHeight || implicitHeight
139−
140− clip: true
141− headerAlignment: cardTool.headerAlignment
142−
143− Rectangle {
144− anchors.fill: parent
145− color: "transparent"
146− border.width: 1
147− border.color: "green"
148− }
149− }
150130 }
151131
152132 Rectangle {
@@ -257,7 +237,7 @@ Rectangle {
257237 id: testCase
258238 name: "Card"
259239
260− property Card internalCard: findChild(cardTool, "cardToolCard")
240+ property var internalCard: findChild(cardTool, "cardToolCard")
261241
262242 when: windowShown
263243
@@ -272,16 +252,16 @@ Rectangle {
272252
273253 function test_card_size_data() {
274254 return [
275− { tag: "Medium", width: units.gu(18.5), height: function() { return card.height }, index: 0 },
276− { tag: "No art", width: units.gu(18.5), height: function() { return card.height }, index: 1 },
277− { tag: "No summary", width: units.gu(18.5), height: function() { return card.height }, index: 2 },
278− { tag: "Just header", width: units.gu(18.5), height: function() { return card.height }, index: 3 },
279− { tag: "Just title", width: units.gu(18.5), height: function() { return card.height }, index: 4 },
280− { tag: "Title and subtitle", width: units.gu(18.5), height: function() { return card.height }, index: 5 },
281− { tag: "Title and mascot", width: units.gu(18.5), height: function() { return card.height }, index: 6 },
282− { tag: "Small", width: units.gu(12), height: function() { return card.height }, index: 7 },
283− { tag: "Large", width: units.gu(38), height: function() { return card.height }, index: 8 },
284− { tag: "Horizontal", width: units.gu(38), height: function() { return card.height }, index: 9 },
255+ { tag: "Medium", width: units.gu(18.5), height: function() { return internalCard ? internalCard.height : 0 }, index: 0 },
256+ { tag: "No art", width: units.gu(18.5), height: function() { return internalCard ? internalCard.height : 0 }, index: 1 },
257+ { tag: "No summary", width: units.gu(18.5), height: function() { return internalCard ? internalCard.height : 0 }, index: 2 },
258+ { tag: "Just header", width: units.gu(18.5), height: function() { return internalCard ? internalCard.height : 0 }, index: 3 },
259+ { tag: "Just title", width: units.gu(18.5), height: function() { return internalCard ? internalCard.height : 0 }, index: 4 },
260+ { tag: "Title and subtitle", width: units.gu(18.5), height: function() { return internalCard ? internalCard.height : 0 }, index: 5 },
261+ { tag: "Title and mascot", width: units.gu(18.5), height: function() { return internalCard ? internalCard.height : 0 }, index: 6 },
262+ { tag: "Small", width: units.gu(12), height: function() { return internalCard ? internalCard.height : 0 }, index: 7 },
263+ { tag: "Large", width: units.gu(38), height: function() { return internalCard ? internalCard.height : 0 }, index: 8 },
264+ { tag: "Horizontal", width: units.gu(38), height: function() { return internalCard ? internalCard.height : 0 }, index: 9 },
285265 { tag: "OrganicGrid", width: undefined, height: undefined, index: 0, layout_index: 1},
286266 { tag: "Journal", width: undefined, height: units.gu(20), size: 20, index: 0, layout_index: 2 },
287267 { tag: "OversizedJournal", width: undefined, height: units.gu(18.5), size: 40, index: 0, layout_index: 2 },
tests/utils/modules/Unity/Test/UnityTestCase.qml+26
@@ -260,6 +260,32 @@ TestCase {
260260 }
261261 }
262262
263+ function touchPinch(item, x1Start, y1Start, x1End, y1End, x2Start, y2Start, x2End, y2End) {
264+ // Make sure the item is rendered
265+ waitForRendering(item);
266+
267+ var event1 = touchEvent();
268+ // first finger
269+ event1.press(0, x1Start, y1Start);
270+ event1.commit();
271+ // second finger
272+ event1.stationary(0);
273+ event1.press(1, x2Start, y2Start);
274+ event1.commit();
275+
276+ // pinch
277+ for (var i = 0.0; i < 1.0; i += 0.02) {
278+ event1.move(0, x1Start + (x1End - x1Start) * i, y1Start + (y1End - y1Start) * i);
279+ event1.move(1, x2Start + (x2End - x2Start) * i, y2Start + (y2End - y2Start) * i);
280+ event1.commit();
281+ }
282+
283+ // release
284+ event1.release(0, x1End, y1End);
285+ event1.release(1, x2End, y2End);
286+ event1.commit();
287+ }
288+
263289 function fetchRootItem(item) {
264290 if (item.parent)
265291 return fetchRootItem(item.parent)