on xenial help sign in
Merged with trunk.
commit65a6b130a491ccae91929009a200a7cd9a79af2e authorLeo Arias <leo.arias@canonical.com> dateWed 25 Jun 2014, 09:25 (12 years ago) parentbe07744ad0a13f
48 files changed+1673−101
debian/changelog+35 debian/control+1−1 plugins/Dash/CardCreator.js+18−11 plugins/Dash/listviewwithpageheader.cpp+25−5 plugins/Dash/listviewwithpageheader.h+3 plugins/Unity/CMakeLists.txt+1 plugins/Unity/Session/CMakeLists.txt+17 plugins/Unity/Session/dbusunitysessionservice.cpp+46 plugins/Unity/Session/dbusunitysessionservice.h+82 plugins/Unity/Session/plugin.cpp+40 plugins/Unity/Session/plugin.h+34 plugins/Unity/Session/qmldir+3 po/sl.po+6−6 qml/Components/PageHeader.qml+15−3 qml/Components/ResponsiveVerticalJournal.qml+74 qml/Dash/CardCarousel.qml−2 qml/Dash/CardFilterGrid.qml+1−4 qml/Dash/CardVerticalJournal.qml+89 qml/Dash/DashContent.qml+9−1 qml/Dash/DashDepartments.qml+202 qml/Dash/DashDepartmentsList.qml+195 qml/Dash/DashRenderer.qml−7 qml/Dash/GenericScopeView.qml+10−1 qml/Dash/ScopeItem.qml+12 qml/Dash/graphics/tick@30.png qml/Notifications/Notification.qml+13 qml/Shell.qml+25 src/main.cpp+1 tests/mocks/Unity/CMakeLists.txt+2 tests/mocks/Unity/fake_department.cpp+127 tests/mocks/Unity/fake_department.h+61 tests/mocks/Unity/fake_scope.cpp+36−1 tests/mocks/Unity/fake_scope.h+6 tests/mocks/Unity/fake_scopes.cpp+2−2 tests/mocks/Unity/fake_unity_plugin.cpp+2 tests/plugins/Dash/cardcreator/1.res+14−9 tests/plugins/Dash/cardcreator/2.res+2−1 tests/plugins/Dash/cardcreator/3.res+20−15 tests/plugins/Dash/cardcreator/4.res+2−1 tests/plugins/Dash/cardcreator/5.res+21−16 tests/plugins/Unity/CMakeLists.txt+1 tests/plugins/Unity/Session/CMakeLists.txt+20 tests/plugins/Unity/Session/sessionbackendtest.cpp+64 tests/qmltests/CMakeLists.txt+1 tests/qmltests/Components/tst_ResponsiveVerticalJournal.qml+183 tests/qmltests/Dash/tst_Card.qml+9 tests/qmltests/Dash/tst_DashContent.qml+114−3 tests/qmltests/Notifications/tst_Notifications.qml+29−12
debian/changelog+35
@@ -1,3 +1,38 @@
1+unity8 (7.89+14.10.20140624-0ubuntu1) utopic; urgency=low
2+
3+ [ Ying-Chun Liu ]
4+ * Add logout support. Reviewed by: Daniel d'Andrada (LP: #1302213)
5+
6+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Tue, 24 Jun 2014 08:17:09 +0000
7+
8+unity8 (7.89+14.10.20140623.1-0ubuntu1) utopic; urgency=low
9+
10+ [ Michał Sawicz ]
11+ * Make so that fixedArtShapeSize actually fixes artShapeSize.
12+
13+ [ Albert Astals ]
14+ * Add VerticalJournal integration to Dash/scopes/QML (LP: #1326467)
15+ * Make so that fixedArtShapeSize actually fixes artShapeSize.
16+
17+ [ Mirco Müller ]
18+ * Added the frontend-part of sound-hint support for notifications with
19+ updated QML-tests.
20+
21+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Mon, 23 Jun 2014 11:17:12 +0000
22+
23+unity8 (7.89+14.10.20140619.3-0ubuntu1) utopic; urgency=low
24+
25+ * New rebuild forced
26+
27+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Thu, 19 Jun 2014 16:02:41 +0000
28+
29+unity8 (7.89+14.10.20140619.2-0ubuntu1) utopic; urgency=low
30+
31+ [ Albert Astals ]
32+ * Departments support (LP: #1320847)
33+
34+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Thu, 19 Jun 2014 11:17:40 +0000
35+
136unity8 (7.89+14.10.20140616.1-0ubuntu1) utopic; urgency=low
237
338 [ Pawel Stolowski ]
debian/control+1−1
@@ -17,7 +17,7 @@ Build-Depends: cmake,
1717 libpulse-dev,
1818 libqmenumodel-dev (>= 0.2.7),
1919 libqt5xmlpatterns5-dev,
20− libunity-api-dev (>= 7.80.6),
20+ libunity-api-dev (>= 7.82),
2121 libunity-mir-dev,
2222 libusermetricsoutput1-dev,
2323 libxcb1-dev,
plugins/Dash/CardCreator.js+18−11
@@ -55,8 +55,8 @@ var kBackgroundLoaderCode = 'Loader {\n\
5555// %3 is used as image height
5656var kArtShapeHolderCode = 'Item { \n\
5757 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\
58+ height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height; \n\
59+ width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width; \n\
6060 anchors { %1 } \n\
6161 Loader { \n\
6262 id: artShapeLoader; \n\
@@ -68,13 +68,18 @@ var kArtShapeHolderCode = 'Item { \n\
6868 id: artShape; \n\
6969 objectName: "artShape"; \n\
7070 radius: "medium"; \n\
71− readonly property real aspect: components !== undefined ? components["art"]["aspect-ratio"] : 1; \n\
71+ visible: image.status == Image.Ready; \n\
72+ readonly property real fixedArtShapeSizeAspect: (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) ? root.fixedArtShapeSize.width / root.fixedArtShapeSize.height : -1; \n\
73+ readonly property real aspect: fixedArtShapeSizeAspect > 0 ? fixedArtShapeSizeAspect : components !== undefined ? components["art"]["aspect-ratio"] : 1; \n\
7274 readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect; \n\
7375 Component.onCompleted: { updateWidthHeightBindings(); if (artShapeBorderSource !== undefined) borderSource = artShapeBorderSource; } \n\
7476 onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings(); \n\
75− visible: image.status == Image.Ready; \n\
77+ Connections { target: root; onFixedArtShapeSizeChanged: updateWidthHeightBindings(); } \n\
7678 function updateWidthHeightBindings() { \n\
77− if (aspectSmallerThanImageAspect) { \n\
79+ if (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) { \n\
80+ width = root.fixedArtShapeSize.width; \n\
81+ height = root.fixedArtShapeSize.height; \n\
82+ } else if (aspectSmallerThanImageAspect) { \n\
7883 width = Qt.binding(function() { return !visible ? 0 : image.width }); \n\
7984 height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect }); \n\
8085 } else { \n\
@@ -108,7 +113,7 @@ var kOverlayLoaderCode = 'Loader { \n\
108113 visible: showHeader && status == Loader.Ready; \n\
109114 sourceComponent: ShaderEffect { \n\
110115 id: overlay; \n\
111− height: fixedHeaderHeight != -1 ? fixedHeaderHeight : headerHeight; \n\
116+ height: fixedHeaderHeight > 0 ? fixedHeaderHeight + units.gu(1) * 2 : headerHeight; \n\
112117 opacity: 0.6; \n\
113118 property var source: ShaderEffectSource { \n\
114119 id: shaderSource; \n\
@@ -145,6 +150,7 @@ var kHeaderRow2Code = 'Row { \n\
145150 objectName: "outerRow"; \n\
146151 property real margins: units.gu(1); \n\
147152 spacing: margins; \n\
153+ height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight; \n\
148154 anchors { %1 } \n\
149155 anchors.right: parent.right; \n\
150156 anchors.margins: margins;\n\
@@ -163,6 +169,7 @@ var kHeaderRow3Code = 'Row { \n\
163169 objectName: "outerRow"; \n\
164170 property real margins: units.gu(1); \n\
165171 spacing: margins; \n\
172+ height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight; \n\
166173 anchors { %1 } \n\
167174 anchors.right: parent.right; \n\
168175 anchors.margins: margins;\n\
@@ -315,7 +322,7 @@ function cardString(template, components) {
315322 anchors = 'left: parent.left';
316323 if (hasMascot || hasTitle) {
317324 widthCode = 'height * artShape.aspect'
318− heightCode = 'headerHeight';
325+ heightCode = 'headerHeight + 2 * units.gu(1)';
319326 } else {
320327 // This side of the else is a bit silly, who wants an horizontal layout without mascot and title?
321328 // So we define a "random" height of the image height + 2 gu for the margins
@@ -366,13 +373,13 @@ function cardString(template, components) {
366373 }
367374
368375 if (hasHeaderRow) {
369− code += 'readonly property int headerHeight: row.height + row.margins * 2;\n'
376+ code += 'readonly property int headerHeight: row.height;\n'
370377 } else if (hasMascot) {
371− code += 'readonly property int headerHeight: mascotImage.height + units.gu(1) * 2;\n'
378+ code += 'readonly property int headerHeight: mascotImage.height;\n'
372379 } else if (hasSubtitle) {
373− code += 'readonly property int headerHeight: titleLabel.height + titleLabel.anchors.topMargin * 2 + subtitleLabel.height + subtitleLabel.anchors.topMargin;\n'
380+ code += 'readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;\n'
374381 } else if (hasTitle) {
375− code += 'readonly property int headerHeight: titleLabel.height + titleLabel.anchors.topMargin * 2;\n'
382+ code += 'readonly property int headerHeight: titleLabel.height;\n'
376383 } else {
377384 code += 'readonly property int headerHeight: 0;\n'
378385 }
plugins/Dash/listviewwithpageheader.cpp+25−5
@@ -258,6 +258,8 @@ void ListViewWithPageHeader::setHeader(QQuickItem *headerItem)
258258 if (m_headerItem) {
259259 m_headerItem->setParentItem(contentItem());
260260 m_headerItem->setZ(1);
261+ m_previousHeaderImplicitHeight = m_headerItem->implicitHeight();
262+ QQuickItemPrivate::get(m_headerItem)->addItemChangeListener(this, QQuickItemPrivate::ImplicitHeight);
261263 }
262264 qreal newHeaderHeight = m_headerItem ? m_headerItem->height() : 0;
263265 if (!m_visibleItems.isEmpty() && newHeaderHeight != oldHeaderHeight) {
@@ -496,7 +498,15 @@ void ListViewWithPageHeader::viewportMoved(Qt::Orientations orient)
496498
497499 QQuickFlickable::viewportMoved(orient);
498500// qDebug() << "ListViewWithPageHeader::viewportMoved" << contentY();
499− qreal diff = m_previousContentY - contentY();
501+ const qreal diff = m_previousContentY - contentY();
502+ adjustHeader(diff);
503+ m_previousContentY = contentY();
504+ layout();
505+ polish();
506+}
507+
508+void ListViewWithPageHeader::adjustHeader(qreal diff)
509+{
500510 const bool showHeaderAnimationRunning = m_contentYAnimation->isRunning() && contentYAnimationType == ContentYAnimationShowHeader;
501511 if (m_headerItem) {
502512 const auto oldHeaderItemShownHeight = m_headerItemShownHeight;
@@ -558,10 +568,6 @@ void ListViewWithPageHeader::viewportMoved(Qt::Orientations orient)
558568 adjustMinYExtent();
559569 }
560570 }
561−
562− m_previousContentY = contentY();
563− layout();
564− polish();
565571}
566572
567573void ListViewWithPageHeader::createDelegateModel()
@@ -1071,6 +1077,20 @@ void ListViewWithPageHeader::itemGeometryChanged(QQuickItem * /*item*/, const QR
10711077 }
10721078}
10731079
1080+void ListViewWithPageHeader::itemImplicitHeightChanged(QQuickItem *item)
1081+{
1082+ if (item == m_headerItem) {
1083+ const qreal diff = m_headerItem->implicitHeight() - m_previousHeaderImplicitHeight;
1084+ if (diff != 0) {
1085+ adjustHeader(diff);
1086+ m_previousHeaderImplicitHeight = m_headerItem->implicitHeight();
1087+ layout();
1088+ polish();
1089+ m_contentHeightDirty = true;
1090+ }
1091+ }
1092+}
1093+
10741094void ListViewWithPageHeader::headerHeightChanged(qreal newHeaderHeight, qreal oldHeaderHeight, qreal oldHeaderY)
10751095{
10761096 const qreal heightDiff = newHeaderHeight - oldHeaderHeight;
plugins/Dash/listviewwithpageheader.h+3
@@ -106,6 +106,7 @@ protected:
106106 void viewportMoved(Qt::Orientations orient) override;
107107 qreal minYExtent() const override;
108108 void itemGeometryChanged(QQuickItem *item, const QRectF &newGeometry, const QRectF &oldGeometry) override;
109+ void itemImplicitHeightChanged(QQuickItem *item) override;
109110 void updatePolish() override;
110111
111112private Q_SLOTS:
@@ -146,6 +147,7 @@ private:
146147 bool removeNonVisibleItems(qreal bufferFrom, qreal bufferTo);
147148 ListItem *createItem(int modelIndex, bool asynchronous);
148149
150+ void adjustHeader(qreal diff);
149151 void adjustMinYExtent();
150152 void updateClipItem();
151153 void headerHeightChanged(qreal newHeaderHeight, qreal oldHeaderHeight, qreal oldHeaderY);
@@ -178,6 +180,7 @@ private:
178180
179181 QQuickItem *m_headerItem;
180182 qreal m_previousContentY;
183+ qreal m_previousHeaderImplicitHeight;
181184 qreal m_headerItemShownHeight; // The height of header shown when the header is shown outside its topmost position
182185 // i.e. it's being shown after dragging down in the middle of the list
183186 enum ContentYAnimationType { ContentYAnimationShowHeader, ContentYAnimationMaximizeVisibleArea };
plugins/Unity/CMakeLists.txt+1
@@ -1,2 +1,3 @@
11add_subdirectory(Indicators)
22add_subdirectory(Launcher)
3+add_subdirectory(Session)
plugins/Unity/Session/CMakeLists.txtnew file+17
@@ -0,0 +1,17 @@
1+include_directories(
2+ ${CMAKE_CURRENT_SOURCE_DIR}
3+)
4+
5+set(QMLSESSIONPLUGIN_SRC
6+ plugin.cpp
7+ dbusunitysessionservice.cpp
8+ )
9+
10+add_library(UnitySession-qml MODULE
11+ ${QMLSESSIONPLUGIN_SRC}
12+ )
13+
14+qt5_use_modules(UnitySession-qml DBus Qml)
15+
16+# export the qmldir and qmltypes files
17+add_unity8_plugin(Unity.Session 0.1 Unity/Session TARGETS UnitySession-qml)
plugins/Unity/Session/dbusunitysessionservice.cppnew file+46
@@ -0,0 +1,46 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it under
5+ * the terms of the GNU Lesser General Public License version 3, as published by
6+ * the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but WITHOUT
9+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+ * Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+// local
18+#include "dbusunitysessionservice.h"
19+
20+// Qt
21+#include <QDBusConnection>
22+
23+DBusUnitySessionService::DBusUnitySessionService() : QObject()
24+{
25+ QDBusConnection connection = QDBusConnection::sessionBus();
26+
27+ connection.registerService("com.canonical.Unity");
28+ connection.registerObject("/com/canonical/Unity/Session", this,
29+ QDBusConnection::ExportScriptableSignals
30+ | QDBusConnection::ExportScriptableSlots
31+ | QDBusConnection::ExportScriptableInvokables);
32+}
33+
34+DBusUnitySessionService::~DBusUnitySessionService()
35+{
36+}
37+
38+void DBusUnitySessionService::Logout()
39+{
40+ Q_EMIT logoutReady();
41+}
42+
43+void DBusUnitySessionService::RequestLogout()
44+{
45+ Q_EMIT logoutRequested(false);
46+}
plugins/Unity/Session/dbusunitysessionservice.hnew file+82
@@ -0,0 +1,82 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it under
5+ * the terms of the GNU Lesser General Public License version 3, as published by
6+ * the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but WITHOUT
9+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+ * Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef DBUSUNITYSESSIONSERVICE_H
18+#define DBUSUNITYSESSIONSERVICE_H
19+
20+#include <QObject>
21+
22+/**
23+ * DBusUnitySessionService provides com.canonical.Unity.Session dbus
24+ * interface.
25+ *
26+ * com.canonical.Unity.Session interface provides public methods
27+ * and signals to handle Logout/Reboot/Shutdown.
28+ */
29+class DBusUnitySessionService : public QObject
30+{
31+ Q_OBJECT
32+ Q_CLASSINFO("D-Bus Interface", "com.canonical.Unity.Session")
33+
34+public:
35+ DBusUnitySessionService();
36+ ~DBusUnitySessionService();
37+
38+Q_SIGNALS:
39+ /**
40+ * logoutRequested signal
41+ *
42+ * This signal is emitted when some applications request the system to
43+ * logout.
44+ * @param have_inhibitors if there are any special running applications
45+ * which inhibit the logout.
46+ */
47+ void logoutRequested(bool have_inhibitors);
48+
49+ /**
50+ * logoutReady signal
51+ *
52+ * This signal is emitted when all the apps are closed. And the system
53+ * is safe to logout.
54+ */
55+ void logoutReady();
56+
57+public Q_SLOTS:
58+ /**
59+ * Logout the system.
60+ *
61+ * This method directly logout the system without user's confirmation.
62+ * Ordinary applications should avoid calling this method. Please call
63+ * RequestLogout() to ask the user to decide logout or not.
64+ * This method will stop all the running applications and then signal
65+ * logoutReady when all the apps stopped.
66+ */
67+ Q_SCRIPTABLE void Logout();
68+
69+ /**
70+ * Issue a logout request.
71+ *
72+ * This method emit the logoutRequested signal to the shell with a boolean
73+ * which indicates if there's any inhibitors. The shell should receive
74+ * this signal and display a dialog to ask the user to confirm the logout
75+ * action. If confirmed, shell can call Logout() method to kill the apps
76+ * and then logout
77+ */
78+ Q_SCRIPTABLE void RequestLogout();
79+
80+};
81+
82+#endif // DBUSUNITYSESSIONSERVICE_H
plugins/Unity/Session/plugin.cppnew 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+ * Authors: Ying-Chun Liu (PaulLiu) <paul.liu@canonical.com>
17+ */
18+
19+#include "plugin.h"
20+#include "dbusunitysessionservice.h"
21+
22+#include <QAbstractItemModel>
23+#include <QDBusConnection>
24+#include <QtQml/qqml.h>
25+
26+static QObject *dbusunitysessionservice_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
27+{
28+ Q_UNUSED(engine)
29+ Q_UNUSED(scriptEngine)
30+
31+ return new DBusUnitySessionService();
32+}
33+
34+void SessionPlugin::registerTypes(const char *uri)
35+{
36+ qmlRegisterType<QAbstractItemModel>();
37+
38+ Q_ASSERT(uri == QLatin1String("Unity.Session"));
39+ qmlRegisterSingletonType<DBusUnitySessionService>(uri, 0, 1, "DBusUnitySessionService", dbusunitysessionservice_provider);
40+}
plugins/Unity/Session/plugin.hnew file+34
@@ -0,0 +1,34 @@
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+ * Authors: Ying-Chun Liu (PaulLiu) <paul.liu@canonical.com>
17+ */
18+
19+#ifndef SESSION_PLUGIN_H
20+#define SESSION_PLUGIN_H
21+
22+#include <QtQml/QQmlEngine>
23+#include <QtQml/QQmlExtensionPlugin>
24+
25+class SessionPlugin : public QQmlExtensionPlugin
26+{
27+ Q_OBJECT
28+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
29+
30+public:
31+ void registerTypes(const char *uri);
32+};
33+
34+#endif
plugins/Unity/Session/qmldirnew file+3
@@ -0,0 +1,3 @@
1+module Unity.Session
2+plugin UnitySession-qml
3+typeinfo UnitySession.qmltypes
po/sl.po+6−6
@@ -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-06-13 09:55+0200\n"
11−"PO-Revision-Date: 2014-05-22 14:07+0000\n"
11+"PO-Revision-Date: 2014-06-23 09:29+0000\n"
1212"Last-Translator: Damir Jerovšek <Unknown>\n"
1313"Language-Team: Slovenian <sl@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-06-17 06:51+0000\n"
18−"X-Generator: Launchpad (build 17045)\n"
17+"X-Launchpad-Export-Date: 2014-06-24 06:33+0000\n"
18+"X-Generator: Launchpad (build 17065)\n"
1919
2020#: plugins/Unity/Launcher/launcheritem.cpp:43
2121#: plugins/Unity/Launcher/launcheritem.cpp:73
@@ -82,7 +82,7 @@ msgstr "Klic v sili"
8282
8383#: qml/Components/Lockscreen.qml:231
8484msgid "OK"
85−msgstr ""
85+msgstr "V REDU"
8686
8787#: qml/Components/PageHeader.qml:365
8888msgid "Recent searches"
@@ -99,11 +99,11 @@ msgstr "Pozdravljeni"
9999
100100#: qml/Components/PinLockscreen.qml:198
101101msgid "CANCEL"
102−msgstr ""
102+msgstr "PREKLIČI"
103103
104104#: qml/Components/PinLockscreen.qml:216
105105msgid "DONE"
106−msgstr ""
106+msgstr "KONČANO"
107107
108108#: qml/Components/SeeMore.qml:35
109109msgid "See more"
qml/Components/PageHeader.qml+15−3
@@ -27,12 +27,13 @@ Item {
2727 property ListModel searchHistory
2828 property var scope: null
2929 property alias childItem: itemContainer.children
30+ property alias bottomItem: bottomContainer.children
3031 property alias showBackButton: backButton.visible
3132
3233 signal backClicked
3334
34− height: units.gu(8.5)
35− implicitHeight: units.gu(8.5)
35+ height: header.height + units.gu(2) + bottomContainer.height
36+ implicitHeight: header.height + units.gu(2) + bottomContainer.height
3637
3738 function triggerSearch() {
3839 if (searchEntryEnabled) searchField.forceActiveFocus()
@@ -404,9 +405,20 @@ Item {
404405 top: header.bottom
405406 left: parent.left
406407 right: parent.right
407− bottom: parent.bottom
408+ bottom: bottomContainer.top
408409 }
409410
410411 source: "graphics/PageHeaderBaseDivider.sci"
411412 }
413+
414+ Item {
415+ id: bottomContainer
416+
417+ anchors {
418+ left: parent.left
419+ right: parent.right
420+ bottom: parent.bottom
421+ }
422+ height: childrenRect.height
423+ }
412424}
qml/Components/ResponsiveVerticalJournal.qmlnew file+74
@@ -0,0 +1,74 @@
1+/*
2+ * Copyright (C) 2013-2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import Ubuntu.Components 0.1
19+import "../Components"
20+import Dash 0.1
21+
22+/*!
23+ \brief A responsive wrapper around VerticalJournal.
24+
25+ Based on defined column width, delegates are laid out in columns following
26+ a top-left most position rule.
27+
28+ Example:
29+ +-----+ +-----+ +-----+
30+ | | | 2 | | |
31+ | | | | | |
32+ | 1 | +-----+ | 3 |
33+ | | +-----+ | |
34+ | | | | +-----+
35+ +-----+ | 4 | +-----+
36+ +-----+ | | | 5 |
37+ | 6 | +-----+ | |
38+ | | +-----+
39+ +-----+
40+*/
41+Item {
42+ property real minimumColumnSpacing: units.gu(1)
43+
44+ property alias columnWidth: verticalJournalView.columnWidth
45+ property alias rowSpacing: verticalJournalView.rowSpacing
46+ property alias model: verticalJournalView.model
47+ property alias delegate: verticalJournalView.delegate
48+ property alias displayMarginBeginning: verticalJournalView.displayMarginBeginning
49+ property alias displayMarginEnd: verticalJournalView.displayMarginEnd
50+ implicitHeight: verticalJournalView.implicitHeight
51+
52+ VerticalJournal {
53+ id: verticalJournalView
54+ objectName: "responsiveVerticalJournalView"
55+ anchors {
56+ fill: parent
57+ leftMargin: columnSpacing / 2
58+ rightMargin: columnSpacing / 2
59+ topMargin: rowSpacing / 2
60+ bottomMargin: rowSpacing / 2
61+ }
62+ clip: parent.height != implicitHeight
63+
64+ function px2gu(pixels) {
65+ return Math.floor(pixels / units.gu(1))
66+ }
67+
68+ columnSpacing: {
69+ // parent.width = columns * columnWidth + (columns-1) * spacing + spacing(margins)
70+ var expectedColumns = Math.max(1, Math.floor(parent.width / (columnWidth + minimumColumnSpacing)));
71+ Math.floor((parent.width - expectedColumns * columnWidth) / expectedColumns);
72+ }
73+ }
74+}
qml/Dash/CardCarousel.qml−2
@@ -27,9 +27,7 @@ DashRenderer {
2727 property alias selectedItemScaleFactor: carousel.selectedItemScaleFactor
2828 property alias tileAspectRatio: carousel.tileAspectRatio
2929
30− currentItem: carousel.currentItem
3130 height: carousel.implicitHeight + units.gu(6)
32− verticalSpacing: units.gu(3)
3331
3432 Carousel {
3533 id: carousel
qml/Dash/CardFilterGrid.qml+1−4
@@ -22,11 +22,8 @@ DashRenderer {
2222
2323 expandable: filterGrid.expandable
2424 collapsedHeight: filterGrid.collapsedHeight
25− margins: filterGrid.margins
2625 uncollapsedHeight: filterGrid.uncollapsedHeight
2726 originY: filterGrid.originY
28− verticalSpacing: units.gu(1)
29− currentItem: filterGrid.currentItem
3027 height: filterGrid.height
3128 filtered: filterGrid.filtered
3229
@@ -40,7 +37,7 @@ DashRenderer {
4037 minimumHorizontalSpacing: units.gu(1)
4138 delegateWidth: cardTool.cardWidth
4239 delegateHeight: cardTool.cardHeight
43− verticalSpacing: genericFilterGrid.verticalSpacing
40+ verticalSpacing: units.gu(1)
4441 model: genericFilterGrid.model
4542 collapsedRowCount: Math.min(2, cardTool && cardTool.template && cardTool.template["collapsed-rows"] || 2)
4643 displayMarginBeginning: genericFilterGrid.displayMarginBeginning
qml/Dash/CardVerticalJournal.qmlnew file+89
@@ -0,0 +1,89 @@
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+DashRenderer {
22+ id: root
23+
24+ readonly property double collapseLimit: units.gu(35)
25+
26+ uncollapsedHeight: cardVerticalJournal.implicitHeight
27+ collapsedHeight: Math.min(collapseLimit, cardVerticalJournal.implicitHeight)
28+ expandable: uncollapsedHeight > collapseLimit
29+
30+ // This minHeight is used as bootstrapper for the height. Vertical Journal
31+ // is special by the fact that it doesn't know how to calculate its implicit height unless we give it
32+ // enough height that it can start creating its children so we make sure it has enough height for that
33+ // in case the model is non empty
34+ readonly property double minHeight: root.model.count > 1 ? cardVerticalJournal.rowSpacing + 1 : 0
35+ height: filtered ? Math.max(collapsedHeight, minHeight) : uncollapsedHeight
36+
37+ Behavior on height {
38+ id: heightBehaviour
39+ enabled: false
40+ animation: UbuntuNumberAnimation {
41+ onRunningChanged: {
42+ if (!running) {
43+ heightBehaviour.enabled = false
44+ }
45+ }
46+ }
47+ }
48+
49+ function setFilter(filter, animate) {
50+ heightBehaviour.enabled = animate;
51+ filtered = filter;
52+ }
53+
54+ ResponsiveVerticalJournal {
55+ id: cardVerticalJournal
56+
57+ model: root.model
58+
59+ anchors.fill: parent
60+ rowSpacing: minimumColumnSpacing
61+ columnWidth: cardTool.cardWidth
62+
63+ displayMarginBeginning: root.displayMarginBeginning
64+ displayMarginEnd: root.displayMarginEnd
65+
66+ delegate: Loader {
67+ id: loader
68+ sourceComponent: cardTool.cardComponent
69+ width: cardTool.cardWidth
70+ anchors.horizontalCenter: parent.horizontalCenter
71+ onLoaded: {
72+ item.objectName = "delegate" + index;
73+ item.width = Qt.binding(function() { return cardTool.cardWidth; });
74+ item.height = Qt.binding(function() { return cardTool.cardHeight; });
75+ item.fixedArtShapeSize = Qt.binding(function() { return cardTool.artShapeSize; });
76+ item.fixedHeaderHeight = Qt.binding(function() { return cardTool.headerHeight; });
77+ item.cardData = Qt.binding(function() { return model; });
78+ item.template = Qt.binding(function() { return cardTool.template; });
79+ item.components = Qt.binding(function() { return cardTool.components; });
80+ item.headerAlignment = Qt.binding(function() { return cardTool.headerAlignment; });
81+ }
82+ Connections {
83+ target: loader.item
84+ onClicked: root.clicked(index, result)
85+ onPressAndHold: root.pressAndHold(index)
86+ }
87+ }
88+ }
89+}
qml/Dash/DashContent.qml+9−1
@@ -151,7 +151,7 @@ Item {
151151 item.previewListView = previewListView;
152152 item.scope = Qt.binding(function() { return scope })
153153 item.isCurrent = Qt.binding(function() { return visible && ListView.isCurrentItem })
154− item.tabBarHeight = dashPageHeader.implicitHeight;
154+ item.tabBarHeight = Qt.binding(function() { return dashPageHeader.implicitHeight; })
155155 dashContent.scopeLoaded(item.scope.id)
156156 }
157157 Connections {
@@ -217,6 +217,14 @@ Item {
217217 onTriggered: tabBar.selectionMode = false
218218 }
219219 }
220+
221+ bottomItem: DashDepartments {
222+ scope: dashContentList.currentItem ? dashContentList.currentItem.theScope : null
223+ width: parent.width <= units.gu(60) ? parent.width : units.gu(40)
224+ anchors.right: parent.right
225+ windowHeight: dashContent.height
226+ windowWidth: dashContent.width
227+ }
220228 }
221229 }
222230
qml/Dash/DashDepartments.qmlnew file+202
@@ -0,0 +1,202 @@
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 Ubuntu.Components 0.1
19+
20+AbstractButton {
21+ id: root
22+ objectName: "dashDepartments"
23+
24+ property var scope: null
25+
26+ property bool showList: false
27+
28+ readonly property var currentDepartment: scope && scope.hasDepartments ? scope.getDepartment(scope.currentDepartmentId) : null
29+
30+ property alias windowWidth: blackRect.width
31+ property alias windowHeight: blackRect.height
32+
33+ // Are we drilling down the tree or up?
34+ property bool isGoingBack: false
35+
36+ visible: root.currentDepartment != null
37+
38+ height: visible ? units.gu(5) : 0
39+
40+ onClicked: {
41+ root.showList = !root.showList;
42+ }
43+
44+ Rectangle {
45+ id: blackRect
46+ color: "black"
47+ opacity: departmentListView.currentItem && departmentListView.currentItem.visible ? 0.3 : 0
48+ Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
49+ anchors.top: departmentListView.top
50+ anchors.right: parent.right
51+ visible: opacity != 0
52+ }
53+
54+ Image {
55+ anchors {
56+ top: parent.top
57+ left: parent.left
58+ right: parent.right
59+ }
60+ fillMode: Image.Stretch
61+ source: "graphics/dash_divider_top_lightgrad.png"
62+ z: -1
63+ }
64+
65+ Image {
66+ anchors {
67+ bottom: parent.bottom
68+ left: parent.left
69+ right: parent.right
70+ }
71+ fillMode: Image.Stretch
72+ source: "graphics/dash_divider_top_darkgrad.png"
73+ z: -1
74+ }
75+
76+ Label {
77+ anchors.fill: parent
78+ anchors.margins: units.gu(2)
79+ verticalAlignment: Text.AlignVCenter
80+ text: root.currentDepartment ? root.currentDepartment.label : ""
81+ color: "gray" // TODO remove once we're a separate app
82+ }
83+
84+ Image {
85+ anchors.verticalCenter: parent.verticalCenter
86+ anchors.right: parent.right
87+ anchors.rightMargin: units.gu(2)
88+ rotation: showList ? 180 : 0
89+ source: "image://theme/dropdown-menu"
90+ sourceSize.height: parent.height - units.gu(2)
91+ sourceSize.width: units.gu(2)
92+ fillMode: Image.PreserveAspectFit
93+ }
94+
95+ // departmentListView is outside root
96+ ListView {
97+ id: departmentListView
98+ objectName: "departmentListView"
99+ orientation: ListView.Horizontal
100+ interactive: false
101+ clip: root.width != windowWidth
102+ model: ListModel {
103+ id: departmentModel
104+ // We have two roles
105+ // departmentId: the department id of the department the list represents
106+ // nullifyDepartment: overrides departmentId to be null
107+ // This is used to "clear" the delegate when going "right" on the tree
108+ }
109+ anchors {
110+ left: parent.left
111+ right: parent.right
112+ top: root.bottom
113+ }
114+ readonly property int maxHeight: (windowHeight - mapToItem(null, root.x, root.y).y) - units.gu(8)
115+ property int prevHeight: maxHeight
116+ height: currentItem ? currentItem.height : maxHeight
117+ onHeightChanged: {
118+ if (root.showList) {
119+ prevHeight = currentItem.desiredHeight;
120+ }
121+ }
122+ highlightMoveDuration: UbuntuAnimation.FastDuration
123+ delegate: DashDepartmentsList {
124+ objectName: "department" + index
125+ visible: height != 0
126+ width: departmentListView.width
127+ property real desiredHeight: {
128+ if (root.showList) {
129+ if (department && department.loaded && x == departmentListView.contentX)
130+ {
131+ return Math.min(implicitHeight, departmentListView.maxHeight);
132+ } else {
133+ return departmentListView.prevHeight;
134+ }
135+ } else {
136+ return 0;
137+ }
138+ }
139+ height: desiredHeight
140+ department: (nullifyDepartment || !scope) ? null : scope.getDepartment(departmentId)
141+ currentDepartment: root.currentDepartment
142+ onEnterDepartment: {
143+ scope.loadDepartment(newDepartmentId);
144+ // We only need to add a new item to the model
145+ // if we have children, otherwise just load it
146+ if (hasChildren) {
147+ isGoingBack = false;
148+ departmentModel.append({"departmentId": newDepartmentId, "nullifyDepartment": false});
149+ departmentListView.currentIndex++;
150+ } else {
151+ showList = false;
152+ }
153+ }
154+ onGoBackToParentClicked: {
155+ scope.loadDepartment(department.parentDepartmentId);
156+ isGoingBack = true;
157+ departmentModel.setProperty(departmentListView.currentIndex - 1, "nullifyDepartment", false);
158+ departmentListView.currentIndex--;
159+ }
160+ onAllDepartmentClicked: {
161+ showList = false;
162+ if (root.currentDepartment.parentDepartmentId == department.departmentId) {
163+ // For leaves we have to go to the parent too
164+ scope.loadDepartment(root.currentDepartment.parentDepartmentId);
165+ }
166+ }
167+ }
168+ onContentXChanged: {
169+ if (contentX == width * departmentListView.currentIndex) {
170+ if (isGoingBack) {
171+ departmentModel.remove(departmentListView.currentIndex + 1);
172+ } else {
173+ departmentModel.setProperty(departmentListView.currentIndex - 1, "nullifyDepartment", true);
174+ }
175+ }
176+ }
177+ }
178+
179+ InverseMouseArea {
180+ anchors.fill: departmentListView
181+ enabled: root.showList
182+ onClicked: root.showList = false
183+ }
184+
185+ onScopeChanged: {
186+ departmentModel.clear();
187+ if (scope && scope.hasDepartments) {
188+ departmentModel.append({"departmentId": scope.currentDepartmentId, "nullifyDepartment": false});
189+ }
190+ }
191+
192+ Connections {
193+ target: scope
194+ onHasDepartmentsChanged: {
195+ if (scope.hasDepartments) {
196+ departmentModel.append({"departmentId": scope.currentDepartmentId, "nullifyDepartment": false});
197+ } else {
198+ departmentModel.clear();
199+ }
200+ }
201+ }
202+}
qml/Dash/DashDepartmentsList.qmlnew file+195
@@ -0,0 +1,195 @@
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 Ubuntu.Components 0.1
19+
20+Item {
21+ id: root
22+ property var department: null
23+ property var currentDepartment: null
24+ signal enterDepartment(var newDepartmentId, bool hasChildren)
25+ signal goBackToParentClicked()
26+ signal allDepartmentClicked()
27+
28+ readonly property int itemHeight: units.gu(5)
29+ implicitHeight: flickable.contentHeight
30+
31+ Rectangle {
32+ color: "white"
33+ anchors.fill: parent
34+ }
35+
36+ ActivityIndicator {
37+ id: loadingIndicator
38+ anchors.centerIn: parent
39+ running: !(department && department.loaded)
40+ }
41+ clip: true
42+
43+ Behavior on height {
44+ UbuntuNumberAnimation {
45+ id: heightAnimation
46+ duration: UbuntuAnimation.SnapDuration
47+ }
48+ }
49+
50+ Flickable {
51+ id: flickable
52+
53+ anchors.fill: parent
54+
55+ contentHeight: column.height
56+ contentWidth: width
57+
58+ Column {
59+ id: column
60+ width: parent.width
61+
62+ // TODO: check if SDK ListItems could be used here
63+ // and if not make them be useful since this is a quite common pattern
64+
65+ AbstractButton {
66+ id: backButton
67+ objectName: "backButton"
68+ width: parent.width
69+ visible: department && !department.isRoot || false
70+ height: itemHeight
71+
72+ onClicked: root.goBackToParentClicked();
73+
74+ Image {
75+ id: backImage
76+ anchors {
77+ verticalCenter: parent.verticalCenter
78+ left: parent.left
79+ leftMargin: units.gu(2)
80+ }
81+ source: "image://theme/back"
82+ sourceSize.height: parent.height - units.gu(3)
83+ sourceSize.width: units.gu(3)
84+ fillMode: Image.PreserveAspectFit
85+ }
86+
87+ Label {
88+ anchors {
89+ verticalCenter: parent.verticalCenter
90+ left: backImage.right
91+ leftMargin: units.gu(0.5)
92+ }
93+ text: department ? department.parentLabel : ""
94+ color: "gray" // TODO remove once we're a separate app
95+ }
96+
97+ Rectangle {
98+ anchors {
99+ bottom: parent.bottom
100+ left: parent.left
101+ right: parent.right
102+ leftMargin: units.gu(2)
103+ rightMargin: units.gu(2)
104+ }
105+ color: "gray"
106+ opacity: 0.2
107+ height: units.dp(1)
108+ }
109+ }
110+
111+ AbstractButton {
112+ id: allButton
113+ objectName: "allButton"
114+ width: parent.width
115+ visible: department && (!department.isRoot || (root.currentDepartment && !root.currentDepartment.isRoot && root.currentDepartment.parentDepartmentId == department.departmentId)) || false
116+ height: itemHeight
117+
118+ Label {
119+ anchors {
120+ verticalCenter: parent.verticalCenter
121+ left: parent.left
122+ leftMargin: units.gu(2)
123+ }
124+ text: department ? (department.allLabel != "" ? department.allLabel : department.label) : ""
125+ font.bold: true
126+ color: "gray" // TODO remove once we're a separate app
127+ }
128+
129+ Rectangle {
130+ anchors {
131+ bottom: parent.bottom
132+ left: parent.left
133+ right: parent.right
134+ leftMargin: units.gu(2)
135+ rightMargin: units.gu(2)
136+ }
137+ color: "gray"
138+ opacity: 0.2
139+ height: units.dp(1)
140+ }
141+
142+ onClicked: root.allDepartmentClicked();
143+ }
144+
145+ Repeater {
146+ model: department && department.loaded ? department : null
147+ clip: true
148+ delegate: AbstractButton {
149+ objectName: root.objectName + "child" + index
150+ height: root.itemHeight
151+ width: root.width
152+
153+ onClicked: root.enterDepartment(departmentId, hasChildren)
154+
155+ Label {
156+ anchors {
157+ verticalCenter: parent.verticalCenter
158+ left: parent.left
159+ leftMargin: units.gu(2)
160+ }
161+ text: label
162+ color: "gray" // TODO remove once we're a separate app
163+ }
164+
165+ Image {
166+ anchors {
167+ verticalCenter: parent.verticalCenter
168+ right: parent.right
169+ rightMargin: units.gu(2)
170+ }
171+ source: hasChildren ? "image://theme/chevron" : "graphics/tick.png"
172+ sourceSize.height: parent.height - units.gu(3)
173+ sourceSize.width: units.gu(3)
174+ fillMode: Image.PreserveAspectFit
175+ visible: hasChildren || isActive
176+ }
177+
178+ Rectangle {
179+ anchors {
180+ bottom: parent.bottom
181+ left: parent.left
182+ right: parent.right
183+ leftMargin: units.gu(2)
184+ rightMargin: units.gu(2)
185+ }
186+ color: "gray"
187+ opacity: 0.1
188+ height: units.dp(1)
189+ visible: index != department.count - 1
190+ }
191+ }
192+ }
193+ }
194+ }
195+}
qml/Dash/DashRenderer.qml−7
@@ -25,8 +25,6 @@ Item {
2525
2626 property int collapsedHeight: height
2727
28− property int margins: 0
29−
3028 property int uncollapsedHeight: height
3129
3230 property int displayMarginBeginning: 0
@@ -35,11 +33,6 @@ Item {
3533
3634 property real originY: 0
3735
38− property real verticalSpacing: 0
39−
40− // The current item of the renderer
41− property var currentItem
42−
4336 // The model to renderer
4437 property var model
4538
qml/Dash/GenericScopeView.qml+10−1
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2014 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -147,6 +147,7 @@ FocusScope {
147147 source: {
148148 switch (cardTool.categoryLayout) {
149149 case "carousel": return "CardCarousel.qml";
150+ case "vertical-journal": return "CardVerticalJournal.qml";
150151 case "running-apps": return "Apps/RunningApplicationsGrid.qml";
151152 case "grid":
152153 default: return "CardFilterGrid.qml";
@@ -202,6 +203,14 @@ FocusScope {
202203 previewListView.currentIndex = index;
203204 previewListView.open = true
204205 }
206+ onExpandableChanged: {
207+ // This can happen with the VJ that doesn't know how height it will be on creation
208+ // so doesn't set expandable until a bit too late for onLoaded
209+ if (rendererLoader.item.expandable) {
210+ var shouldFilter = baseItem.category != categoryView.expandedCategoryId;
211+ rendererLoader.item.setFilter(shouldFilter, false /*animate*/);
212+ }
213+ }
205214 }
206215 Connections {
207216 target: categoryView
qml/Dash/ScopeItem.qml+12
@@ -31,6 +31,10 @@ Item {
3131 signal gotoScope(string scopeId)
3232 signal openScope(var scope)
3333
34+ // TODO see how much code we can share
35+ // between here and other Dash parts, it's starting to have
36+ // too much duplicated code with the DashDepartments, etc
37+
3438 Item {
3539 id: scopeViewHolder
3640
@@ -79,6 +83,14 @@ Item {
7983 fontSize: "x-large"
8084 elide: Text.ElideRight
8185 }
86+
87+ bottomItem: DashDepartments {
88+ scope: root.scope
89+ width: parent.width <= units.gu(60) ? parent.width : units.gu(40)
90+ anchors.right: parent.right
91+ windowHeight: root.height
92+ windowWidth: root.width
93+ }
8294 }
8395 }
8496
qml/Dash/graphics/tick@30.pngnew file

Binary file not shown.

qml/Notifications/Notification.qml+13
@@ -15,6 +15,7 @@
1515 */
1616
1717import QtQuick 2.0
18+import QtMultimedia 5.0
1819import Ubuntu.Components 0.1
1920import Unity.Notifications 1.0
2021import QMenuModel 0.1
@@ -66,6 +67,18 @@ Item {
6667 return result;
6768 }
6869
70+ Audio {
71+ id: sound
72+ objectName: "sound"
73+ source: hints["suppress-sound"] != "" ? hints["sound-file"] : undefined
74+ }
75+
76+ onOpacityChanged: {
77+ if (opacity == 1.0 && hints["suppress-sound"] != "true" && sound.source) {
78+ sound.play();
79+ }
80+ }
81+
6982 Behavior on height {
7083 id: normalHeightBehavior
7184
qml/Shell.qml+25
@@ -31,6 +31,7 @@ import "Panel"
3131import "Components"
3232import "Notifications"
3333import Unity.Notifications 1.0 as NotificationBackend
34+import Unity.Session 0.1
3435
3536FocusScope {
3637 id: shell
@@ -267,6 +268,30 @@ FocusScope {
267268 }
268269 }
269270
271+ Connections {
272+ target: DBusUnitySessionService
273+
274+ function closeAllApps() {
275+ while (true) {
276+ var app = ApplicationManager.get(0);
277+ if (app === null) {
278+ break;
279+ }
280+ ApplicationManager.stopApplication(app.appId);
281+ }
282+ }
283+
284+ onLogoutRequested: {
285+ // TODO: Display a dialog to ask the user to confirm.
286+ DBusUnitySessionService.Logout();
287+ }
288+
289+ onLogoutReady: {
290+ closeAllApps();
291+ Qt.quit();
292+ }
293+ }
294+
270295 Loader {
271296 id: applicationsDisplayLoader
272297 anchors.fill: parent
src/main.cpp+1
@@ -161,6 +161,7 @@ Load the testability driver");
161161
162162 view->setSource(source);
163163 view->setColor("transparent");
164+ QObject::connect(view->engine(), SIGNAL(quit()), application, SLOT(quit()));
164165
165166 if (qgetenv("QT_QPA_PLATFORM") == "ubuntu" || isUbuntuMirServer || parser.isSet(fullscreenOption)) {
166167 view->showFullScreen();
tests/mocks/Unity/CMakeLists.txt+2
@@ -22,12 +22,14 @@ set(UnityQML_SOURCES
2222 fake_scope.cpp
2323 fake_scopes.cpp
2424 fake_categories.cpp
25+ fake_department.cpp
2526 fake_resultsmodel.cpp
2627 fake_previewmodel.cpp
2728 fake_previewstack.cpp
2829 fake_previewwidgetmodel.cpp
2930 fake_unity_plugin.cpp
3031 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/CategoriesInterface.h
32+ ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/DepartmentInterface.h
3133 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/PreviewModelInterface.h
3234 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/PreviewStackInterface.h
3335 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/PreviewWidgetModelInterface.h
tests/mocks/Unity/fake_department.cppnew file+127
@@ -0,0 +1,127 @@
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 "fake_department.h"
18+
19+#include "fake_scope.h"
20+
21+#include <QTimer>
22+
23+Department::Department(const QString& departmentId, const QString& label, const QString& allLabel, const QString& parentId, const QString& parentLabel, Scope* scope)
24+ : m_departmentId(departmentId)
25+ , m_label(label)
26+ , m_allLabel(allLabel)
27+ , m_parentId(parentId)
28+ , m_parentLabel(parentLabel)
29+ , m_loaded(false)
30+ , m_scope(scope)
31+{
32+ QTimer::singleShot(1500, this, SLOT(slotLoaded()));
33+ connect(scope, SIGNAL(currentDepartmentIdChanged()), this, SLOT(slotCurrentDepartmentChanged()));
34+}
35+
36+QString Department::departmentId() const
37+{
38+ return m_departmentId;
39+}
40+
41+QString Department::label() const
42+{
43+ return m_label;
44+}
45+
46+QString Department::allLabel() const
47+{
48+ return m_allLabel;
49+}
50+
51+QString Department::parentDepartmentId() const
52+{
53+ return m_parentId;
54+}
55+
56+QString Department::parentLabel() const
57+{
58+ return m_parentLabel;
59+}
60+
61+void Department::slotLoaded()
62+{
63+ m_loaded = true;
64+ Q_EMIT loadedChanged();
65+}
66+
67+bool Department::loaded() const
68+{
69+ return m_loaded;
70+}
71+
72+int Department::count() const
73+{
74+ return rowCount();
75+}
76+
77+bool Department::isRoot() const
78+{
79+ return m_departmentId == "root";
80+}
81+
82+int Department::rowCount(const QModelIndex & /*parent*/) const
83+{
84+ if (!m_loaded || m_departmentId.startsWith("child") || m_departmentId == "middle3")
85+ return 0;
86+ else
87+ return 8;
88+}
89+
90+QHash<int, QByteArray> Department::roleNames() const
91+{
92+ QHash<int, QByteArray> res;
93+ res[RoleDepartmentId] = "departmentId";
94+ res[RoleLabel] = "label";
95+ res[RoleHasChildren] = "hasChildren";
96+ res[RoleIsActive] = "isActive";
97+ return res;
98+}
99+
100+QVariant Department::data(const QModelIndex &index, int role) const
101+{
102+ switch (role) {
103+ case RoleDepartmentId:
104+ if (m_departmentId == "root")
105+ return QString("middle%1").arg(index.row());
106+ else if (m_departmentId.startsWith("middle"))
107+ return QString("child%1%2").arg(m_departmentId).arg(index.row());
108+ break;
109+ case RoleLabel:
110+ return QString("%1Child%2").arg(m_departmentId).arg(index.row());
111+ break;
112+ case RoleHasChildren:
113+ return m_departmentId == "root" && index.row() != 3;
114+ break;
115+ case RoleIsActive:
116+ return m_scope->currentDepartmentId() == data(index, RoleDepartmentId);
117+ break;
118+ }
119+ return QVariant();
120+}
121+
122+void Department::slotCurrentDepartmentChanged()
123+{
124+ // This is wasteful, should only emit it if really something changed in this
125+ // deparment, but this is a mock, so no need to optimize
126+ Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), QVector<int>() << RoleIsActive);
127+}
tests/mocks/Unity/fake_department.hnew file+61
@@ -0,0 +1,61 @@
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+#ifndef FAKE_DEPARTMENT_H
18+#define FAKE_DEPARTMENT_H
19+
20+#include <unity/shell/scopes/DepartmentInterface.h>
21+
22+class Scope;
23+
24+class Department : public unity::shell::scopes::DepartmentInterface
25+{
26+ Q_OBJECT
27+
28+public:
29+ Department(const QString& departmentId, const QString& label, const QString& allLabel, const QString& parentId, const QString& parentLabel, Scope* scope);
30+
31+ QString departmentId() const override;
32+ QString label() const override;
33+ QString allLabel() const override;
34+ QString parentDepartmentId() const override;
35+ QString parentLabel() const override;
36+ bool loaded() const override;
37+ int count() const override;
38+ bool isRoot() const override;
39+
40+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
41+ QHash<int, QByteArray> roleNames() const;
42+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
43+
44+public Q_SLOTS:
45+ void slotCurrentDepartmentChanged();
46+
47+private Q_SLOTS:
48+ void slotLoaded();
49+
50+private:
51+ QString m_departmentId;
52+ QString m_label;
53+ QString m_allLabel;
54+ QString m_parentId;
55+ QString m_parentLabel;
56+ bool m_loaded;
57+ QString m_currentDepartment;
58+ Scope *m_scope;
59+};
60+
61+#endif // FAKE_DEPARTMENT_H
tests/mocks/Unity/fake_scope.cpp+36−1
@@ -15,6 +15,7 @@
1515 */
1616
1717#include "fake_scope.h"
18+#include "fake_department.h"
1819#include "fake_resultsmodel.h"
1920
2021Scope::Scope(QObject* parent) : Scope(QString(), QString(), false, parent)
@@ -28,6 +29,7 @@ Scope::Scope(QString const& id, QString const& name, bool visible, QObject* pare
2829 , m_visible(visible)
2930 , m_searching(false)
3031 , m_isActive(false)
32+ , m_currentDeparment("root")
3133 , m_previewRendererName("preview-generic")
3234 , m_categories(new Categories(20, this))
3335{
@@ -102,7 +104,7 @@ void Scope::setFormFactor(const QString &str) {
102104void Scope::setActive(const bool active) {
103105 if (active != m_isActive) {
104106 m_isActive = active;
105− Q_EMIT isActiveChanged(active);
107+ Q_EMIT isActiveChanged();
106108 }
107109}
108110
@@ -142,3 +144,36 @@ void Scope::closeScope(unity::shell::scopes::ScopeInterface* /*scope*/)
142144{
143145 qFatal("Scope::closeScope is not implemented");
144146}
147+
148+QString Scope::currentDepartmentId() const
149+{
150+ return m_currentDeparment;
151+}
152+
153+bool Scope::hasDepartments() const
154+{
155+ return true;
156+}
157+
158+unity::shell::scopes::DepartmentInterface* Scope::getDepartment(const QString& id)
159+{
160+ if (id.isEmpty())
161+ return nullptr;
162+
163+ QString parentId;
164+ QString parentLabel;
165+ if (id.startsWith("middle")) {
166+ parentId = "root";
167+ parentLabel = "root";
168+ } else if (id.startsWith("child")) {
169+ parentId = id.mid(5, 7);
170+ parentLabel = parentId;
171+ }
172+ return new Department(id, id, "all"+id, parentId, parentLabel, this);
173+}
174+
175+void Scope::loadDepartment(const QString& id)
176+{
177+ m_currentDeparment = id;
178+ Q_EMIT currentDepartmentIdChanged();
179+}
tests/mocks/Unity/fake_scope.h+6
@@ -59,6 +59,11 @@ public:
5959 Q_INVOKABLE void cancelActivation() override;
6060 Q_INVOKABLE void closeScope(unity::shell::scopes::ScopeInterface* scope) override;
6161
62+ QString currentDepartmentId() const override;
63+ bool hasDepartments() const override;
64+ Q_INVOKABLE unity::shell::scopes::DepartmentInterface* getDepartment(const QString& id) override;
65+ Q_INVOKABLE void loadDepartment(const QString& id) override;
66+
6267protected:
6368
6469 QString m_id;
@@ -71,6 +76,7 @@ protected:
7176 bool m_visible;
7277 bool m_searching;
7378 bool m_isActive;
79+ QString m_currentDeparment;
7480
7581 QString m_previewRendererName;
7682
tests/mocks/Unity/fake_scopes.cpp+2−2
@@ -49,7 +49,7 @@ void Scopes::updateScopes()
4949
5050 if (!m_loaded) {
5151 m_loaded = true;
52− Q_EMIT loadedChanged(m_loaded);
52+ Q_EMIT loadedChanged();
5353 }
5454}
5555
@@ -65,7 +65,7 @@ void Scopes::clear()
6565
6666 if (m_loaded) {
6767 m_loaded = false;
68− Q_EMIT loadedChanged(m_loaded);
68+ Q_EMIT loadedChanged();
6969 }
7070}
7171
tests/mocks/Unity/fake_unity_plugin.cpp+2
@@ -22,6 +22,7 @@
2222// local
2323#include "fake_scopes.h"
2424#include "fake_categories.h"
25+#include "fake_department.h"
2526#include "fake_previewmodel.h"
2627#include "fake_previewwidgetmodel.h"
2728#include "fake_resultsmodel.h"
@@ -42,6 +43,7 @@ void FakeUnityPlugin::registerTypes(const char *uri)
4243 qmlRegisterType<Scopes>(uri, 0, 2, "Scopes");
4344 qmlRegisterType<Scope>(uri, 0, 2, "MockScope");
4445 qmlRegisterUncreatableType<unity::shell::scopes::ScopeInterface>(uri, 0, 2, "Scope", "Can't create Scope object in QML.");
46+ qmlRegisterUncreatableType<unity::shell::scopes::DepartmentInterface>(uri, 0, 2, "Department", "Can't create Department object in QML.");
4547 qmlRegisterUncreatableType<unity::shell::scopes::CategoriesInterface>(uri, 0, 2, "Categories", "Can't create Categories object in QML.");
4648 qmlRegisterUncreatableType<ResultsModel>(uri, 0, 2, "ResultsModel", "Can't create ResultsModel object in QML.");
4749 qmlRegisterType<PreviewModel>(uri, 0, 2, "FakePreviewModel");
tests/plugins/Dash/cardcreator/1.res+14−9
@@ -16,8 +16,8 @@ onArtShapeBorderSourceChanged: { if (artShapeBorderSource !== undefined && artSh
1616readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
1717Item {
1818 id: artShapeHolder;
19− height: root.fixedArtShapeSize.height != -1 ? root.fixedArtShapeSize.height : artShapeLoader.height;
20− width: root.fixedArtShapeSize.width != -1 ? root.fixedArtShapeSize.width : artShapeLoader.width;
19+ height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
20+ width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
2121 anchors { horizontalCenter: parent.horizontalCenter; }
2222 Loader {
2323 id: artShapeLoader;
@@ -28,14 +28,19 @@ Item {
2828 sourceComponent: UbuntuShape {
2929 id: artShape;
3030 objectName: "artShape";
31− radius: "medium";
32− readonly property real aspect: components !== undefined ? components["art"]["aspect-ratio"] : 1;
33− readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect;
34− Component.onCompleted: { updateWidthHeightBindings(); if (artShapeBorderSource !== undefined) borderSource = artShapeBorderSource; }
35− onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings();
31+ radius: "medium";
3632 visible: image.status == Image.Ready;
33+ readonly property real fixedArtShapeSizeAspect: (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) ? root.fixedArtShapeSize.width / root.fixedArtShapeSize.height : -1;
34+ readonly property real aspect: fixedArtShapeSizeAspect > 0 ? fixedArtShapeSizeAspect : components !== undefined ? components["art"]["aspect-ratio"] : 1;
35+ readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect;
36+ Component.onCompleted: { updateWidthHeightBindings(); if (artShapeBorderSource !== undefined) borderSource = artShapeBorderSource; }
37+ onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings();
38+ Connections { target: root; onFixedArtShapeSizeChanged: updateWidthHeightBindings(); }
3739 function updateWidthHeightBindings() {
38− if (aspectSmallerThanImageAspect) {
40+ if (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) {
41+ width = root.fixedArtShapeSize.width;
42+ height = root.fixedArtShapeSize.height;
43+ } else if (aspectSmallerThanImageAspect) {
3944 width = Qt.binding(function() { return !visible ? 0 : image.width });
4045 height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect });
4146 } else {
@@ -56,7 +61,7 @@ Item {
5661 }
5762 }
5863 }
59−readonly property int headerHeight: titleLabel.height + titleLabel.anchors.topMargin * 2;
64+readonly property int headerHeight: titleLabel.height;
6065Label {
6166 id: titleLabel;
6267 objectName: "titleLabel";
tests/plugins/Dash/cardcreator/2.res+2−1
@@ -46,12 +46,13 @@ Loader {
4646 }
4747 }
4848readonly property size artShapeSize: Qt.size(-1, -1);
49−readonly property int headerHeight: row.height + row.margins * 2;
49+readonly property int headerHeight: row.height;
5050Row {
5151 id: row;
5252 objectName: "outerRow";
5353 property real margins: units.gu(1);
5454 spacing: margins;
55+ height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight;
5556 anchors { top: parent.top;
5657 topMargin: units.gu(1);
5758 left: parent.left;
tests/plugins/Dash/cardcreator/3.res+20−15
@@ -16,8 +16,8 @@ onArtShapeBorderSourceChanged: { if (artShapeBorderSource !== undefined && artSh
1616readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
1717Item {
1818 id: artShapeHolder;
19− height: root.fixedArtShapeSize.height != -1 ? root.fixedArtShapeSize.height : artShapeLoader.height;
20− width: root.fixedArtShapeSize.width != -1 ? root.fixedArtShapeSize.width : artShapeLoader.width;
19+ height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
20+ width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
2121 anchors { horizontalCenter: parent.horizontalCenter; }
2222 Loader {
2323 id: artShapeLoader;
@@ -29,19 +29,24 @@ Item {
2929 id: artShape;
3030 objectName: "artShape";
3131 radius: "medium";
32− readonly property real aspect: components !== undefined ? components["art"]["aspect-ratio"] : 1;
33− readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect;
34− Component.onCompleted: { updateWidthHeightBindings(); if (artShapeBorderSource !== undefined) borderSource = artShapeBorderSource; }
35− onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings();
3632 visible: image.status == Image.Ready;
37− function updateWidthHeightBindings() {
38− if (aspectSmallerThanImageAspect) {
39− width = Qt.binding(function() { return !visible ? 0 : image.width });
40− height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect });
41− } else {
42− width = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.width : height * image.aspect });
43− height = Qt.binding(function() { return !visible ? 0 : image.height });
44− }
33+ readonly property real fixedArtShapeSizeAspect: (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) ? root.fixedArtShapeSize.width / root.fixedArtShapeSize.height : -1;
34+ readonly property real aspect: fixedArtShapeSizeAspect > 0 ? fixedArtShapeSizeAspect : components !== undefined ? components["art"]["aspect-ratio"] : 1;
35+ readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect;
36+ Component.onCompleted: { updateWidthHeightBindings(); if (artShapeBorderSource !== undefined) borderSource = artShapeBorderSource; }
37+ onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings();
38+ Connections { target: root; onFixedArtShapeSizeChanged: updateWidthHeightBindings(); }
39+ function updateWidthHeightBindings() {
40+ if (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) {
41+ width = root.fixedArtShapeSize.width;
42+ height = root.fixedArtShapeSize.height;
43+ } else if (aspectSmallerThanImageAspect) {
44+ width = Qt.binding(function() { return !visible ? 0 : image.width });
45+ height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect });
46+ } else {
47+ width = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.width : height * image.aspect });
48+ height = Qt.binding(function() { return !visible ? 0 : image.height });
49+ }
4550 }
4651 image: Image {
4752 objectName: "artImage";
@@ -56,7 +61,7 @@ Item {
5661 }
5762 }
5863 }
59−readonly property int headerHeight: titleLabel.height + titleLabel.anchors.topMargin * 2 + subtitleLabel.height + subtitleLabel.anchors.topMargin;
64+readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;
6065Label {
6166 id: titleLabel;
6267 objectName: "titleLabel";
tests/plugins/Dash/cardcreator/4.res+2−1
@@ -13,12 +13,13 @@ AbstractButton {
1313 property bool showHeader: true;
1414 implicitWidth: childrenRect.width;
1515readonly property size artShapeSize: Qt.size(-1, -1);
16−readonly property int headerHeight: row.height + row.margins * 2;
16+readonly property int headerHeight: row.height;
1717Row {
1818 id: row;
1919 objectName: "outerRow";
2020 property real margins: units.gu(1);
2121 spacing: margins;
22+ height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight;
2223 anchors { top: parent.top;
2324 topMargin: units.gu(1);
2425 left: parent.left;
tests/plugins/Dash/cardcreator/5.res+21−16
@@ -16,8 +16,8 @@ onArtShapeBorderSourceChanged: { if (artShapeBorderSource !== undefined && artSh
1616readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
1717Item {
1818 id: artShapeHolder;
19− height: root.fixedArtShapeSize.height != -1 ? root.fixedArtShapeSize.height : artShapeLoader.height;
20− width: root.fixedArtShapeSize.width != -1 ? root.fixedArtShapeSize.width : artShapeLoader.width;
19+ height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
20+ width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
2121 anchors { horizontalCenter: parent.horizontalCenter; }
2222 Loader {
2323 id: artShapeLoader;
@@ -29,19 +29,24 @@ Item {
2929 id: artShape;
3030 objectName: "artShape";
3131 radius: "medium";
32− readonly property real aspect: components !== undefined ? components["art"]["aspect-ratio"] : 1;
33− readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect;
34− Component.onCompleted: { updateWidthHeightBindings(); if (artShapeBorderSource !== undefined) borderSource = artShapeBorderSource; }
35− onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings();
3632 visible: image.status == Image.Ready;
37− function updateWidthHeightBindings() {
38− if (aspectSmallerThanImageAspect) {
39− width = Qt.binding(function() { return !visible ? 0 : image.width });
40− height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect });
41− } else {
42− width = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.width : height * image.aspect });
43− height = Qt.binding(function() { return !visible ? 0 : image.height });
44− }
33+ readonly property real fixedArtShapeSizeAspect: (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) ? root.fixedArtShapeSize.width / root.fixedArtShapeSize.height : -1;
34+ readonly property real aspect: fixedArtShapeSizeAspect > 0 ? fixedArtShapeSizeAspect : components !== undefined ? components["art"]["aspect-ratio"] : 1;
35+ readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect;
36+ Component.onCompleted: { updateWidthHeightBindings(); if (artShapeBorderSource !== undefined) borderSource = artShapeBorderSource; }
37+ onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings();
38+ Connections { target: root; onFixedArtShapeSizeChanged: updateWidthHeightBindings(); }
39+ function updateWidthHeightBindings() {
40+ if (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) {
41+ width = root.fixedArtShapeSize.width;
42+ height = root.fixedArtShapeSize.height;
43+ } else if (aspectSmallerThanImageAspect) {
44+ width = Qt.binding(function() { return !visible ? 0 : image.width });
45+ height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect });
46+ } else {
47+ width = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.width : height * image.aspect });
48+ height = Qt.binding(function() { return !visible ? 0 : image.height });
49+ }
4550 }
4651 image: Image {
4752 objectName: "artImage";
@@ -68,7 +73,7 @@ Loader {
6873 visible: showHeader && status == Loader.Ready;
6974 sourceComponent: ShaderEffect {
7075 id: overlay;
71− height: fixedHeaderHeight != -1 ? fixedHeaderHeight : headerHeight;
76+ height: fixedHeaderHeight > 0 ? fixedHeaderHeight + units.gu(1) * 2 : headerHeight;
7277 opacity: 0.6;
7378 property var source: ShaderEffectSource {
7479 id: shaderSource;
@@ -96,7 +101,7 @@ Loader {
96101 }";
97102 }
98103 }
99−readonly property int headerHeight: titleLabel.height + titleLabel.anchors.topMargin * 2 + subtitleLabel.height + subtitleLabel.anchors.topMargin;
104+readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;
100105Label {
101106 id: titleLabel;
102107 objectName: "titleLabel";
tests/plugins/Unity/CMakeLists.txt+1
@@ -1,2 +1,3 @@
11add_subdirectory(Indicators)
22add_subdirectory(Launcher)
3+add_subdirectory(Session)
tests/plugins/Unity/Session/CMakeLists.txtnew file+20
@@ -0,0 +1,20 @@
1+include_directories(
2+ ${CMAKE_CURRENT_BINARY_DIR}
3+ ${CMAKE_SOURCE_DIR}/plugins/Unity/Session
4+)
5+
6+add_definitions(-DSM_BUSNAME=sessionBus)
7+add_definitions(-DSRCDIR="${CMAKE_CURRENT_SOURCE_DIR}")
8+add_definitions(-DSESSION_TESTING)
9+
10+### SessionBackendTest
11+set(testBackendCommand dbus-test-runner --task ${CMAKE_CURRENT_BINARY_DIR}/sessionbackendtestExec
12+ --parameter -o --parameter ${CMAKE_BINARY_DIR}/sessionbackendtest.xml,xunitxml
13+ --parameter -o --parameter -,txt)
14+add_test(NAME sessionbackendtest COMMAND ${testBackendCommand})
15+add_custom_target(sessionbackendtest ${testBackendCommand})
16+add_executable(sessionbackendtestExec
17+ sessionbackendtest.cpp
18+ ${CMAKE_SOURCE_DIR}/plugins/Unity/Session/dbusunitysessionservice.cpp
19+)
20+qt5_use_modules(sessionbackendtestExec Test Core Qml DBus)
tests/plugins/Unity/Session/sessionbackendtest.cppnew file+64
@@ -0,0 +1,64 @@
1+/*
2+ * Copyright 2013 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Authors:
17+ * Ying-Chun Liu (PaulLiu) <paul.liu@canonical.com>
18+ */
19+
20+#include <QtTest>
21+#include <QDBusConnection>
22+#include <QDBusMessage>
23+#include <QDBusInterface>
24+#include <QDBusReply>
25+#include <QDBusVariant>
26+
27+#include "dbusunitysessionservice.h"
28+
29+class SessionBackendTest : public QObject
30+{
31+ Q_OBJECT
32+
33+private Q_SLOTS:
34+
35+ void initTestCase() {
36+ }
37+
38+ void testDbusIfaceMethods_data() {
39+ QTest::addColumn<QString>("method");
40+
41+ QTest::newRow("Logout") << "RequestLogout";
42+ }
43+
44+ void testDbusIfaceMethods() {
45+ QFETCH(QString, method);
46+
47+ DBusUnitySessionService dbusUnitySessionService;
48+
49+ QDBusConnection con = QDBusConnection::sessionBus();
50+ QDBusInterface interface ("com.canonical.Unity",
51+ "/com/canonical/Unity/Session",
52+ "com.canonical.Unity.Session",
53+ con);
54+ QDBusReply<void> reply;
55+
56+ QCOMPARE(interface.isValid(), true);
57+ reply = interface.call(method);
58+ QCOMPARE(reply.isValid(), true);
59+
60+ }
61+};
62+
63+QTEST_GUILESS_MAIN(SessionBackendTest)
64+#include "sessionbackendtest.moc"
tests/qmltests/CMakeLists.txt+1
@@ -29,6 +29,7 @@ add_qml_test(Components LazyImage)
2929add_qml_test(Components Rating)
3030add_qml_test(Components ResponsiveFlowView)
3131add_qml_test(Components ResponsiveGridView)
32+add_qml_test(Components ResponsiveVerticalJournal IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/plugins)
3233add_qml_test(Components SeeMore)
3334add_qml_test(Components Showable)
3435add_qml_test(Components PageHeaderLabel)
tests/qmltests/Components/tst_ResponsiveVerticalJournal.qmlnew file+183
@@ -0,0 +1,183 @@
1+/*
2+ * Copyright 2013-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 ".."
20+import "../../../qml/Components"
21+import Ubuntu.Components.ListItems 0.1 as ListItem
22+import Ubuntu.Components 0.1
23+import Utils 0.1
24+import Unity.Test 0.1 as UT
25+
26+Item {
27+ width: units.gu(80) + controls.width
28+ height: units.gu(80)
29+
30+ Column {
31+ id: controls
32+ width: units.gu(40)
33+ height: parent.height
34+ anchors.top: parent.top
35+ anchors.right: parent.right
36+ ListItem.ValueSelector {
37+ id: cardSizeSelector
38+ text: "card-size"
39+ // small, medium, large card sizes
40+ values: [units.gu(12), units.gu(18.5), units.gu(38)]
41+ selectedIndex: 0
42+ }
43+ ListItem.ValueSelector {
44+ id: minColumnSpacingSelector
45+ text: "minColumnSpacing"
46+ values: [0, units.gu(2), units.gu(8), units.gu(25)]
47+ selectedIndex: 0
48+ }
49+ ListItem.ValueSelector {
50+ id: rowSpacingSelector
51+ text: "rowSpacing"
52+ values: [units.gu(1), units.gu(2), units.gu(4), units.gu(8)]
53+ selectedIndex: 1
54+ }
55+ }
56+
57+ ListModel {
58+ id: fakeModel
59+ ListElement { name: "A" }
60+ ListElement { name: "B" }
61+ ListElement { name: "C" }
62+ ListElement { name: "D" }
63+ ListElement { name: "E" }
64+ ListElement { name: "F" }
65+ ListElement { name: "G" }
66+ ListElement { name: "H" }
67+ ListElement { name: "I" }
68+ ListElement { name: "J" }
69+ ListElement { name: "K" }
70+ ListElement { name: "L" }
71+ ListElement { name: "M" }
72+ ListElement { name: "N" }
73+ ListElement { name: "O" }
74+ ListElement { name: "P" }
75+ ListElement { name: "Q" }
76+ ListElement { name: "R" }
77+ ListElement { name: "S" }
78+ ListElement { name: "T" }
79+ ListElement { name: "U" }
80+ }
81+
82+ SortFilterProxyModel {
83+ id: wrappedFakeModel
84+ model: fakeModel
85+ }
86+
87+ Rectangle {
88+ id: journalRect
89+ height: parent.height
90+ color: "grey"
91+ anchors.top: parent.top
92+ anchors.left: parent.left
93+ anchors.right: controls.left
94+
95+ ResponsiveVerticalJournal {
96+ id: journal
97+ anchors.fill: parent
98+ model: wrappedFakeModel
99+ minimumColumnSpacing: minColumnSpacingSelector.values[minColumnSpacingSelector.selectedIndex]
100+ rowSpacing: rowSpacingSelector.values[rowSpacingSelector.selectedIndex]
101+ columnWidth: cardSizeSelector.values[cardSizeSelector.selectedIndex]
102+
103+ delegate: Rectangle {
104+ id: delegateItem
105+ // So that it can be identified by test code
106+ property bool isJournalDelegate: true
107+ objectName: "delegate" + index
108+ color: "grey"
109+ border.color: "red"
110+ border.width: 1
111+
112+ // width derived from Card's template['card-size']
113+ width: cardSizeSelector.values[cardSizeSelector.selectedIndex]
114+ height: Math.max(units.gu(8), Math.floor(Math.random() * 300))
115+
116+ Rectangle {
117+ color: "green"
118+ anchors.centerIn: parent
119+ width: units.gu(6)
120+ height: units.gu(6)
121+ Text {
122+ anchors.centerIn: parent
123+ text: name
124+ }
125+ }
126+
127+ Text { x:0; y:0; text:"(" + parent.x + ", " + parent.y + ")"}
128+ }
129+ }
130+ }
131+
132+ UT.UnityTestCase {
133+ name: "ResponsiveVerticalJournal"
134+ when: windowShown
135+
136+ function test_minimumColumnSpacing_data() {
137+ return [{minColumnSpacingIndex: 0, expectedColumns: 2},
138+ {minColumnSpacingIndex: 1, expectedColumns: 2},
139+ {minColumnSpacingIndex: 2, expectedColumns: 1}]
140+ }
141+
142+ // Test how minimumColumnSpacing affects column count.
143+ function test_minimumColumnSpacing(data) {
144+ cardSizeSelector.selectedIndex = 2 // large card
145+
146+ minColumnSpacingSelector.selectedIndex = data.minColumnSpacingIndex
147+
148+ tryCompareFunction(countJournalDelegatesOnFirstRow, data.expectedColumns)
149+ }
150+
151+ function test_maximumNumberOfColumns_data() {
152+ return [{cardSizeIndex: 0, expectedColumns: 6},
153+ {cardSizeIndex: 1, expectedColumns: 4},
154+ {cardSizeIndex: 2, expectedColumns: 2}]
155+ }
156+
157+ // Test how maximumNumberOfColumns and columnWidth affect column count.
158+ function test_maximumNumberOfColumns(data) {
159+ minColumnSpacingSelector.selectedIndex = 0 // no spacing
160+
161+ cardSizeSelector.selectedIndex = data.cardSizeIndex // columnWidth
162+
163+ tryCompareFunction(countJournalDelegatesOnFirstRow, data.expectedColumns)
164+ }
165+
166+ function countJournalDelegatesOnFirstRow() {
167+ return __countJournalDelegatesOnFirstRow(journal.visibleChildren, 0)
168+ }
169+
170+ function __countJournalDelegatesOnFirstRow(objList, total) {
171+ for (var i = 0; i < objList.length; ++i) {
172+ var child = objList[i];
173+ if (child.isJournalDelegate !== undefined && child.y === 0) {
174+ ++total;
175+ } else {
176+ total = __countJournalDelegatesOnFirstRow(
177+ child.visibleChildren, total)
178+ }
179+ }
180+ return total
181+ }
182+ }
183+}
tests/qmltests/Dash/tst_Card.qml+9
@@ -305,6 +305,15 @@ Rectangle {
305305 }
306306 }
307307
308+ function test_art_shape_fixed_size() {
309+ selector.selectedIndex = 6;
310+ card.fixedArtShapeSize = Qt.size( units.gu(8), units.gu(4) );
311+ waitForRendering(card);
312+ tryCompare(art, "width", units.gu(8));
313+ tryCompare(art, "height", units.gu(4));
314+ tryCompare(art, "fixedArtShapeSizeAspect", 2);
315+ }
316+
308317 function test_art_layout_data() {
309318 return [
310319 { tag: "Vertical", left: function() { return units.gu(1); }, index: 0},
tests/qmltests/Dash/tst_DashContent.qml+114−3
@@ -124,13 +124,13 @@ Item {
124124
125125 waitForRendering(categoryListView);
126126
127− categoryListView.contentY = units.gu(11);
128− console.log("contentY", categoryListView.contentY);
127+ categoryListView.contentY = units.gu(15);
128+ tryCompare(categoryListView, "contentY", units.gu(15));
129129
130130 var startX = dashContentList.width/2;
131131 var startY = dashContentList.height/2;
132132 touchFlick(dashContentList, startX - units.gu(2), startY, startX, startY);
133− tryCompare(categoryListView, "contentY", units.gu(11) - categoryListView.pageHeader.height);
133+ tryCompare(categoryListView, "contentY", units.gu(15) - categoryListView.pageHeader.height);
134134 }
135135
136136 function test_set_current_scope_reset() {
@@ -407,5 +407,116 @@ Item {
407407 var carouselLV = findChild(dashCategory1, "listView");
408408 verify(carouselLV.tileWidth / carouselLV.tileHeight == cardTool.components["art"]["aspect-ratio"]);
409409 }
410+
411+ function test_departments() {
412+ var dashDepartments = findChild(dashContent, "dashDepartments");
413+ compare(dashDepartments.visible, true);
414+ compare(dashDepartments.showList, false);
415+ waitForRendering(dashDepartments);
416+ mouseClick(dashDepartments, 0, 0);
417+ compare(dashDepartments.showList, true);
418+
419+ var departmentListView = findChild(dashDepartments, "departmentListView");
420+ compare(departmentListView.count, 1);
421+ tryCompare(departmentListView.currentItem.department, "loaded", true);
422+
423+ waitForRendering(departmentListView);
424+ waitForRendering(departmentListView.currentItem);
425+
426+ var allButton = findChild(dashDepartments, "allButton");
427+ compare(allButton.visible, false);
428+
429+ var department = findChild(dashDepartments, "department0child3");
430+ mouseClick(department, 0, 0);
431+ compare(dashDepartments.showList, false);
432+ tryCompare(dashDepartments.currentDepartment, "departmentId", "middle3");
433+ tryCompare(departmentListView.currentItem.department, "departmentId", "root");
434+
435+ mouseClick(dashDepartments, 0, 0);
436+ compare(dashDepartments.showList, true);
437+ waitForRendering(departmentListView);
438+ waitForRendering(departmentListView.currentItem);
439+ compare(allButton.visible, true);
440+
441+ mouseClick(allButton, 0, 0);
442+ compare(dashDepartments.showList, false);
443+ tryCompare(dashDepartments.currentDepartment, "departmentId", "root");
444+ tryCompare(departmentListView.currentItem.department, "departmentId", "root");
445+
446+ mouseClick(dashDepartments, 0, 0);
447+ compare(dashDepartments.showList, true);
448+ waitForRendering(departmentListView);
449+ waitForRendering(departmentListView.currentItem);
450+ compare(allButton.visible, false);
451+
452+ department = findChild(dashDepartments, "department0child2");
453+ mouseClick(department, 0, 0);
454+ compare(dashDepartments.showList, true);
455+ tryCompare(dashDepartments.currentDepartment, "departmentId", "middle2");
456+ tryCompare(departmentListView.currentItem.department, "departmentId", "middle2");
457+
458+ var departmentList1 = findChild(dashDepartments, "department1");
459+ allButton = findChild(departmentList1, "allButton");
460+ var backButton = findChild(findChild(departmentList1, "department1"), "backButton");
461+ compare(allButton.visible, true);
462+ compare(backButton.visible, true);
463+
464+ mouseClick(allButton, 0, 0);
465+ compare(dashDepartments.showList, false);
466+ tryCompare(dashDepartments.currentDepartment, "departmentId", "middle2");
467+ tryCompare(departmentListView.currentItem.department, "departmentId", "middle2");
468+
469+ mouseClick(dashDepartments, 0, 0);
470+ compare(dashDepartments.showList, true);
471+ waitForRendering(departmentListView);
472+ waitForRendering(departmentListView.currentItem);
473+ compare(allButton.visible, true);
474+ compare(backButton.visible, true);
475+
476+ tryCompare(departmentList1.department, "loaded", true);
477+ department = findChild(dashDepartments, "department1child2");
478+ mouseClick(department, 0, 0);
479+ compare(dashDepartments.showList, false);
480+ tryCompare(dashDepartments.currentDepartment, "departmentId", "childmiddle22");
481+ tryCompare(departmentListView.currentItem.department, "departmentId", "middle2");
482+
483+ mouseClick(dashDepartments, 0, 0);
484+ compare(dashDepartments.showList, true);
485+ waitForRendering(departmentListView);
486+ waitForRendering(departmentListView.currentItem);
487+
488+ tryCompare(departmentList1.department, "loaded", true);
489+ department = findChild(dashDepartments, "department1child3");
490+ mouseClick(department, 0, 0);
491+ compare(dashDepartments.showList, false);
492+ tryCompare(dashDepartments.currentDepartment, "departmentId", "childmiddle23");
493+ tryCompare(departmentListView.currentItem.department, "departmentId", "middle2");
494+
495+ mouseClick(dashDepartments, 0, 0);
496+ compare(dashDepartments.showList, true);
497+ waitForRendering(departmentListView);
498+ waitForRendering(departmentListView.currentItem);
499+ mouseClick(backButton, 0, 0);
500+
501+ tryCompare(dashDepartments.currentDepartment, "departmentId", "root");
502+ tryCompare(departmentListView.currentItem.department, "departmentId", "root");
503+ compare(dashDepartments.showList, true);
504+ mouseClick(dashDepartments, 0, 0);
505+ compare(dashDepartments.showList, false);
506+
507+ mouseClick(dashDepartments, 0, 0);
508+ compare(dashDepartments.showList, true);
509+ tryCompare(departmentListView.currentItem.department, "loaded", true);
510+ department = findChild(dashDepartments, "department0child2");
511+ mouseClick(department, 0, 0);
512+ compare(dashDepartments.showList, true);
513+ departmentList1 = findChild(dashDepartments, "department1");
514+ compare(departmentList1.department.loaded, false);
515+ tryCompare(dashDepartments.currentDepartment, "departmentId", "middle2");
516+ allButton = findChild(departmentList1, "allButton");
517+ tryCompare(dashDepartments.currentDepartment, "departmentId", "middle2");
518+ mouseClick(allButton, 0, 0);
519+ tryCompare(dashDepartments.currentDepartment, "departmentId", "middle2");
520+ }
410521 }
411522}
tests/qmltests/Notifications/tst_Notifications.qml+29−12
@@ -21,6 +21,7 @@ import "../../../qml/Notifications"
2121import Ubuntu.Components 0.1
2222import Unity.Test 0.1
2323import Unity.Notifications 1.0
24+import QtMultimedia 5.0
2425
2526Row {
2627 id: rootRow
@@ -245,7 +246,8 @@ Row {
245246 nonShapedIcon: false,
246247 secondaryIconVisible: true,
247248 buttonRowVisible: true,
248− buttonTinted: true
249+ buttonTinted: true,
250+ hasSound: false
249251 },
250252 {
251253 tag: "Ephemeral notification - icon-summary layout",
@@ -263,12 +265,15 @@ Row {
263265 nonShapedIcon: false,
264266 secondaryIconVisible: true,
265267 buttonRowVisible: false,
266− buttonTinted: false
268+ buttonTinted: false,
269+ hasSound: false
267270 },
268271 {
269272 tag: "Ephemeral notification - check suppression of secondary icon for icon-summary layout",
270273 type: Notification.Ephemeral,
271− hints: {"x-canonical-private-button-tint": "false"},
274+ hints: {"x-canonical-private-button-tint": "false",
275+ "sound-file": "dummy.ogg",
276+ "suppress-sound": "true"},
272277 summary: "New comment successfully published",
273278 body: "",
274279 icon: "",
@@ -282,12 +287,14 @@ Row {
282287 nonShapedIcon: false,
283288 secondaryIconVisible: true,
284289 buttonRowVisible: false,
285− buttonTinted: false
290+ buttonTinted: false,
291+ hasSound: false
286292 },
287293 {
288294 tag: "Interactive notification",
289295 type: Notification.Interactive,
290− hints: {"x-canonical-private-button-tint": "false"},
296+ hints: {"x-canonical-private-button-tint": "false",
297+ "sound-file": "dummy.ogg"},
291298 summary: "Interactive notification",
292299 body: "This is a notification that can be clicked",
293300 icon: "../graphics/avatars/amanda.png",
@@ -300,12 +307,14 @@ Row {
300307 nonShapedIcon: false,
301308 secondaryIconVisible: false,
302309 buttonRowVisible: false,
303− buttonTinted: false
310+ buttonTinted: false,
311+ hasSound: true
304312 },
305313 {
306314 tag: "Snap Decision without secondary icon and no button-tint",
307315 type: Notification.SnapDecision,
308− hints: {"x-canonical-private-button-tint": "false"},
316+ hints: {"x-canonical-private-button-tint": "false",
317+ "sound-file": "dummy.ogg"},
309318 summary: "Bro Coly",
310319 body: "At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.",
311320 icon: "../graphics/avatars/anna_olsson.png",
@@ -319,12 +328,14 @@ Row {
319328 nonShapedIcon: false,
320329 secondaryIconVisible: false,
321330 buttonRowVisible: true,
322− buttonTinted: false
331+ buttonTinted: false,
332+ hasSound: true
323333 },
324334 {
325335 tag: "Ephemeral notification",
326336 type: Notification.Ephemeral,
327− hints: {"x-canonical-private-button-tint": "false"},
337+ hints: {"x-canonical-private-button-tint": "false",
338+ "sound-file": "dummy.ogg"},
328339 summary: "Cole Raby",
329340 body: "I did not expect it to be that late.",
330341 icon: "../graphics/avatars/funky.png",
@@ -337,13 +348,15 @@ Row {
337348 nonShapedIcon: false,
338349 secondaryIconVisible: true,
339350 buttonRowVisible: false,
340− buttonTinted: false
351+ buttonTinted: false,
352+ hasSound: true
341353 },
342354 {
343355 tag: "Ephemeral notification with non-shaped icon",
344356 type: Notification.Ephemeral,
345357 hints: {"x-canonical-private-button-tint": "false",
346− "x-canonical-non-shaped-icon": "true"},
358+ "x-canonical-non-shaped-icon": "true",
359+ "sound-file": "dummy.ogg"},
347360 summary: "Contacts",
348361 body: "Synchronised contacts-database with cloud-storage.",
349362 icon: "../graphics/applicationIcons/contacts-app.png",
@@ -356,7 +369,8 @@ Row {
356369 nonShapedIcon: true,
357370 secondaryIconVisible: false,
358371 buttonRowVisible: false,
359− buttonTinted: false
372+ buttonTinted: false,
373+ hasSound: true
360374 }
361375 ]
362376 }
@@ -419,6 +433,9 @@ Row {
419433 compare(bodyLabel.visible, data.bodyVisible, "body-text visibility is incorrect")
420434 compare(buttonRow.visible, data.buttonRowVisible, "button visibility is incorrect")
421435
436+ var audioItem = findInvisibleChild(notification, "sound")
437+ tryCompare(audioItem, "playbackState", data.hasSound ? Audio.PlayingState : Audio.StoppedState)
438+
422439 if(data.buttonRowVisible) {
423440 var buttonCancel = findChild(buttonRow, "button1")
424441 var buttonAccept = findChild(buttonRow, "button0")