on xenial help sign in
Merge from trunk
commit567f2a35fbdce6975cf51b84d92d84d665efc3bf authorMichael Terry <michael.terry@canonical.com> dateMon 5 May 2014, 18:07 (12 years ago) parent0ec5075657fdca
36 files changed+1046−260
CMakeLists.txt+5−5 CODING+11−11 build → build.sh+1−2 debian/changelog+30 debian/control+9−10 debian/rules+1−2 plugins/DashViews/abstractdashview.cpp−34 plugins/DashViews/abstractdashview.h−17 plugins/DashViews/horizontaljournal.cpp−12 plugins/DashViews/horizontaljournal.h−4 plugins/DashViews/listviewwithpageheader.cpp−50 plugins/DashViews/listviewwithpageheader.h−20 plugins/DashViews/organicgrid.cpp−8 plugins/DashViews/organicgrid.h−4 plugins/DashViews/verticaljournal.cpp−8 plugins/DashViews/verticaljournal.h−4 plugins/Unity/Launcher/backend/launcherbackend.cpp+266−16 plugins/Unity/Launcher/backend/launcherbackend.h+52−4 qml/Components/LazyImage.qml+1 qml/Components/ZoomableImage.qml+153 qml/Dash/Previews/PreviewProgress.qml+3−2 qml/Dash/Previews/PreviewZoomableImage.qml+4−7 qml/Notifications/Notifications.qml+11 qml/Shell.qml+18 run → run.sh+1−1 run_on_device → run_on_device.sh+2−2 tests/autopilot/unity8/shell/tests/test_notifications.py+83 tests/plugins/DashViews/listviewwithpageheadersectionexternalmodeltest.cpp−4 tests/plugins/DashViews/listviewwithpageheadersectiontest.cpp−12 tests/plugins/DashViews/listviewwithpageheadertest.cpp−15 tests/plugins/Unity/Launcher/CMakeLists.txt+3−3 tests/plugins/Unity/Launcher/launcherbackendtest.cpp+167 tests/qmltests/CMakeLists.txt+1 tests/qmltests/Components/tst_ZoomableImage.qml+192 tests/qmltests/Dash/Previews/tst_PreviewZoomableImage.qml+6−3 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+30
@@ -1,3 +1,33 @@
1+unity8 (7.86+14.10.20140505-0ubuntu1) utopic; urgency=low
2+
3+ [ Ted Gould ]
4+ * Provide a dbus interface for setting the count and countVisible
5+ properties. (LP: #1301400)
6+
7+ [ Michał Sawicz ]
8+ * Pass env variables to initctl start.
9+ * Suffix .sh to our scripts and clean up debian/rules.
10+ * Adapt to Debian Qt package renames and drop unneeded Dee plugin
11+ dependency.
12+
13+ [ Ying-Chun Liu ]
14+ * Add Zoomable Image for Preview widgets.
15+
16+ [ Albert Astals ]
17+ * Remove support for Qt <= 5.2.1
18+
19+ [ Mirco Müller ]
20+ * Implemented feature-request from Design for modal snap-decision
21+ notifications on the phone. See LP #1285712 (LP: #1285712)
22+
23+ [ Andrea Cimitan ]
24+ * Make progressbas in preview widget big as the button
25+
26+ [ CI bot ]
27+ * Resync trunk
28+
29+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Mon, 05 May 2014 12:09:43 +0000
30+
131unity8 (7.86+14.10.20140502.6-0ubuntu1) utopic; urgency=low
232
333 [ tpeeters ]
debian/control+9−10
@@ -27,19 +27,19 @@ Build-Depends: cmake,
2727 python3-all:any,
2828 python3-setuptools,
2929 python:any (>= 2.7),
30+ qml-module-qtquick-xmllistmodel,
31+ qml-module-qtquick2,
32+ qml-module-qttest,
3033 qt5-default,
31− qtbase5-dev,
34+ qtbase5-dev (>= 5.2.1),
3235 qtbase5-dev-tools,
33− qtbase5-private-dev,
34− qtdeclarative5-dev,
36+ qtbase5-private-dev (>= 5.2.1),
37+ qtdeclarative5-dev (>= 5.2.1),
3538 qtdeclarative5-dev-tools,
36− qtdeclarative5-private-dev,
39+ qtdeclarative5-private-dev (>= 5.2.1),
3740 qtdeclarative5-qtmultimedia-plugin,
38− qtdeclarative5-qtquick2-plugin,
39− qtdeclarative5-test-plugin,
4041 qtdeclarative5-ubuntu-settings-components,
4142 qtdeclarative5-ubuntu-ui-toolkit-plugin,
42− qtdeclarative5-xmllistmodel-plugin,
4343 ttf-ubuntu-font-family,
4444Standards-Version: 3.9.4
4545Homepage: http://launchpad.net/unity
@@ -77,10 +77,9 @@ Depends: gsettings-desktop-schemas,
7777 libglib2.0-bin,
7878 libunity-mir1 (>= 0.4),
7979 qmenumodel-qml (>= 0.2.7),
80− qtdeclarative5-dee-plugin,
80+ qml-module-qtquick-xmllistmodel,
8181 qtdeclarative5-gsettings1.0,
8282 qtdeclarative5-ubuntu-settings-components,
83− qtdeclarative5-xmllistmodel-plugin,
8483 unity-greeter-session-broadcast,
8584 unity-launcher-impl-3,
8685 unity8-common (= ${source:Version}),
@@ -117,7 +116,7 @@ Depends: qtdeclarative5-ubuntu-thumbnailer0.1 | ubuntu-thumbnailer-impl,
117116 qtdeclarative5-unity-notifications-plugin | unity-notifications-impl,
118117 ubuntu-thumbnailer-impl-0,
119118 unity-application-impl-2,
120− unity-notifications-impl-2,
119+ unity-notifications-impl-3,
121120 unity-plugin-scopes | unity-scopes-impl,
122121 unity-scopes-impl-0,
123122 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:
plugins/DashViews/abstractdashview.cpp−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−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−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−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−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−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−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−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/verticaljournal.cpp−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−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/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
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/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/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/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+18
@@ -440,6 +440,24 @@ BasicShell {
440440 }
441441 }
442442
443+ Rectangle {
444+ id: modalNotificationBackground
445+
446+ visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
447+ color: "#000000"
448+ anchors.fill: parent
449+ opacity: 0.5
450+
451+ MouseArea {
452+ anchors.fill: parent
453+ }
454+
455+ InputFilterArea {
456+ anchors.fill: parent
457+ blockInput: modalNotificationBackground.visible
458+ }
459+ }
460+
443461 Notifications {
444462 id: notifications
445463
run → run.shrenamed+1−1
@@ -86,6 +86,6 @@ else
8686 echo "Unity8 is already running, please stop it first"
8787 exit 1
8888 fi
89− /sbin/initctl start unity8 BINARY="`readlink -f $QML_PHONE_SHELL_PATH` $QML_PHONE_SHELL_ARGS $@"
89+ /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
9090 tailf -n 0 ~/.cache/upstart/unity8.log
9191fi
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}
tests/autopilot/unity8/shell/tests/test_notifications.py+83
@@ -208,6 +208,89 @@ class InteractiveNotificationBase(NotificationsBase):
208208 self.touch.tap_object(notification.select_single(objectName="button4"))
209209 self.assert_notification_action_id_was_called("action_decline_4")
210210
211+ def test_modal_sd_without_greeter (self):
212+ """A snap-decision on a phone should block input to shell beneath it when there's no greeter."""
213+ unity_proxy = self.launch_unity()
214+ unlock_unity(unity_proxy)
215+
216+ summary = "Incoming file"
217+ body = "Frank would like to send you the file: essay.pdf"
218+ icon_path = "sync-idle"
219+ hints = [
220+ ("x-canonical-snap-decisions", "true"),
221+ ("x-canonical-non-shaped-icon", "true"),
222+ ]
223+
224+ actions = [
225+ ('action_accept', 'Accept'),
226+ ('action_decline_1', 'Decline'),
227+ ]
228+
229+ self._create_interactive_notification(
230+ summary,
231+ body,
232+ icon_path,
233+ "NORMAL",
234+ actions,
235+ hints
236+ )
237+
238+ # verify that we cannot reveal the launcher (no longer interact with the shell)
239+ time.sleep(1)
240+ self.main_window.show_dash_swiping()
241+ launcher = self.main_window.get_launcher()
242+ self.assertThat(launcher.shown, Eventually(Equals(False)))
243+
244+ # verify and interact with the triggered snap-decision notification
245+ notify_list = self._get_notifications_list()
246+ get_notification = lambda: notify_list.wait_select_single(
247+ 'Notification', objectName='notification1')
248+ notification = get_notification()
249+ self._assert_notification(notification, summary, body, True, False, 1.0)
250+ self.touch.tap_object(notification.select_single(objectName="button0"))
251+ self.assert_notification_action_id_was_called("action_accept")
252+
253+ def test_modal_sd_with_greeter (self):
254+ """A snap-decision on a phone should not block input to the greeter beneath it."""
255+ unity_proxy = self.launch_unity()
256+
257+ summary = "Incoming file"
258+ body = "Frank would like to send you the file: essay.pdf"
259+ icon_path = "sync-idle"
260+ hints = [
261+ ("x-canonical-snap-decisions", "true"),
262+ ("x-canonical-non-shaped-icon", "true"),
263+ ]
264+
265+ actions = [
266+ ('action_accept', 'Accept'),
267+ ('action_decline_1', 'Decline'),
268+ ]
269+
270+ self._create_interactive_notification(
271+ summary,
272+ body,
273+ icon_path,
274+ "NORMAL",
275+ actions,
276+ hints
277+ )
278+
279+ # verify that we can swipe away the greeter (interact with the "shell")
280+ time.sleep(1)
281+ self.main_window.show_dash_swiping()
282+ greeter = self.main_window.get_greeter()
283+ self.assertThat(greeter.shown, Eventually(Equals(False)))
284+
285+ # verify and interact with the triggered snap-decision notification
286+ notify_list = self._get_notifications_list()
287+ get_notification = lambda: notify_list.wait_select_single(
288+ 'Notification', objectName='notification1')
289+ notification = get_notification()
290+ self._assert_notification(notification, summary, body, True, False, 1.0)
291+ self.touch.tap_object(notification.select_single(objectName="button0"))
292+ self.assert_notification_action_id_was_called("action_accept")
293+
211294 def _create_interactive_notification(
212295 self,
213296 summary="",
tests/plugins/DashViews/listviewwithpageheadersectionexternalmodeltest.cpp−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−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−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/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+1
@@ -33,6 +33,7 @@ add_qml_test(Components Revealer)
3333add_qml_test(Components SeeMore)
3434add_qml_test(Components Showable)
3535add_qml_test(Components PageHeaderLabel)
36+add_qml_test(Components ZoomableImage)
3637add_qml_test(Dash Dash)
3738add_qml_test(Dash DashContent)
3839add_qml_test(Dash Card)
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_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/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)