on xenial help sign in
merged with trunk
f7406b6Nick Dedekind
cmake/modules/QmlTest.cmake+15−3
@@ -27,11 +27,14 @@ endif()
27
27
28set(qmlscene_exe ${CMAKE_BINARY_DIR}/tests/uqmlscene/uqmlscene)
28set(qmlscene_exe ${CMAKE_BINARY_DIR}/tests/uqmlscene/uqmlscene)
29
29
30+set(test_env UNITY_TESTING=1)
31+
30macro(add_manual_qml_test SUBPATH COMPONENT_NAME)
32macro(add_manual_qml_test SUBPATH COMPONENT_NAME)
31 set(options NO_ADD_TEST NO_TARGETS)
33 set(options NO_ADD_TEST NO_TARGETS)
32 set(multi_value_keywords IMPORT_PATHS TARGETS PROPERTIES ENVIRONMENT)
34 set(multi_value_keywords IMPORT_PATHS TARGETS PROPERTIES ENVIRONMENT)
33
35
34 cmake_parse_arguments(qmltest "${options}" "" "${multi_value_keywords}" ${ARGN})
36 cmake_parse_arguments(qmltest "${options}" "" "${multi_value_keywords}" ${ARGN})
37+ cmake_parse_arguments(qmltest_default "${options}" "" "${multi_value_keywords}" ${qmltest_DEFAULT_PROPERTIES})
35
38
36 set(qmlscene_TARGET try${COMPONENT_NAME})
39 set(qmlscene_TARGET try${COMPONENT_NAME})
37 set(qmltest_FILE ${SUBPATH}/tst_${COMPONENT_NAME})
40 set(qmltest_FILE ${SUBPATH}/tst_${COMPONENT_NAME})
@@ -49,8 +52,12 @@ macro(add_manual_qml_test SUBPATH COMPONENT_NAME)
49 endforeach(IMPORT_PATH)
52 endforeach(IMPORT_PATH)
50 endif()
53 endif()
51
54
55+ if("${qmltest_ENVIRONMENT}" STREQUAL "")
56+ set(qmltest_ENVIRONMENT "${qmltest_default_ENVIRONMENT}")
57+ endif()
58+
52 set(qmlscene_command
59 set(qmlscene_command
53− env ${qmltest_ENVIRONMENT}
60+ env ${test_env} ${qmltest_ENVIRONMENT}
54 ${qmlscene_exe} -qmljsdebugger=port:3768 ${CMAKE_CURRENT_SOURCE_DIR}/${qmltest_FILE}.qml
61 ${qmlscene_exe} -qmljsdebugger=port:3768 ${CMAKE_CURRENT_SOURCE_DIR}/${qmltest_FILE}.qml
55 ${qmlscene_imports}
62 ${qmlscene_imports}
56 )
63 )
@@ -71,6 +78,7 @@ macro(add_qml_test_internal SUBPATH COMPONENT_NAME ITERATIONS)
71 set(multi_value_keywords IMPORT_PATHS TARGETS PROPERTIES ENVIRONMENT)
78 set(multi_value_keywords IMPORT_PATHS TARGETS PROPERTIES ENVIRONMENT)
72
79
73 cmake_parse_arguments(qmltest "${options}" "" "${multi_value_keywords}" ${ARGN})
80 cmake_parse_arguments(qmltest "${options}" "" "${multi_value_keywords}" ${ARGN})
81+ cmake_parse_arguments(qmltest_default "${options}" "" "${multi_value_keywords}" ${qmltest_DEFAULT_PROPERTIES})
74
82
75 set(qmltest_TARGET test${COMPONENT_NAME})
83 set(qmltest_TARGET test${COMPONENT_NAME})
76 set(qmltest_xvfb_TARGET xvfbtest${COMPONENT_NAME})
84 set(qmltest_xvfb_TARGET xvfbtest${COMPONENT_NAME})
@@ -102,8 +110,12 @@ macro(add_qml_test_internal SUBPATH COMPONENT_NAME ITERATIONS)
102 set(ITERATIONS_STRING "")
110 set(ITERATIONS_STRING "")
103 endif()
111 endif()
104
112
113+ if("${qmltest_ENVIRONMENT}" STREQUAL "")
114+ set(qmltest_ENVIRONMENT "${qmltest_default_ENVIRONMENT}")
115+ endif()
116+
105 set(qmltest_command
117 set(qmltest_command
106− env ${qmltest_ENVIRONMENT} UNITY_TESTING=1
118+ env ${test_env} ${qmltest_ENVIRONMENT}
107 ${qmltestrunner_exe} -input ${CMAKE_CURRENT_SOURCE_DIR}/${qmltest_FILE}.qml
119 ${qmltestrunner_exe} -input ${CMAKE_CURRENT_SOURCE_DIR}/${qmltest_FILE}.qml
108 ${qmltestrunner_imports}
120 ${qmltestrunner_imports}
109 ${ITERATIONS_STRING}
121 ${ITERATIONS_STRING}
@@ -117,7 +129,7 @@ macro(add_qml_test_internal SUBPATH COMPONENT_NAME ITERATIONS)
117 set(LD_PRELOAD_PATH "LD_PRELOAD=/usr/lib/${ARCH_TRIPLET}/mesa/libGL.so.1")
129 set(LD_PRELOAD_PATH "LD_PRELOAD=/usr/lib/${ARCH_TRIPLET}/mesa/libGL.so.1")
118 endif()
130 endif()
119 set(qmltest_xvfb_command
131 set(qmltest_xvfb_command
120− env ${qmltest_ENVIRONMENT} ${LD_PRELOAD_PATH} UNITY_TESTING=1
132+ env ${test_env} ${qmltest_ENVIRONMENT} ${LD_PRELOAD_PATH}
121 xvfb-run --server-args "-screen 0 1024x768x24" --auto-servernum
133 xvfb-run --server-args "-screen 0 1024x768x24" --auto-servernum
122 ${qmltestrunner_exe} -input ${CMAKE_CURRENT_SOURCE_DIR}/${qmltest_FILE}.qml
134 ${qmltestrunner_exe} -input ${CMAKE_CURRENT_SOURCE_DIR}/${qmltest_FILE}.qml
123 ${qmltestrunner_imports}
135 ${qmltestrunner_imports}
data/unity8-dash.desktop.in+3−3
@@ -1,9 +1,9 @@
1[Desktop Entry]
1[Desktop Entry]
2Type=Application
2Type=Application
3−Name=Unity 8 Dash
3+Name=Scopes
4−Comment=The converged Unity shell dash
4+Comment=An interface for using the Scopes system
5Exec=@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_BINDIR@/unity8-dash
5Exec=@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_BINDIR@/unity8-dash
6Terminal=false
6Terminal=false
7−Icon=unity8
7+Icon=
8NoDisplay=true
8NoDisplay=true
9X-Ubuntu-Touch=true
9X-Ubuntu-Touch=true
data/unity8-filewatcher.confnew file+8
@@ -0,0 +1,8 @@
1+description "File system watcher for unity8"
2+author "Michael Zanetti <michael.zanetti@canonical.com>"
3+
4+# Workaround for bug 1360208. ~ and * in one expression doesn't work currently
5+start on (file FILE=/home/phablet/.local/share/applications/*.desktop) or (file FILE=/usr/share/applications/*.desktop)
6+
7+exec dbus-send --type=method_call --dest=com.canonical.Unity.Launcher /com/canonical/Unity/Launcher com.canonical.Unity.Launcher.Refresh
8+
data/unity8.desktop.in+1−1
@@ -4,6 +4,6 @@ Name=Unity 8
4Comment=The converged Unity shell
4Comment=The converged Unity shell
5Exec=@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_BINDIR@/unity8
5Exec=@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_BINDIR@/unity8
6Terminal=false
6Terminal=false
7−Icon=unity8
7+Icon=
8NoDisplay=true
8NoDisplay=true
9X-Ubuntu-Touch=true
9X-Ubuntu-Touch=true
debian/changelog+60
@@ -1,3 +1,63 @@
1+unity8 (8.00+14.10.20140918.3-0ubuntu1) utopic; urgency=low
2+
3+ [ Michał Sawicz ]
4+ * Add -windowgeometry option to the Dash and drop any user-visible
5+ mentions of Dash
6+ * Dash: Fix issue when expanding a category and collapsing another one
7+ at the same time.
8+
9+ [ Albert Astals ]
10+ * Dash: Fix issue when expanding a category and collapsing another one
11+ at the same time.
12+ * Make the categoryView invisible when we are in the preview mode (LP:
13+ #1341205)
14+ * Pixel pushing in the dash header (LP: #1365929)
15+
16+ [ Ying-Chun Liu ]
17+ * Re-add restart button for power menu. (LP: 1358197) (LP: #1358197)
18+
19+ [ Marcus Tomlinson ]
20+ * Don't show a preview if a null response is returned from
21+ scope.preview(result)
22+
23+ [ Mirco Müller ]
24+ * Don't limit the number of text-lines for body-text. (LP: #1369438)
25+
26+ [ Michael Zanetti ]
27+ * Don't hide launcher when nothing happens on long left edge swipes
28+ (LP: #1357333)
29+ * Rework LauncherBackend
30+
31+ [ Michael Terry ]
32+ * When running qmluitests, make sure that they use LANGUAGE=C, fixing
33+ a test failure when running locally in the US.
34+
35+ [ Daniel d'Andrada ]
36+ * Some ApplicationWindow test improvements
37+
38+ [ Rodney Dawes ]
39+ * Check purchase state to determine if purchase was cancelled and hide
40+ progress. (LP: #1362622)
41+
42+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Thu, 18 Sep 2014 21:26:42 +0000
43+
44+unity8 (8.00+14.10.20140918-0ubuntu1) utopic; urgency=low
45+
46+ [ Michael Zanetti ]
47+ * Focus first app if there are already some running when we're
48+ starting up (LP: #1339883)
49+
50+ [ Michał Sawicz ]
51+ * Don't play empty urls in Notification.qml
52+
53+ [ Daniel d'Andrada ]
54+ * Improve tst_Shell
55+ * Build without any warnings
56+ * Make tst_Card work from outside the source tree (LP:1359201) (LP:
57+ #1359201)
58+
59+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Thu, 18 Sep 2014 09:44:06 +0000
60+
1unity8 (8.00+14.10.20140910.1-0ubuntu1) utopic; urgency=low
61unity8 (8.00+14.10.20140910.1-0ubuntu1) utopic; urgency=low
2
62
3 [ Pete Woods ]
63 [ Pete Woods ]
debian/control+4−4
@@ -24,7 +24,7 @@ Build-Depends: cmake,
24 libpulse-dev,
24 libpulse-dev,
25 libqmenumodel-dev (>= 0.2.8),
25 libqmenumodel-dev (>= 0.2.8),
26 libqt5xmlpatterns5-dev,
26 libqt5xmlpatterns5-dev,
27− libunity-api-dev (>= 7.89),
27+ libunity-api-dev (>= 7.90),
28 libusermetricsoutput1-dev,
28 libusermetricsoutput1-dev,
29 libxcb1-dev,
29 libxcb1-dev,
30 pkg-config,
30 pkg-config,
@@ -87,7 +87,7 @@ Depends: gsettings-desktop-schemas,
87 qtdeclarative5-gsettings1.0,
87 qtdeclarative5-gsettings1.0,
88 qtdeclarative5-qtmir-plugin (>= 0.4.3),
88 qtdeclarative5-qtmir-plugin (>= 0.4.3),
89 qtdeclarative5-ubuntu-telephony0.1,
89 qtdeclarative5-ubuntu-telephony0.1,
90− unity-launcher-impl-3,
90+ unity-launcher-impl-4,
91 unity8-common (= ${source:Version}),
91 unity8-common (= ${source:Version}),
92 unity8-private (= ${binary:Version}),
92 unity8-private (= ${binary:Version}),
93 unity8-private | unity-launcher-impl,
93 unity8-private | unity-launcher-impl,
@@ -171,12 +171,12 @@ Pre-Depends: ${misc:Pre-Depends},
171Depends: accountsservice-ubuntu-schemas (>= 0.0.3),
171Depends: accountsservice-ubuntu-schemas (>= 0.0.3),
172 gsettings-ubuntu-schemas (>= 0.0.2+14.10.20140815),
172 gsettings-ubuntu-schemas (>= 0.0.2+14.10.20140815),
173 libhardware2,
173 libhardware2,
174− libunity-core-6.0-9,
174+ unity-schemas (>= 7.3.1+14.10.20140915),
175 pay-service,
175 pay-service,
176 ${misc:Depends},
176 ${misc:Depends},
177 ${shlibs:Depends},
177 ${shlibs:Depends},
178Provides: unity-launcher-impl,
178Provides: unity-launcher-impl,
179− unity-launcher-impl-3,
179+ unity-launcher-impl-4,
180Description: Unity 8 private libs
180Description: Unity 8 private libs
181 The Unity 8 shell is the primary user interface for Ubuntu devices.
181 The Unity 8 shell is the primary user interface for Ubuntu devices.
182 .
182 .
debian/unity8.install+1
@@ -1,5 +1,6 @@
1data/unity8.conf usr/share/upstart/sessions/
1data/unity8.conf usr/share/upstart/sessions/
2data/unity8-dash.conf usr/share/upstart/sessions/
2data/unity8-dash.conf usr/share/upstart/sessions/
3+data/unity8-filewatcher.conf usr/share/upstart/sessions/
3usr/bin/unity8
4usr/bin/unity8
4usr/bin/unity8-dash
5usr/bin/unity8-dash
5usr/share/applications/unity8.desktop
6usr/share/applications/unity8.desktop
plugins/Dash/CardCreator.js+2−2
@@ -78,8 +78,8 @@ var kArtShapeHolderCode = 'Item { \n\
78 width = root.fixedArtShapeSize.width; \n\
78 width = root.fixedArtShapeSize.width; \n\
79 height = root.fixedArtShapeSize.height; \n\
79 height = root.fixedArtShapeSize.height; \n\
80 } else { \n\
80 } else { \n\
81− width = Qt.binding(function() { return !visible ? 0 : image.width }); \n\
81+ width = Qt.binding(function() { return image.status !== Image.Ready ? 0 : image.width }); \n\
82− height = Qt.binding(function() { return !visible ? 0 : image.height }); \n\
82+ height = Qt.binding(function() { return image.status !== Image.Ready ? 0 : image.height }); \n\
83 } \n\
83 } \n\
84 } \n\
84 } \n\
85 image: Image { \n\
85 image: Image { \n\
plugins/Dash/listviewwithpageheader.cpp+11−1
@@ -409,6 +409,16 @@ void ListViewWithPageHeader::showHeader()
409 }
409 }
410}
410}
411
411
412+int ListViewWithPageHeader::firstCreatedIndex() const
413+{
414+ return m_firstVisibleIndex;
415+}
416+
417+int ListViewWithPageHeader::createdItemCount() const
418+{
419+ return m_visibleItems.count();
420+}
421+
412QQuickItem *ListViewWithPageHeader::item(int modelIndex) const
422QQuickItem *ListViewWithPageHeader::item(int modelIndex) const
413{
423{
414 ListItem *item = itemAtIndex(modelIndex);
424 ListItem *item = itemAtIndex(modelIndex);
@@ -520,7 +530,7 @@ void ListViewWithPageHeader::adjustHeader(qreal diff)
520 // (but the header was not shown by it's own position)
530 // (but the header was not shown by it's own position)
521 // or the header is partially shown and we are not doing a maximizeVisibleArea either
531 // or the header is partially shown and we are not doing a maximizeVisibleArea either
522 const bool scrolledUp = m_previousContentY > contentY();
532 const bool scrolledUp = m_previousContentY > contentY();
523− const bool notRebounding = contentY() + height() < contentHeight();
533+ const bool notRebounding = qRound(contentY() + height()) < qRound(contentHeight());
524 const bool notShownByItsOwn = contentY() + diff >= m_headerItem->y() + m_headerItem->height();
534 const bool notShownByItsOwn = contentY() + diff >= m_headerItem->y() + m_headerItem->height();
525 const bool maximizeVisibleAreaRunning = m_contentYAnimation->isRunning() && contentYAnimationType == ContentYAnimationMaximizeVisibleArea;
535 const bool maximizeVisibleAreaRunning = m_contentYAnimation->isRunning() && contentYAnimationType == ContentYAnimationMaximizeVisibleArea;
526
536
plugins/Dash/listviewwithpageheader.h+2
@@ -86,6 +86,8 @@ public:
86
86
87 Q_INVOKABLE void positionAtBeginning();
87 Q_INVOKABLE void positionAtBeginning();
88 Q_INVOKABLE void showHeader();
88 Q_INVOKABLE void showHeader();
89+ Q_INVOKABLE int firstCreatedIndex() const;
90+ Q_INVOKABLE int createdItemCount() const;
89 Q_INVOKABLE QQuickItem *item(int modelIndex) const;
91 Q_INVOKABLE QQuickItem *item(int modelIndex) const;
90
92
91 // The index has to be created for this to try to do something
93 // The index has to be created for this to try to do something
plugins/Lights/Lights.cpp+1−2
@@ -20,8 +20,7 @@
20#include <QtCore/QDebug>
20#include <QtCore/QDebug>
21
21
22extern "C" {
22extern "C" {
23−#include <hardware/hardware.h>
24−#include <hardware/lights.h>
23+#include "android-hardware.h"
25#include <string.h>
24#include <string.h>
26}
25}
27
26
plugins/Lights/android-hardware.hnew file+23
@@ -0,0 +1,23 @@
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+
18+// The purpose of this header wrapper is just to avoid having warnings being reported on
19+// code from external header files, as we can't do anything about them.
20+#pragma GCC system_header
21+
22+#include <hardware/hardware.h>
23+#include <hardware/lights.h>
plugins/Ubuntu/Payments/Payments.cpp+21−1
@@ -41,13 +41,23 @@ static void observer(PayPackage* /* package */, const char* itemid, PayPackageIt
41 case PAY_PACKAGE_ITEM_STATUS_PURCHASED:
41 case PAY_PACKAGE_ITEM_STATUS_PURCHASED:
42 Q_EMIT self->purchaseCompleted();
42 Q_EMIT self->purchaseCompleted();
43 break;
43 break;
44+ case PAY_PACKAGE_ITEM_STATUS_PURCHASING:
45+ self->setPurchasing(true);
46+ break;
47+ case PAY_PACKAGE_ITEM_STATUS_NOT_PURCHASED:
48+ if (self->purchasing()) {
49+ self->setPurchasing(false);
50+ Q_EMIT self->purchaseCancelled();
51+ }
52+ break;
44 default:
53 default:
45 break;
54 break;
46 }
55 }
47}
56}
48
57
49Payments::Payments(QObject *parent)
58Payments::Payments(QObject *parent)
50− : QObject(parent)
59+ : QObject(parent),
60+ m_purchasing(false)
51{
61{
52 m_package = pay_package_new("click-scope");
62 m_package = pay_package_new("click-scope");
53 pay_package_item_observer_install(m_package, observer, this);
63 pay_package_item_observer_install(m_package, observer, this);
@@ -80,6 +90,11 @@ QString Payments::formattedPrice() const
80 return locale.toCurrencyString(m_price, m_currency);
90 return locale.toCurrencyString(m_price, m_currency);
81}
91}
82
92
93+bool Payments::purchasing() const
94+{
95+ return m_purchasing;
96+}
97+
83void Payments::setCurrency(const QString &currency)
98void Payments::setCurrency(const QString &currency)
84{
99{
85 if(m_currency != currency) {
100 if(m_currency != currency) {
@@ -112,6 +127,11 @@ void Payments::setStoreItemId(const QString &store_item_id)
112 pay_package_item_start_verification(m_package, m_store_item_id.toLocal8Bit().data());
127 pay_package_item_start_verification(m_package, m_store_item_id.toLocal8Bit().data());
113}
128}
114
129
130+void Payments::setPurchasing(bool is_purchasing)
131+{
132+ m_purchasing = is_purchasing;
133+}
134+
115void Payments::start()
135void Payments::start()
116{
136{
117 pay_package_item_start_purchase(m_package, m_store_item_id.toLocal8Bit().data());
137 pay_package_item_start_purchase(m_package, m_store_item_id.toLocal8Bit().data());
plugins/Ubuntu/Payments/Payments.h+4
@@ -39,10 +39,12 @@ public:
39 double price() const;
39 double price() const;
40 QString storeItemId() const;
40 QString storeItemId() const;
41 QString formattedPrice() const;
41 QString formattedPrice() const;
42+ bool purchasing() const;
42
43
43 void setCurrency(const QString& currency);
44 void setCurrency(const QString& currency);
44 void setPrice(const double price);
45 void setPrice(const double price);
45 void setStoreItemId(const QString& store_item_id);
46 void setStoreItemId(const QString& store_item_id);
47+ void setPurchasing(bool is_purchasing);
46 Q_INVOKABLE void start();
48 Q_INVOKABLE void start();
47
49
48Q_SIGNALS:
50Q_SIGNALS:
@@ -53,11 +55,13 @@ Q_SIGNALS:
53
55
54 void purchaseError(const QString &error);
56 void purchaseError(const QString &error);
55 void purchaseCompleted();
57 void purchaseCompleted();
58+ void purchaseCancelled();
56
59
57private:
60private:
58 QString m_currency;
61 QString m_currency;
59 double m_price;
62 double m_price;
60 QString m_store_item_id;
63 QString m_store_item_id;
64+ bool m_purchasing;
61
65
62 PayPackage *m_package;
66 PayPackage *m_package;
63};
67};
plugins/Unity/Launcher/CMakeLists.txt+5−3
@@ -1,5 +1,5 @@
1include(FindPkgConfig)
1include(FindPkgConfig)
2−pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=3)
2+pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=4)
3pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=3)
3pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=3)
4pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
4pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
5
5
@@ -16,8 +16,10 @@ set(QMLLAUNCHERPLUGIN_SRC
16 launchermodel.cpp
16 launchermodel.cpp
17 launcheritem.cpp
17 launcheritem.cpp
18 quicklistmodel.cpp
18 quicklistmodel.cpp
19− common/quicklistentry.cpp
19+ quicklistentry.cpp
20− backend/launcherbackend.cpp
20+ dbusinterface.cpp
21+ gsettings.cpp
22+ desktopfilehandler.cpp
21 ${CMAKE_SOURCE_DIR}/plugins/AccountsService/AccountsServiceDBusAdaptor.cpp
23 ${CMAKE_SOURCE_DIR}/plugins/AccountsService/AccountsServiceDBusAdaptor.cpp
22 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherItemInterface.h
24 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherItemInterface.h
23 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherModelInterface.h
25 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherModelInterface.h
plugins/Unity/Launcher/backend/launcherbackend.cppdeleted−591
@@ -1,591 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * Authors:
5− * Michael Terry <michael.terry@canonical.com>
6− * Michael Zanetti <michael.zanetti@canonical.com>
7− *
8− * This program is free software; you can redistribute it and/or modify
9− * it under the terms of the GNU General Public License as published by
10− * the Free Software Foundation; version 3.
11− *
12− * This program is distributed in the hope that it will be useful,
13− * but WITHOUT ANY WARRANTY; without even the implied warranty of
14− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15− * GNU General Public License for more details.
16− *
17− * You should have received a copy of the GNU General Public License
18− * along with this program. If not, see <http://www.gnu.org/licenses/>.
19− */
20−
21−#include "AccountsServiceDBusAdaptor.h"
22−#include "launcherbackend.h"
23−
24−#include <QDir>
25−#include <QDBusArgument>
26−#include <QFileInfo>
27−#include <QGSettings>
28−#include <QDebug>
29−#include <QStandardPaths>
30−
31−#include <libintl.h>
32−
33−class LauncherBackendItem
34−{
35−public:
36− QString displayName;
37− QString icon;
38− int count;
39− bool countVisible;
40−};
41−
42−LauncherBackend::LauncherBackend(QObject *parent):
43− QDBusVirtualObject(parent),
44− m_accounts(nullptr)
45−{
46−#ifndef LAUNCHER_TESTING
47− m_accounts = new AccountsServiceDBusAdaptor(this);
48−#endif
49− m_user = qgetenv("USER");
50− syncFromAccounts();
51−
52− /* Set up ourselves on DBus */
53− QDBusConnection con = QDBusConnection::sessionBus();
54− if (!con.registerService("com.canonical.Unity.Launcher")) {
55− qDebug() << "Unable to register launcher name";
56− }
57− if (!con.registerVirtualObject("/com/canonical/Unity/Launcher", this, QDBusConnection::VirtualObjectRegisterOption::SubPath)) {
58− qDebug() << "Unable to register launcher object";
59− }
60−}
61−
62−LauncherBackend::~LauncherBackend()
63−{
64− /* Remove oursevles from DBus */
65− QDBusConnection con = QDBusConnection::sessionBus();
66− con.unregisterService("com.canonical.Unity.Launcher");
67− con.unregisterObject("/com/canonical/Unity/Launcher");
68−
69− /* Clear data */
70− m_storedApps.clear();
71−
72− Q_FOREACH(LauncherBackendItem *item, m_itemCache) {
73− delete item;
74− }
75− m_itemCache.clear();
76−}
77−
78−QStringList LauncherBackend::storedApplications() const
79−{
80− return m_storedApps;
81−}
82−
83−void LauncherBackend::setStoredApplications(const QStringList &appIds)
84−{
85− if (appIds.count() < m_storedApps.count()) {
86− Q_FOREACH(const QString &appId, m_storedApps) {
87− if (!appIds.contains(appId)) {
88− delete m_itemCache.take(appId);
89− }
90− }
91− }
92− m_storedApps = appIds;
93− Q_FOREACH(const QString &appId, appIds) {
94− if (!m_itemCache.contains(appId)) {
95− QString df = findDesktopFile(appId);
96− if (!df.isEmpty()) {
97− LauncherBackendItem *item = parseDesktopFile(df);
98− m_itemCache.insert(appId, item);
99− } else {
100− // Cannot find any data for that app... ignoring it.
101− qWarning() << "cannot find desktop file for" << appId << ". discarding app.";
102− m_storedApps.removeAll(appId);
103− }
104− }
105− }
106− syncToAccounts();
107−}
108−
109−QString LauncherBackend::desktopFile(const QString &appId) const
110−{
111− return findDesktopFile(appId);
112−}
113−
114−QString LauncherBackend::displayName(const QString &appId) const
115−{
116− LauncherBackendItem *item = m_itemCache.value(appId, nullptr);
117− if (item) {
118− return item->displayName;
119− }
120−
121− QString df = findDesktopFile(appId);
122− if (!df.isEmpty()) {
123− LauncherBackendItem *item = parseDesktopFile(df);
124− m_itemCache.insert(appId, item);
125− return item->displayName;
126− }
127−
128− return QString();
129−}
130−
131−QString LauncherBackend::icon(const QString &appId) const
132−{
133− QString iconName;
134− LauncherBackendItem *item = getItem(appId);
135− if (item) {
136− iconName = item->icon;
137− }
138−
139− return iconName;
140−}
141−
142−QList<QuickListEntry> LauncherBackend::quickList(const QString &appId) const
143−{
144− // TODO: Get static (from .desktop file) and dynamic (from the app itself)
145− // entries and return them here. Frontend related entries (like "Pin to launcher")
146− // don't matter here. This is just the backend part.
147− // TODO: emit quickListChanged() when the dynamic part changes
148− Q_UNUSED(appId)
149− return QList<QuickListEntry>();
150−}
151−
152−int LauncherBackend::progress(const QString &appId) const
153−{
154− // TODO: Return value for progress emblem.
155− // TODO: emit progressChanged() when this value changes.
156− Q_UNUSED(appId)
157− return -1;
158−}
159−
160−int LauncherBackend::count(const QString &appId) const
161−{
162− int count = -1;
163− LauncherBackendItem *item = getItem(appId);
164−
165− if (item) {
166− if (item->countVisible) {
167− count = item->count;
168− }
169− }
170−
171− return count;
172−}
173−
174−void LauncherBackend::setCount(const QString &appId, int count) const
175−{
176− LauncherBackendItem *item = getItem(appId);
177−
178− bool emitchange = false;
179− if (item) {
180− emitchange = (item->count != count);
181− item->count = count;
182− }
183−
184− if (emitchange) {
185− /* TODO: This needs to use the accessor to handle the visibility
186− correctly, but when we have the two properties we can just use
187− the local value */
188− Q_EMIT countChanged(appId, this->count(appId));
189− QVariant vcount(item->count);
190− emitPropChangedDbus(appId, "count", vcount);
191− }
192−}
193−
194−bool LauncherBackend::countVisible(const QString &appId) const
195−{
196− bool visible = false;
197− LauncherBackendItem *item = getItem(appId);
198−
199− if (item) {
200− visible = item->countVisible;
201− }
202−
203− return visible;
204−}
205−
206−void LauncherBackend::setCountVisible(const QString &appId, bool visible) const
207−{
208− LauncherBackendItem *item = getItem(appId);
209−
210− bool emitchange = false;
211− if (item) {
212− emitchange = (item->countVisible != visible);
213− item->countVisible = visible;
214− } else {
215− qDebug() << "Unable to find:" << appId;
216− }
217−
218− if (emitchange) {
219− /* TODO: Because we're using visible in determining the
220− count we need to emit a count changed as well */
221− Q_EMIT countChanged(appId, this->count(appId));
222− Q_EMIT countVisibleChanged(appId, item->countVisible);
223− QVariant vCountVisible(item->countVisible);
224− emitPropChangedDbus(appId, "countVisible", vCountVisible);
225− }
226−}
227−
228−void LauncherBackend::setUser(const QString &username)
229−{
230− if (qgetenv("USER") == "lightdm" && m_user != username) {
231− m_user = username;
232− syncFromAccounts();
233− }
234−}
235−
236−void LauncherBackend::triggerQuickListAction(const QString &appId, const QString &quickListId)
237−{
238− // TODO: execute the given quicklist action
239− Q_UNUSED(appId)
240− Q_UNUSED(quickListId)
241−}
242−
243−void LauncherBackend::syncFromAccounts()
244−{
245− QList<QVariantMap> apps;
246− bool defaults = true;
247−
248− m_storedApps.clear();
249−
250− if (m_accounts && !m_user.isEmpty()) {
251− QVariant variant = m_accounts->getUserProperty(m_user, "com.canonical.unity.AccountsService", "launcher-items");
252− if (variant.isValid() && variant.canConvert<QDBusArgument>()) {
253− apps = qdbus_cast<QList<QVariantMap>>(variant.value<QDBusArgument>());
254− defaults = isDefaultsItem(apps);
255− }
256− }
257−
258− if (m_accounts && defaults) { // Checking accounts as it'll be null when !useStorage
259− QGSettings gSettings("com.canonical.Unity.Launcher", "/com/canonical/unity/launcher/");
260− Q_FOREACH(const QString &entry, gSettings.get("favorites").toStringList()) {
261− if (entry.startsWith("application://")) {
262− QString appId = entry;
263− // Transform "application://foobar.desktop" to "application://foobar"
264− appId.remove("application://");
265− if (appId.endsWith(".desktop")) {
266− appId.chop(8);
267− }
268− QString df = findDesktopFile(appId);
269−
270− if (!df.isEmpty()) {
271− m_storedApps << appId;
272−
273− if (!m_itemCache.contains(appId)) {
274− m_itemCache.insert(appId, parseDesktopFile(df));
275− }
276− }
277− }
278− if (entry.startsWith("appid://")) {
279− QString appId = entry;
280− appId.remove("appid://");
281− // Strip hook name and current-user-version in case its there
282−
283− if (appId.split('/').count() != 3) {
284− qWarning() << "ignoring entry " + appId + ". Not a valid appId.";
285− continue;
286− }
287− appId = appId.split('/').first() + "_" + appId.split('/').at(1);
288− QString df = findDesktopFile(appId);
289−
290− if (!df.isEmpty()) {
291− m_storedApps << appId;
292−
293− if (!m_itemCache.contains(appId)) {
294− m_itemCache.insert(appId, parseDesktopFile(df));
295− }
296− }
297− }
298− }
299− } else {
300− for (const QVariant &app: apps) {
301− loadFromVariant(app.toMap());
302− }
303− }
304−}
305−
306−void LauncherBackend::syncToAccounts()
307−{
308− if (m_accounts && !m_user.isEmpty()) {
309− QList<QVariantMap> items;
310−
311− Q_FOREACH(const QString &appId, m_storedApps) {
312− items << itemToVariant(appId);
313− }
314−
315− m_accounts->setUserProperty(m_user, "com.canonical.unity.AccountsService", "launcher-items", QVariant::fromValue(items));
316− }
317−}
318−
319−QString LauncherBackend::findDesktopFile(const QString &appId) const
320−{
321− int dashPos = -1;
322− QString helper = appId;
323−
324− QStringList searchDirs = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation);
325−#ifdef LAUNCHER_TESTING
326− searchDirs << "";
327−#endif
328−
329− do {
330− if (dashPos != -1) {
331− helper = helper.replace(dashPos, 1, '/');
332− }
333−
334− Q_FOREACH(const QString &searchDirName, searchDirs) {
335− QDir searchDir(searchDirName);
336− Q_FOREACH(const QString &desktopFile, searchDir.entryList(QStringList() << "*.desktop")) {
337− if (desktopFile.startsWith(helper)) {
338− QFileInfo fileInfo(searchDir, desktopFile);
339− return fileInfo.absoluteFilePath();
340− }
341− }
342− }
343−
344− dashPos = helper.indexOf("-");
345− } while (dashPos != -1);
346−
347− return QString();
348−}
349−
350−LauncherBackendItem* LauncherBackend::parseDesktopFile(const QString &desktopFile) const
351−{
352− QSettings settings(desktopFile, QSettings::IniFormat);
353−
354− LauncherBackendItem* item = new LauncherBackendItem();
355−
356− item->displayName = settings.value("Desktop Entry/Name").toString();
357− const QString domain = settings.value("Desktop Entry/X-Ubuntu-Gettext-Domain").toString();
358− if (!domain.isEmpty()) {
359− item->displayName = dgettext(domain.toUtf8().constData(), item->displayName.toUtf8().constData());
360− }
361−
362− QString iconString = settings.value("Desktop Entry/Icon").toString();
363− QString pathString = settings.value("Desktop Entry/Path").toString();
364− if (QFileInfo(iconString).exists()) {
365− item->icon = QFileInfo(iconString).absoluteFilePath();
366− } else if (QFileInfo(pathString + '/' + iconString).exists()) {
367− item->icon = pathString + '/' + iconString;
368− } else {
369− item->icon = "image://theme/" + iconString;
370− }
371−
372− /* TODO: These should be looked up in a cache somewhere */
373− item->count = 0;
374− item->countVisible = false;
375−
376− return item;
377−}
378−
379−/* Gets an item, and tries to create a new one if we need it to */
380−LauncherBackendItem* LauncherBackend::getItem(const QString &appId) const
381−{
382− LauncherBackendItem *item = m_itemCache.value(appId, nullptr);
383− if (!item) {
384− QString df = findDesktopFile(appId);
385− if (!df.isEmpty()) {
386− item = parseDesktopFile(df);
387− if (item) {
388− m_itemCache[appId] = item;
389− } else {
390− qWarning() << "Unable to parse desktop file for" << appId << "path" << df;
391− }
392− } else {
393− qWarning() << "Unable to find desktop file for:" << appId;
394− }
395− }
396−
397− if (!item)
398− qWarning() << "Unable to find item for: " << appId;
399−
400− return item;
401−}
402−
403−void LauncherBackend::loadFromVariant(const QVariantMap &details)
404−{
405− if (!details.contains("id")) {
406− return;
407− }
408− QString appId = details.value("id").toString();
409−
410− LauncherBackendItem *item = m_itemCache.value(appId, nullptr);
411− if (item) {
412− delete item;
413− }
414−
415− item = new LauncherBackendItem();
416−
417− item->displayName = details.value("name").toString();
418− item->icon = details.value("icon").toString();
419− item->count = details.value("count").toInt();
420− item->countVisible = details.value("countVisible").toBool();
421−
422− m_itemCache.insert(appId, item);
423− m_storedApps.append(appId);
424−}
425−
426−QVariantMap LauncherBackend::itemToVariant(const QString &appId) const
427−{
428− LauncherBackendItem *item = m_itemCache.value(appId);
429− QVariantMap details;
430− details.insert("id", appId);
431− details.insert("name", item->displayName);
432− details.insert("icon", item->icon);
433− details.insert("count", item->count);
434− details.insert("countVisible", item->countVisible);
435− return details;
436−}
437−
438−bool LauncherBackend::isDefaultsItem(const QList<QVariantMap> &apps) const
439−{
440− // To differentiate between an empty list and a list that hasn't been set
441− // yet (and should thus be populated with the defaults), we use a special
442− // list of one item with the 'defaults' field set to true.
443− return (apps.size() == 1 && apps[0].value("defaults").toBool());
444−}
445−
446−bool LauncherBackend::handleMessage(const QDBusMessage& message, const QDBusConnection& connection)
447−{
448− /* Check to make sure we're getting properties on our interface */
449− if (message.type() != QDBusMessage::MessageType::MethodCallMessage) {
450− return false;
451− }
452− if (message.interface() != "org.freedesktop.DBus.Properties") {
453− return false;
454− }
455− if (message.arguments()[0].toString() != "com.canonical.Unity.Launcher.Item") {
456− return false;
457− }
458−
459− /* Break down the path to just the app id */
460− QString pathtemp = message.path();
461− if (!pathtemp.startsWith("/com/canonical/Unity/Launcher/")) {
462− return false;
463− }
464− pathtemp.remove("/com/canonical/Unity/Launcher/");
465− if (pathtemp.indexOf('/') >= 0) {
466− return false;
467− }
468−
469− /* Find ourselves an appid */
470− QString appid = decodeAppId(pathtemp);
471− QVariantList retval;
472−
473− if (message.member() == "Get") {
474− if (message.arguments()[1].toString() == "count") {
475− retval.append(QVariant::fromValue(QDBusVariant(this->count(appid))));
476− } else if (message.arguments()[1].toString() == "countVisible") {
477− retval.append(QVariant::fromValue(QDBusVariant(this->countVisible(appid))));
478− }
479− } else if (message.member() == "Set") {
480− if (message.arguments()[1].toString() == "count") {
481− this->setCount(appid, message.arguments()[2].value<QDBusVariant>().variant().toInt());
482− } else if (message.arguments()[1].toString() == "countVisible") {
483− this->setCountVisible(appid, message.arguments()[2].value<QDBusVariant>().variant().toBool());
484− }
485− } else if (message.member() == "GetAll") {
486− retval.append(this->itemToVariant(appid));
487− } else {
488− return false;
489− }
490−
491− QDBusMessage reply = message.createReply(retval);
492− return connection.send(reply);
493−}
494−
495−QString LauncherBackend::introspect(const QString &path) const
496−{
497− /* This case we should just list the nodes */
498− if (path == "/com/canonical/Unity/Launcher/" || path == "/com/canonical/Unity/Launcher") {
499− QString nodes;
500−
501− Q_FOREACH(const QString &appId, m_itemCache.keys()) {
502− nodes.append("<node name=\"");
503− nodes.append(encodeAppId(appId));
504− nodes.append("\"/>\n");
505− }
506−
507− return nodes;
508− }
509−
510− /* Should not happen, but let's handle it */
511− if (!path.startsWith("/com/canonical/Unity/Launcher")) {
512− return "";
513− }
514−
515− /* Now we should be looking at a node */
516− QString nodeiface =
517− "<interface name=\"com.canonical.Unity.Launcher.Item\">"
518− "<property name=\"count\" type=\"i\" access=\"readwrite\" />"
519− "<property name=\"countVisible\" type=\"b\" access=\"readwrite\" />"
520− "</interface>";
521− return nodeiface;
522−}
523−
524−QString LauncherBackend::decodeAppId(const QString& path)
525−{
526− QByteArray bytes = path.toUtf8();
527− QByteArray decoded;
528−
529− for (int i = 0; i < bytes.size(); ++i) {
530− char chr = bytes.at(i);
531−
532− if (chr == '_') {
533− QString number;
534− number.append(bytes.at(i+1));
535− number.append(bytes.at(i+2));
536−
537− bool okay;
538− char newchar = number.toUInt(&okay, 16);
539− if (okay)
540− decoded.append(newchar);
541−
542− i += 2;
543− } else {
544− decoded.append(chr);
545− }
546− }
547−
548− return QString::fromUtf8(decoded);
549−}
550−
551−QString LauncherBackend::encodeAppId(const QString& appId)
552−{
553− QByteArray bytes = appId.toUtf8();
554− QString encoded;
555−
556− for (int i = 0; i < bytes.size(); ++i) {
557− uchar chr = bytes.at(i);
558−
559− if ((chr >= 'a' && chr <= 'z') ||
560− (chr >= 'A' && chr <= 'Z') ||
561− (chr >= '0' && chr <= '9'&& i != 0)) {
562− encoded.append(chr);
563− } else {
564− QString hexval = QString("_%1").arg(chr, 2, 16, QChar('0'));
565− encoded.append(hexval.toUpper());
566− }
567− }
568−
569− return encoded;
570−}
571−
572−void LauncherBackend::emitPropChangedDbus(const QString& appId, const QString& property, QVariant &value) const
573−{
574− QString path("/com/canonical/Unity/Launcher/");
575− path.append(encodeAppId(appId));
576−
577− QDBusMessage message = QDBusMessage::createSignal(path, "org.freedesktop.DBus.Properties", "PropertiesChanged");
578−
579− QList<QVariant> arguments;
580− QVariantHash changedprops;
581− changedprops[property] = QVariant::fromValue(QDBusVariant(value));
582− QVariantList deletedprops;
583−
584− arguments.append(changedprops);
585− arguments.append(deletedprops);
586−
587− message.setArguments(arguments);
588−
589− QDBusConnection con = QDBusConnection::sessionBus();
590− con.send(message);
591−}
plugins/Unity/Launcher/backend/launcherbackend.hdeleted−196
@@ -1,196 +0,0 @@
1−/* Copyright (C) 2013 Canonical, Ltd.
2− *
3− * Authors:
4− * Michael Zanetti <michael.zanetti@canonical.com>
5− *
6− * This program is free software; you can redistribute it and/or modify
7− * it under the terms of the GNU General Public License as published by
8− * the Free Software Foundation; version 3.
9− *
10− * This program is distributed in the hope that it will be useful,
11− * but WITHOUT ANY WARRANTY; without even the implied warranty of
12− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13− * GNU General Public License for more details.
14− *
15− * You should have received a copy of the GNU General Public License
16− * along with this program. If not, see <http://www.gnu.org/licenses/>.
17− */
18−
19−#ifndef LAUNCHERBACKEND_H
20−#define LAUNCHERBACKEND_H
21−
22−#include "common/quicklistentry.h"
23−
24−#include <QObject>
25−#include <QSettings>
26−#include <QStringList>
27−#include <QDBusVirtualObject>
28−
29−class AccountsServiceDBusAdaptor;
30−
31−/**
32− * @brief An interface that provides all the data needed by the launcher.
33− */
34−
35−class LauncherBackendItem;
36−class LauncherBackendTest;
37−
38−class LauncherBackend : public QDBusVirtualObject
39−{
40− Q_OBJECT
41−
42− friend LauncherBackendTest;
43−
44−public:
45− LauncherBackend(QObject *parent = 0);
46− virtual ~LauncherBackend();
47−
48− /**
49− * @brief Returns a list of stored applications.
50− * @returns A list of application IDs.
51− */
52− QStringList storedApplications() const;
53−
54− /**
55− * @brief Set the list of stored applications.
56− *
57− * Any previously stored information for apps not contained
58− * in the list any more, e.g. the pinned state, will be
59− * discarded.
60− *
61− * @param appIds The new list of stored applications.
62− */
63− void setStoredApplications(const QStringList &appIds);
64−
65− /**
66− * @brief Get the full path to the .desktop file.
67− *
68− * The application does not need to be in the list of stored applications.
69− *
70− * @returns The full path to the .dekstop file.
71− */
72− QString desktopFile(const QString &appId) const;
73−
74− /**
75− * @brief Get the user friendly name of an application.
76− *
77− * The application does not need to be in the list of stored applications.
78− *
79− * @param appId The ID of the application.
80− * @returns The user friendly name of the application.
81− */
82− QString displayName(const QString &appId) const;
83−
84− /**
85− * @brief Get the icon of an application.
86− *
87− * The application does not need to be in the list of stored applications.
88− *
89− * @param appId The ID of the application.
90− * @returns The full path to the icon for the application.
91− */
92− QString icon(const QString &appId) const;
93−
94− /**
95− * @brief Get the QuickList for an application.
96− * @param appId The ID of the application.
97− * @returns A QuickListModelInterface containing the QuickList.
98− */
99− QList<QuickListEntry> quickList(const QString &appId) const;
100−
101− /**
102− * @brief Execute an action from the quickList
103− * @param appId The app ID for which the action was triggered
104− * @param the quicklist ID of the action that was triggered
105− */
106− void triggerQuickListAction(const QString &appId, const QString &entryId);
107−
108− /**
109− * @brief Get the progress for the progress overlay of an application.
110− * @param appId The ID of the application.
111− * @returns The percentage of the overlay progress bar. -1 if no progress bar available.
112− */
113− int progress(const QString &appId) const;
114−
115− /**
116− * @brief Get the count of the count overlay of an application.
117− * @param appId The ID of the application.
118− * @returns The number to be displayed in the overlay. -1 if no count overlay is available.
119− */
120− int count(const QString &appId) const;
121−
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−
143− /**
144− * @brief Sets the username for which to look up launcher items.
145− * @param username The username to use.
146− */
147− void setUser(const QString &username);
148−
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−
164−Q_SIGNALS:
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;
169−
170−private:
171− QString findDesktopFile(const QString &appId) const;
172− LauncherBackendItem* parseDesktopFile(const QString &desktopFile) const;
173−
174− QVariantMap itemToVariant(const QString &appId) const;
175− void loadFromVariant(const QVariantMap &details);
176−
177− bool isDefaultsItem(const QList<QVariantMap> &apps) const;
178− void syncFromAccounts();
179− void syncToAccounts();
180−
181− QList<QString> m_storedApps;
182− mutable QHash<QString, LauncherBackendItem*> m_itemCache;
183−
184− AccountsServiceDBusAdaptor *m_accounts;
185− 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);
194−};
195−
196−#endif // LAUNCHERBACKEND_H
plugins/Unity/Launcher/dbusinterface.cppnew file+232
@@ -0,0 +1,232 @@
1+/*
2+ * Copyright 2014 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Authors:
17+ * Michael Zanetti <michael.zanetti@canonical.com>
18+ */
19+
20+#include "dbusinterface.h"
21+#include "launchermodel.h"
22+#include "launcheritem.h"
23+
24+#include <QDBusArgument>
25+#include <QDBusConnection>
26+#include <QDBusMessage>
27+#include <QDebug>
28+
29+DBusInterface::DBusInterface(LauncherModel *parent):
30+ QDBusVirtualObject(parent),
31+ m_launcherModel(parent)
32+{
33+ /* Set up ourselves on DBus */
34+ QDBusConnection con = QDBusConnection::sessionBus();
35+ if (!con.registerService("com.canonical.Unity.Launcher")) {
36+ qWarning() << "Unable to register launcher name";
37+ }
38+ if (!con.registerVirtualObject("/com/canonical/Unity/Launcher", this, QDBusConnection::VirtualObjectRegisterOption::SubPath)) {
39+ qWarning() << "Unable to register launcher object";
40+ }
41+}
42+
43+DBusInterface::~DBusInterface()
44+{
45+ /* Remove oursevles from DBus */
46+ QDBusConnection con = QDBusConnection::sessionBus();
47+ con.unregisterService("com.canonical.Unity.Launcher");
48+ con.unregisterObject("/com/canonical/Unity/Launcher");
49+}
50+
51+QString DBusInterface::introspect(const QString &path) const
52+{
53+ /* This case we should just list the nodes */
54+ if (path == "/com/canonical/Unity/Launcher/" || path == "/com/canonical/Unity/Launcher") {
55+ QString nodes;
56+
57+ // Add Refresh to introspect
58+ nodes = "<interface name=\"com.canonical.Unity.Launcher\">"
59+ "<method name=\"Refresh\"/>"
60+ "</interface>";
61+
62+ // Add dynamic properties for launcher emblems
63+ for (int i = 0; i < m_launcherModel->rowCount(); i++) {
64+ nodes.append("<node name=\"");
65+ nodes.append(encodeAppId(m_launcherModel->get(i)->appId()));
66+ nodes.append("\"/>\n");
67+ }
68+ return nodes;
69+ }
70+
71+ /* Should not happen, but let's handle it */
72+ if (!path.startsWith("/com/canonical/Unity/Launcher")) {
73+ return "";
74+ }
75+
76+ /* Now we should be looking at a node */
77+ QString nodeiface =
78+ "<interface name=\"com.canonical.Unity.Launcher.Item\">"
79+ "<property name=\"count\" type=\"i\" access=\"readwrite\" />"
80+ "<property name=\"countVisible\" type=\"b\" access=\"readwrite\" />"
81+ "</interface>";
82+ return nodeiface;
83+}
84+
85+
86+QString DBusInterface::decodeAppId(const QString& path)
87+{
88+ QByteArray bytes = path.toUtf8();
89+ QByteArray decoded;
90+
91+ for (int i = 0; i < bytes.size(); ++i) {
92+ char chr = bytes.at(i);
93+
94+ if (chr == '_') {
95+ QString number;
96+ number.append(bytes.at(i+1));
97+ number.append(bytes.at(i+2));
98+
99+ bool okay;
100+ char newchar = number.toUInt(&okay, 16);
101+ if (okay)
102+ decoded.append(newchar);
103+
104+ i += 2;
105+ } else {
106+ decoded.append(chr);
107+ }
108+ }
109+
110+ return QString::fromUtf8(decoded);
111+}
112+
113+QString DBusInterface::encodeAppId(const QString& appId)
114+{
115+ QByteArray bytes = appId.toUtf8();
116+ QString encoded;
117+
118+ for (int i = 0; i < bytes.size(); ++i) {
119+ uchar chr = bytes.at(i);
120+
121+ if ((chr >= 'a' && chr <= 'z') ||
122+ (chr >= 'A' && chr <= 'Z') ||
123+ (chr >= '0' && chr <= '9'&& i != 0)) {
124+ encoded.append(chr);
125+ } else {
126+ QString hexval = QString("_%1").arg(chr, 2, 16, QChar('0'));
127+ encoded.append(hexval.toUpper());
128+ }
129+ }
130+
131+ return encoded;
132+}
133+
134+bool DBusInterface::handleMessage(const QDBusMessage& message, const QDBusConnection& connection)
135+{
136+ /* Check to make sure we're getting properties on our interface */
137+ if (message.type() != QDBusMessage::MessageType::MethodCallMessage) {
138+ return false;
139+ }
140+
141+ // First handle methods of the Launcher interface
142+ if (message.interface() == "com.canonical.Unity.Launcher") {
143+ if (message.member() == "Refresh") {
144+ QDBusMessage reply = message.createReply();
145+ Q_EMIT refreshCalled();
146+ return connection.send(reply);
147+ }
148+ }
149+
150+ // Now handle dynamic properties (for launcher emblems)
151+ if (message.interface() != "org.freedesktop.DBus.Properties") {
152+ return false;
153+ }
154+
155+ if (message.member() != "GetAll" && message.arguments()[0].toString() != "com.canonical.Unity.Launcher.Item") {
156+ return false;
157+ }
158+
159+ /* Break down the path to just the app id */
160+ QString pathtemp = message.path();
161+ if (!pathtemp.startsWith("/com/canonical/Unity/Launcher/")) {
162+ return false;
163+ }
164+ pathtemp.remove("/com/canonical/Unity/Launcher/");
165+ if (pathtemp.indexOf('/') >= 0) {
166+ return false;
167+ }
168+
169+ /* Find ourselves an appid */
170+ QString appid = decodeAppId(pathtemp);
171+ int index = m_launcherModel->findApplication(appid);
172+ LauncherItem *item = static_cast<LauncherItem*>(m_launcherModel->get(index));
173+
174+ QVariantList retval;
175+ if (message.member() == "Get") {
176+ if (!item) {
177+ return false;
178+ }
179+ if (message.arguments()[1].toString() == "count") {
180+ retval.append(QVariant::fromValue(QDBusVariant(item->count())));
181+ } else if (message.arguments()[1].toString() == "countVisible") {
182+ retval.append(QVariant::fromValue(QDBusVariant(item->countVisible())));
183+ }
184+ } else if (message.member() == "Set") {
185+ if (message.arguments()[1].toString() == "count") {
186+ int newCount = message.arguments()[2].toInt();
187+ if (!item || newCount != item->count()) {
188+ Q_EMIT countChanged(appid, newCount);
189+ emitPropChangedDbus(appid, "count", QVariant(newCount));
190+ }
191+ } else if (message.arguments()[1].toString() == "countVisible") {
192+ bool newVisible = message.arguments()[2].toBool();
193+ if (!item || newVisible != item->countVisible()) {
194+ Q_EMIT countVisibleChanged(appid, newVisible);
195+ emitPropChangedDbus(appid, "countVisible", newVisible);
196+ }
197+ }
198+ } else if (message.member() == "GetAll") {
199+ if (item) {
200+ QVariantMap all;
201+ all.insert("count", item->count());
202+ all.insert("countVisible", item->countVisible());
203+ retval.append(all);
204+ }
205+ } else {
206+ return false;
207+ }
208+
209+ QDBusMessage reply = message.createReply(retval);
210+ return connection.send(reply);
211+}
212+
213+void DBusInterface::emitPropChangedDbus(const QString& appId, const QString& property, const QVariant &value)
214+{
215+ QString path("/com/canonical/Unity/Launcher/");
216+ path.append(encodeAppId(appId));
217+
218+ QDBusMessage message = QDBusMessage::createSignal(path, "org.freedesktop.DBus.Properties", "PropertiesChanged");
219+
220+ QList<QVariant> arguments;
221+ QVariantHash changedprops;
222+ changedprops[property] = QVariant::fromValue(QDBusVariant(value));
223+ QVariantList deletedprops;
224+
225+ arguments.append(changedprops);
226+ arguments.append(deletedprops);
227+
228+ message.setArguments(arguments);
229+
230+ QDBusConnection con = QDBusConnection::sessionBus();
231+ con.send(message);
232+}
plugins/Unity/Launcher/dbusinterface.hnew file+50
@@ -0,0 +1,50 @@
1+/*
2+ * Copyright 2014 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Authors:
17+ * Michael Zanetti <michael.zanetti@canonical.com>
18+ */
19+
20+#include "launcheritem.h"
21+
22+#include <QDBusVirtualObject>
23+
24+class LauncherModel;
25+
26+class DBusInterface: public QDBusVirtualObject
27+{
28+ Q_OBJECT
29+public:
30+ DBusInterface(LauncherModel *parent);
31+ ~DBusInterface();
32+
33+ // QDBusVirtualObject implementaition
34+ QString introspect (const QString &path) const override;
35+ bool handleMessage(const QDBusMessage& message, const QDBusConnection& connection) override;
36+
37+Q_SIGNALS:
38+ void countChanged(const QString &appId, int count);
39+ void countVisibleChanged(const QString &appId, bool countVisible);
40+ void refreshCalled();
41+
42+private:
43+ static QString decodeAppId(const QString& path);
44+ static QString encodeAppId(const QString& appId);
45+
46+ void emitPropChangedDbus(const QString& appId, const QString& property, const QVariant &value);
47+
48+ LauncherModel *m_launcherModel;
49+
50+};
plugins/Unity/Launcher/desktopfilehandler.cppnew file+151
@@ -0,0 +1,151 @@
1+/*
2+ * Copyright 2014 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Authors:
17+ * Michael Zanetti <michael.zanetti@canonical.com>
18+ */
19+
20+#include "desktopfilehandler.h"
21+
22+#include <QStringList>
23+#include <QStandardPaths>
24+#include <QDir>
25+#include <QSettings>
26+#include <QLocale>
27+
28+#include <libintl.h>
29+
30+DesktopFileHandler::DesktopFileHandler(const QString &appId, QObject *parent):
31+ QObject(parent),
32+ m_appId(appId)
33+{
34+ load();
35+}
36+
37+QString DesktopFileHandler::appId() const
38+{
39+ return m_appId;
40+}
41+
42+void DesktopFileHandler::setAppId(const QString &appId)
43+{
44+ if (m_appId != appId) {
45+ m_appId = appId;
46+ load();
47+ }
48+}
49+
50+QString DesktopFileHandler::filename() const
51+{
52+ return m_filename;
53+}
54+
55+bool DesktopFileHandler::isValid() const
56+{
57+ return !m_filename.isEmpty();
58+}
59+
60+void DesktopFileHandler::load()
61+{
62+ m_filename.clear();
63+
64+ if (m_appId.isEmpty()) {
65+ return;
66+ }
67+
68+ int dashPos = -1;
69+ QString helper = m_appId;
70+
71+ QStringList searchDirs = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation);
72+#ifdef LAUNCHER_TESTING
73+ searchDirs << "";
74+#endif
75+
76+ QString path;
77+ do {
78+ if (dashPos != -1) {
79+ helper.replace(dashPos, 1, '/');
80+ }
81+
82+ if (helper.contains("/")) {
83+ path += helper.split('/').first() + '/';
84+ helper.remove(QRegExp("^" + path));
85+ }
86+
87+ Q_FOREACH(const QString &searchDirName, searchDirs) {
88+ QDir searchDir(searchDirName + "/" + path);
89+ Q_FOREACH(const QString &desktopFile, searchDir.entryList(QStringList() << "*.desktop")) {
90+ if (desktopFile.startsWith(helper)) {
91+ QFileInfo fileInfo(searchDir, desktopFile);
92+ m_filename = fileInfo.absoluteFilePath();
93+ return;
94+ }
95+ }
96+ }
97+
98+ dashPos = helper.indexOf("-");
99+ } while (dashPos != -1);
100+}
101+
102+QString DesktopFileHandler::displayName() const
103+{
104+ if (!isValid()) {
105+ return QString();
106+ }
107+
108+ QSettings settings(m_filename, QSettings::IniFormat);
109+ settings.beginGroup("Desktop Entry");
110+
111+ // First try to find Name[xx_YY] and Name[xx] in .desktop file
112+ QString locale = QLocale::system().name();
113+ QString shortLocale = locale.split('_').first();
114+
115+ if (locale != shortLocale && settings.contains(QString("Name[%1]").arg(locale))) {
116+ return settings.value(QString("Name[%1]").arg(locale)).toString();
117+ }
118+
119+ if (settings.contains(QString("Name[%1]").arg(shortLocale))) {
120+ return settings.value(QString("Name[%1]").arg(shortLocale)).toString();
121+ }
122+
123+ // No translation found in desktop file. Get the untranslated one and have a go with gettext.
124+ QString displayName = settings.value("Name").toString();
125+
126+ if (settings.contains("X-Ubuntu-Gettext-Domain")) {
127+ const QString domain = settings.value("X-Ubuntu-Gettext-Domain").toString();
128+ return dgettext(domain.toUtf8().constData(), displayName.toUtf8().constData());
129+ }
130+
131+ return displayName;
132+}
133+
134+QString DesktopFileHandler::icon() const
135+{
136+ if (!isValid()) {
137+ return QString();
138+ }
139+
140+ QSettings settings(m_filename, QSettings::IniFormat);
141+ settings.beginGroup("Desktop Entry");
142+ QString iconString = settings.value("Icon").toString();
143+ QString pathString = settings.value("Path").toString();
144+
145+ if (QFileInfo(iconString).exists()) {
146+ return QFileInfo(iconString).absoluteFilePath();
147+ } else if (QFileInfo(pathString + '/' + iconString).exists()) {
148+ return pathString + '/' + iconString;
149+ }
150+ return "image://theme/" + iconString;
151+}
plugins/Unity/Launcher/desktopfilehandler.hnew file+56
@@ -0,0 +1,56 @@
1+/*
2+ * Copyright 2014 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Authors:
17+ * Michael Zanetti <michael.zanetti@canonical.com>
18+ */
19+
20+#ifndef DESKTOPFILEHANDLER_H
21+#define DESKTOPFILEHANDLER_H
22+
23+#include <QObject>
24+
25+/**
26+ * When an object of this class is created or whenever setAppId(appId) is called,
27+ * this will search for a .desktop file matching the give appId. If a file is
28+ * found, isValid() will return true and the other methods return the contents
29+ * of the .desktop file.
30+ *
31+ * Note that this class will consider the user's locale and do a best effort
32+ * to return localized values.
33+ */
34+
35+class DesktopFileHandler: public QObject
36+{
37+ Q_OBJECT
38+public:
39+ DesktopFileHandler(const QString &appId = QString(), QObject *parent = nullptr);
40+
41+ QString appId() const;
42+ void setAppId(const QString &appId);
43+
44+ bool isValid() const;
45+ QString filename() const;
46+ QString displayName() const;
47+ QString icon() const;
48+
49+private:
50+ void load();
51+
52+ QString m_appId;
53+ QString m_filename;
54+};
55+
56+#endif
plugins/Unity/Launcher/gsettings.cppnew file+66
@@ -0,0 +1,66 @@
1+/*
2+ * Copyright 2014 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Authors:
17+ * Michael Zanetti <michael.zanetti@canonical.com>
18+ */
19+
20+#include "gsettings.h"
21+
22+#include <QGSettings>
23+#include <QVariant>
24+
25+GSettings::GSettings(QObject *parent):
26+ QObject(parent)
27+{
28+
29+}
30+
31+QStringList GSettings::storedApplications() const
32+{
33+ QStringList storedApps;
34+
35+ QGSettings gSettings("com.canonical.Unity.Launcher", "/com/canonical/unity/launcher/");
36+
37+ Q_FOREACH(const QString &entry, gSettings.get("items").toStringList()) {
38+ if (entry.startsWith("application:///")) {
39+ // convert legacy entries to new world appids
40+ QString appId = entry;
41+ // Transform "application://foobar.desktop" to "foobar"
42+ appId.remove(QRegExp("^application:///"));
43+ appId.remove(QRegExp(".desktop$"));
44+ storedApps << appId;
45+ } else if (entry.startsWith("appid://")) {
46+ QString appId = entry;
47+ appId.remove("appid://");
48+ if (appId.split('/').count() == 3) {
49+ // Strip current-user-version in case its there
50+ appId = appId.split('/').first() + "_" + appId.split('/').at(1);
51+ }
52+ storedApps << appId;
53+ }
54+ }
55+ return storedApps;
56+}
57+
58+void GSettings::setStoredApplications(const QStringList &storedApplications)
59+{
60+ QGSettings gSettings("com.canonical.Unity.Launcher", "/com/canonical/unity/launcher/");
61+ QStringList gSettingsList;
62+ Q_FOREACH(const QString &entry, storedApplications) {
63+ gSettingsList << QString("appid://%1").arg(entry);
64+ }
65+ gSettings.set("items", gSettingsList);
66+}
plugins/Unity/Launcher/gsettings.hnew file+37
@@ -0,0 +1,37 @@
1+/*
2+ * Copyright 2014 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Authors:
17+ * Michael Zanetti <michael.zanetti@canonical.com>
18+ */
19+
20+#ifndef GSETTINGS_H
21+#define GSETTINGS_H
22+
23+#include <QObject>
24+#include <QStringList>
25+
26+
27+class GSettings: public QObject
28+{
29+ Q_OBJECT
30+public:
31+ GSettings(QObject *parent = nullptr);
32+
33+ QStringList storedApplications() const;
34+ void setStoredApplications(const QStringList &storedApplications);
35+};
36+
37+#endif
plugins/Unity/Launcher/launcheritem.cpp+30
@@ -32,6 +32,7 @@ LauncherItem::LauncherItem(const QString &appId, const QString &name, const QStr
32 m_recent(false),
32 m_recent(false),
33 m_progress(-1),
33 m_progress(-1),
34 m_count(0),
34 m_count(0),
35+ m_countVisible(false),
35 m_focused(false),
36 m_focused(false),
36 m_quickList(new QuickListModel(this))
37 m_quickList(new QuickListModel(this))
37{
38{
@@ -55,11 +56,27 @@ QString LauncherItem::name() const
55 return m_name;
56 return m_name;
56}
57}
57
58
59+void LauncherItem::setName(const QString &name)
60+{
61+ if (m_name != name) {
62+ m_name = name;
63+ Q_EMIT nameChanged(name);
64+ }
65+}
66+
58QString LauncherItem::icon() const
67QString LauncherItem::icon() const
59{
68{
60 return m_icon;
69 return m_icon;
61}
70}
62
71
72+void LauncherItem::setIcon(const QString &icon)
73+{
74+ if (m_icon != icon) {
75+ m_icon = icon;
76+ Q_EMIT iconChanged(icon);
77+ }
78+}
79+
63bool LauncherItem::pinned() const
80bool LauncherItem::pinned() const
64{
81{
65 return m_pinned;
82 return m_pinned;
@@ -129,6 +146,19 @@ void LauncherItem::setCount(int count)
129 }
146 }
130}
147}
131
148
149+bool LauncherItem::countVisible() const
150+{
151+ return m_countVisible;
152+}
153+
154+void LauncherItem::setCountVisible(bool countVisible)
155+{
156+ if (m_countVisible != countVisible) {
157+ m_countVisible = countVisible;
158+ Q_EMIT countVisibleChanged(countVisible);
159+ }
160+}
161+
132bool LauncherItem::focused() const
162bool LauncherItem::focused() const
133{
163{
134 return m_focused;
164 return m_focused;
plugins/Unity/Launcher/launcheritem.h+19−20
@@ -34,34 +34,30 @@ class LauncherItem: public LauncherItemInterface
34public:
34public:
35 LauncherItem(const QString &appId, const QString &name, const QString &icon, QObject *parent = 0);
35 LauncherItem(const QString &appId, const QString &name, const QString &icon, QObject *parent = 0);
36
36
37− QString appId() const;
37+ QString appId() const override;
38− QString name() const;
38+ QString name() const override;
39− QString icon() const;
39+ QString icon() const override;
40+ bool pinned() const override;
41+ bool running() const override;
42+ bool recent() const override;
43+ int progress() const override;
44+ int count() const override;
45+ bool countVisible() const override;
46+ bool focused() const override;
47+
48+ unity::shell::launcher::QuickListModelInterface *quickList() const override;
40
49
41− bool pinned() const;
50+private:
51+ void setName(const QString &name);
52+ void setIcon(const QString &icon);
42 void setPinned(bool pinned);
53 void setPinned(bool pinned);
43−
44− bool running() const;
45 void setRunning(bool running);
54 void setRunning(bool running);
46−
47− bool recent() const;
48 void setRecent(bool recent);
55 void setRecent(bool recent);
49−
50− int progress() const;
51 void setProgress(int progress);
56 void setProgress(int progress);
52−
53− int count() const;
54 void setCount(int count);
57 void setCount(int count);
55−
58+ void setCountVisible(bool countVisible);
56− bool focused() const;
57 void setFocused(bool focused);
59 void setFocused(bool focused);
58
60
59− unity::shell::launcher::QuickListModelInterface *quickList() const;
60−
61−Q_SIGNALS:
62− void favoriteChanged(bool favorite);
63− void runningChanged(bool running);
64−
65private:
61private:
66 QString m_appId;
62 QString m_appId;
67 QString m_name;
63 QString m_name;
@@ -71,8 +67,11 @@ private:
71 bool m_recent;
67 bool m_recent;
72 int m_progress;
68 int m_progress;
73 int m_count;
69 int m_count;
70+ bool m_countVisible;
74 bool m_focused;
71 bool m_focused;
75 QuickListModel *m_quickList;
72 QuickListModel *m_quickList;
73+
74+ friend class LauncherModel;
76};
75};
77
76
78#endif // LAUNCHERITEM_H
77#endif // LAUNCHERITEM_H
plugins/Unity/Launcher/launchermodel.cpp+85−15
@@ -19,7 +19,9 @@
19
19
20#include "launchermodel.h"
20#include "launchermodel.h"
21#include "launcheritem.h"
21#include "launcheritem.h"
22−#include "backend/launcherbackend.h"
22+#include "gsettings.h"
23+#include "desktopfilehandler.h"
24+#include "dbusinterface.h"
23
25
24#include <unity/shell/application/ApplicationInfoInterface.h>
26#include <unity/shell/application/ApplicationInfoInterface.h>
25
27
@@ -30,16 +32,24 @@ using namespace unity::shell::application;
30
32
31LauncherModel::LauncherModel(QObject *parent):
33LauncherModel::LauncherModel(QObject *parent):
32 LauncherModelInterface(parent),
34 LauncherModelInterface(parent),
33− m_backend(new LauncherBackend(this)),
35+ m_settings(new GSettings(this)),
36+ m_dbusIface(new DBusInterface(this)),
34 m_appManager(0)
37 m_appManager(0)
35{
38{
36− connect(m_backend, SIGNAL(countChanged(QString,int)), SLOT(countChanged(QString,int)));
39+ connect(m_dbusIface, &DBusInterface::countChanged, this, &LauncherModel::countChanged);
37− connect(m_backend, SIGNAL(progressChanged(QString,int)), SLOT(progressChanged(QString,int)));
40+ connect(m_dbusIface, &DBusInterface::countVisibleChanged, this, &LauncherModel::countVisibleChanged);
41+ connect(m_dbusIface, &DBusInterface::refreshCalled, this, &LauncherModel::refresh);
42+
43+ Q_FOREACH (const QString &entry, m_settings->storedApplications()) {
44+ DesktopFileHandler desktopFile(entry);
45+ if (!desktopFile.isValid()) {
46+ qWarning() << "Couldn't find a .desktop file for" << entry << ". Skipping...";
47+ continue;
48+ }
38
49
39− Q_FOREACH (const QString &entry, m_backend->storedApplications()) {
40 LauncherItem *item = new LauncherItem(entry,
50 LauncherItem *item = new LauncherItem(entry,
41− m_backend->displayName(entry),
51+ desktopFile.displayName(),
42− m_backend->icon(entry),
52+ desktopFile.icon(),
43 this);
53 this);
44 item->setPinned(true);
54 item->setPinned(true);
45 m_list.append(item);
55 m_list.append(item);
@@ -73,6 +83,8 @@ QVariant LauncherModel::data(const QModelIndex &index, int role) const
73 return item->pinned();
83 return item->pinned();
74 case RoleCount:
84 case RoleCount:
75 return item->count();
85 return item->count();
86+ case RoleCountVisible:
87+ return item->countVisible();
76 case RoleProgress:
88 case RoleProgress:
77 return item->progress();
89 return item->progress();
78 case RoleFocused:
90 case RoleFocused:
@@ -139,10 +151,17 @@ void LauncherModel::pin(const QString &appId, int index)
139 if (index == -1) {
151 if (index == -1) {
140 index = m_list.count();
152 index = m_list.count();
141 }
153 }
154+
155+ DesktopFileHandler desktopFile(appId);
156+ if (!desktopFile.isValid()) {
157+ qWarning() << "Can't pin this application, there is no .destkop file available.";
158+ return;
159+ }
160+
142 beginInsertRows(QModelIndex(), index, index);
161 beginInsertRows(QModelIndex(), index, index);
143 LauncherItem *item = new LauncherItem(appId,
162 LauncherItem *item = new LauncherItem(appId,
144− m_backend->displayName(appId),
163+ desktopFile.displayName(),
145− m_backend->icon(appId),
164+ desktopFile.icon(),
146 this);
165 this);
147 item->setPinned(true);
166 item->setPinned(true);
148 m_list.insert(index, item);
167 m_list.insert(index, item);
@@ -197,25 +216,27 @@ void LauncherModel::quickListActionInvoked(const QString &appId, int actionIndex
197
216
198 // Nope, we don't know this action, let the backend forward it to the application
217 // Nope, we don't know this action, let the backend forward it to the application
199 } else {
218 } else {
200− m_backend->triggerQuickListAction(appId, actionId);
219+ // TODO: forward quicklist action to app, possibly via m_dbusIface
201 }
220 }
202 }
221 }
203}
222}
204
223
205void LauncherModel::setUser(const QString &username)
224void LauncherModel::setUser(const QString &username)
206{
225{
207− m_backend->setUser(username);
226+ Q_UNUSED(username)
227+ qWarning() << "This backend doesn't support multiple users";
208}
228}
209
229
210QString LauncherModel::getUrlForAppId(const QString &appId) const
230QString LauncherModel::getUrlForAppId(const QString &appId) const
211{
231{
212 // appId is either an appId or a legacy app name. Let's find out which
232 // appId is either an appId or a legacy app name. Let's find out which
213− if (appId.isEmpty())
233+ if (appId.isEmpty()) {
214 return QString();
234 return QString();
235+ }
215
236
216− QString df = m_backend->desktopFile(appId + ".desktop");
237+ if (!appId.contains("_")) {
217− if (!df.isEmpty())
218 return "application:///" + appId + ".desktop";
238 return "application:///" + appId + ".desktop";
239+ }
219
240
220 QStringList parts = appId.split('_');
241 QStringList parts = appId.split('_');
221 QString package = parts.value(0);
242 QString package = parts.value(0);
@@ -275,7 +296,7 @@ void LauncherModel::storeAppList()
275 appIds << item->appId();
296 appIds << item->appId();
276 }
297 }
277 }
298 }
278− m_backend->setStoredApplications(appIds);
299+ m_settings->setStoredApplications(appIds);
279}
300}
280
301
281int LauncherModel::findApplication(const QString &appId)
302int LauncherModel::findApplication(const QString &appId)
@@ -310,6 +331,55 @@ void LauncherModel::countChanged(const QString &appId, int count)
310 }
331 }
311}
332}
312
333
334+void LauncherModel::countVisibleChanged(const QString &appId, int countVisible)
335+{
336+ int idx = findApplication(appId);
337+ if (idx >= 0) {
338+ LauncherItem *item = m_list.at(idx);
339+ item->setCountVisible(countVisible);
340+ Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleCountVisible);
341+
342+ // If countVisible goes to false, and the item is neither pinned nor recent we can drop it
343+ if (!countVisible && !item->pinned() && !item->recent()) {
344+ beginRemoveRows(QModelIndex(), idx, idx);
345+ m_list.takeAt(idx)->deleteLater();
346+ endRemoveRows();
347+ }
348+ } else {
349+ // Need to create a new LauncherItem and show the highlight
350+ DesktopFileHandler desktopFile(appId);
351+ if (countVisible && desktopFile.isValid()) {
352+ LauncherItem *item = new LauncherItem(appId,
353+ desktopFile.displayName(),
354+ desktopFile.icon());
355+ item->setCountVisible(true);
356+ beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
357+ m_list.append(item);
358+ endInsertRows();
359+ }
360+ }
361+}
362+
363+void LauncherModel::refresh()
364+{
365+ QList<LauncherItem*> toBeRemoved;
366+ Q_FOREACH (LauncherItem* item, m_list) {
367+ DesktopFileHandler desktopFile(item->appId());
368+ if (!desktopFile.isValid()) {
369+ toBeRemoved << item;
370+ } else {
371+ int idx = m_list.indexOf(item);
372+ item->setName(desktopFile.displayName());
373+ item->setIcon(desktopFile.icon());
374+ Q_EMIT dataChanged(index(idx), index(idx), QVector<int>() << RoleName << RoleIcon);
375+ }
376+ }
377+
378+ Q_FOREACH (LauncherItem* item, toBeRemoved) {
379+ requestRemove(item->appId());
380+ }
381+}
382+
313void LauncherModel::applicationAdded(const QModelIndex &parent, int row)
383void LauncherModel::applicationAdded(const QModelIndex &parent, int row)
314{
384{
315 Q_UNUSED(parent);
385 Q_UNUSED(parent);
plugins/Unity/Launcher/launchermodel.h+15−8
@@ -20,15 +20,14 @@
20#ifndef LAUNCHERMODEL_H
20#ifndef LAUNCHERMODEL_H
21#define LAUNCHERMODEL_H
21#define LAUNCHERMODEL_H
22
22
23−// unity-api
24#include <unity/shell/launcher/LauncherModelInterface.h>
23#include <unity/shell/launcher/LauncherModelInterface.h>
25#include <unity/shell/application/ApplicationManagerInterface.h>
24#include <unity/shell/application/ApplicationManagerInterface.h>
26
25
27−// Qt
28#include <QAbstractListModel>
26#include <QAbstractListModel>
29
27
30class LauncherItem;
28class LauncherItem;
31−class LauncherBackend;
29+class GSettings;
30+class DBusInterface;
32
31
33using namespace unity::shell::launcher;
32using namespace unity::shell::launcher;
34using namespace unity::shell::application;
33using namespace unity::shell::application;
@@ -41,14 +40,13 @@ public:
41 LauncherModel(QObject *parent = 0);
40 LauncherModel(QObject *parent = 0);
42 ~LauncherModel();
41 ~LauncherModel();
43
42
44− int rowCount(const QModelIndex &parent) const;
43+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
45
44
46 QVariant data(const QModelIndex &index, int role) const;
45 QVariant data(const QModelIndex &index, int role) const;
47
46
48 Q_INVOKABLE unity::shell::launcher::LauncherItemInterface* get(int index) const;
47 Q_INVOKABLE unity::shell::launcher::LauncherItemInterface* get(int index) const;
49 Q_INVOKABLE void move(int oldIndex, int newIndex);
48 Q_INVOKABLE void move(int oldIndex, int newIndex);
50 Q_INVOKABLE void pin(const QString &appId, int index = -1);
49 Q_INVOKABLE void pin(const QString &appId, int index = -1);
51− Q_INVOKABLE void requestRemove(const QString &appId);
52 Q_INVOKABLE void quickListActionInvoked(const QString &appId, int actionIndex);
50 Q_INVOKABLE void quickListActionInvoked(const QString &appId, int actionIndex);
53 Q_INVOKABLE void setUser(const QString &username);
51 Q_INVOKABLE void setUser(const QString &username);
54 Q_INVOKABLE QString getUrlForAppId(const QString &appId) const;
52 Q_INVOKABLE QString getUrlForAppId(const QString &appId) const;
@@ -56,13 +54,19 @@ public:
56 unity::shell::application::ApplicationManagerInterface* applicationManager() const;
54 unity::shell::application::ApplicationManagerInterface* applicationManager() const;
57 void setApplicationManager(unity::shell::application::ApplicationManagerInterface *appManager);
55 void setApplicationManager(unity::shell::application::ApplicationManagerInterface *appManager);
58
56
57+ int findApplication(const QString &appId);
58+
59+public Q_SLOTS:
60+ void requestRemove(const QString &appId);
61+
59private:
62private:
60 void storeAppList();
63 void storeAppList();
61− int findApplication(const QString &appId);
62
64
63private Q_SLOTS:
65private Q_SLOTS:
64− void progressChanged(const QString &appId, int progress);
65 void countChanged(const QString &appId, int count);
66 void countChanged(const QString &appId, int count);
67+ void countVisibleChanged(const QString &appId, int count);
68+ void progressChanged(const QString &appId, int progress);
69+ void refresh();
66
70
67 void applicationAdded(const QModelIndex &parent, int row);
71 void applicationAdded(const QModelIndex &parent, int row);
68 void applicationRemoved(const QModelIndex &parent, int row);
72 void applicationRemoved(const QModelIndex &parent, int row);
@@ -70,7 +74,10 @@ private Q_SLOTS:
70
74
71private:
75private:
72 QList<LauncherItem*> m_list;
76 QList<LauncherItem*> m_list;
73− LauncherBackend *m_backend;
77+
78+ GSettings *m_settings;
79+ DBusInterface *m_dbusIface;
80+
74 ApplicationManagerInterface *m_appManager;
81 ApplicationManagerInterface *m_appManager;
75};
82};
76
83
plugins/Unity/Launcher/common/quicklistentry.cpp → plugins/Unity/Launcher/quicklistentry.cpprenamed

The content is the same.

plugins/Unity/Launcher/common/quicklistentry.h → plugins/Unity/Launcher/quicklistentry.hrenamed

The content is the same.

plugins/Unity/Launcher/quicklistmodel.h+1−1
@@ -20,7 +20,7 @@
20#ifndef QUICKLISTMODEL_H
20#ifndef QUICKLISTMODEL_H
21#define QUICKLISTMODEL_H
21#define QUICKLISTMODEL_H
22
22
23−#include "common/quicklistentry.h"
23+#include "quicklistentry.h"
24
24
25#include <unity/shell/launcher/QuickListModelInterface.h>
25#include <unity/shell/launcher/QuickListModelInterface.h>
26
26
plugins/Unity/Session/CMakeLists.txt+7
@@ -1,10 +1,16 @@
1+include(FindPkgConfig)
2+
3+pkg_search_module(GIO REQUIRED gio-2.0)
4+
1include_directories(
5include_directories(
2 ${CMAKE_CURRENT_SOURCE_DIR}
6 ${CMAKE_CURRENT_SOURCE_DIR}
7+ ${GIO_INCLUDE_DIRS}
3)
8)
4
9
5set(QMLSESSIONPLUGIN_SRC
10set(QMLSESSIONPLUGIN_SRC
6 plugin.cpp
11 plugin.cpp
7 dbusunitysessionservice.cpp
12 dbusunitysessionservice.cpp
13+ orientationlock.cpp
8 )
14 )
9
15
10add_library(UnitySession-qml MODULE
16add_library(UnitySession-qml MODULE
@@ -12,6 +18,7 @@ add_library(UnitySession-qml MODULE
12 )
18 )
13
19
14qt5_use_modules(UnitySession-qml DBus Qml)
20qt5_use_modules(UnitySession-qml DBus Qml)
21+target_link_libraries(UnitySession-qml ${GIO_LDFLAGS})
15
22
16# export the qmldir and qmltypes files
23# export the qmldir and qmltypes files
17add_unity8_plugin(Unity.Session 0.1 Unity/Session TARGETS UnitySession-qml)
24add_unity8_plugin(Unity.Session 0.1 Unity/Session TARGETS UnitySession-qml)
plugins/Unity/Session/orientationlock.cppnew file+74
@@ -0,0 +1,74 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it under
5+ * the terms of the GNU Lesser General Public License version 3, as published by
6+ * the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but WITHOUT
9+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+ * Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "orientationlock.h"
18+
19+#include <QDBusConnection>
20+#include <QDBusInterface>
21+
22+OrientationLock::OrientationLock(QObject *parent)
23+ : QObject(parent)
24+ , m_enabled(false)
25+ , m_savedOrientation(Qt::PortraitOrientation)
26+{
27+ systemSettings = g_settings_new("com.ubuntu.touch.system");
28+ g_signal_connect(systemSettings, "changed::rotation-lock",
29+ G_CALLBACK(OrientationLock::onEnabledChangedProxy), this);
30+ m_enabled = g_settings_get_boolean(systemSettings, "rotation-lock");
31+}
32+
33+OrientationLock::~OrientationLock()
34+{
35+ g_signal_handlers_disconnect_by_data(systemSettings, this);
36+ g_object_unref(systemSettings);
37+}
38+
39+bool OrientationLock::enabled() const
40+{
41+ return m_enabled;
42+}
43+
44+Qt::ScreenOrientation OrientationLock::savedOrientation() const
45+{
46+ return m_savedOrientation;
47+}
48+
49+void OrientationLock::onEnabledChangedProxy(GSettings */*settings*/, const gchar */*key*/, gpointer data)
50+{
51+ OrientationLock* _this = static_cast<OrientationLock*>(data);
52+ _this->onEnabledChanged();
53+}
54+
55+void OrientationLock::onEnabledChanged()
56+{
57+ const bool enabled = g_settings_get_boolean(systemSettings, "rotation-lock");
58+ if (m_enabled != enabled) {
59+ m_enabled = enabled;
60+ Q_EMIT enabledChanged();
61+ }
62+}
63+
64+void OrientationLock::setSavedOrientation(const Qt::ScreenOrientation orientation)
65+{
66+ if (orientation == m_savedOrientation) {
67+ return;
68+ }
69+
70+ m_savedOrientation = orientation;
71+
72+ //TODO - save value with dbus to persist over sessions
73+ Q_EMIT savedOrientationChanged();
74+}
plugins/Unity/Session/orientationlock.hnew file+62
@@ -0,0 +1,62 @@
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 ORIENTATIONLOCK_H
18+#define ORIENTATIONLOCK_H
19+
20+#include <gio/gio.h>
21+#include <QtCore/QObject>
22+#include <QtDBus/QDBusInterface>
23+
24+/**
25+ * @brief The OrientationLock class exports orientation lock related properties to QML
26+ * It has two properties:
27+ * - readonly boolean with the Orientation lock property state
28+ * - Qt::ScreenOrientation to save the locked orientation state across Sessions (only relevant if lock is true)
29+ */
30+class OrientationLock : public QObject
31+{
32+ Q_OBJECT
33+
34+ Q_PROPERTY(bool enabled READ enabled NOTIFY enabledChanged)
35+ Q_PROPERTY(Qt::ScreenOrientation savedOrientation READ savedOrientation WRITE setSavedOrientation
36+ NOTIFY savedOrientationChanged)
37+
38+public:
39+ explicit OrientationLock(QObject *parent = 0);
40+ ~OrientationLock();
41+
42+ bool enabled() const;
43+ Qt::ScreenOrientation savedOrientation() const;
44+ void setSavedOrientation(const Qt::ScreenOrientation orientation);
45+
46+Q_SIGNALS:
47+ void enabledChanged();
48+ void savedOrientationChanged();
49+
50+private Q_SLOTS:
51+ static void onEnabledChangedProxy(GSettings *settings, const gchar *key, gpointer data);
52+ void onEnabledChanged();
53+
54+private:
55+ QDBusInterface *dbusInterface;
56+ GSettings *systemSettings;
57+
58+ bool m_enabled;
59+ Qt::ScreenOrientation m_savedOrientation;
60+};
61+
62+#endif // ORIENTATIONLOCK_H
plugins/Unity/Session/plugin.cpp+8−4
@@ -18,23 +18,27 @@
18
18
19#include "plugin.h"
19#include "plugin.h"
20#include "dbusunitysessionservice.h"
20#include "dbusunitysessionservice.h"
21+#include "orientationlock.h"
21
22
22#include <QAbstractItemModel>
23#include <QAbstractItemModel>
23#include <QDBusConnection>
24#include <QDBusConnection>
24#include <QtQml/qqml.h>
25#include <QtQml/qqml.h>
25
26
26−static QObject *dbusunitysessionservice_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
27+static QObject *dbusunitysessionservice_provider(QQmlEngine */*engine*/, QJSEngine */*jsEngine*/)
27{
28{
28− Q_UNUSED(engine)
29− Q_UNUSED(scriptEngine)
30−
31 return new DBusUnitySessionService();
29 return new DBusUnitySessionService();
32}
30}
33
31
32+static QObject *orientationlock_provider(QQmlEngine */*engine*/, QJSEngine */*jsEngine*/)
33+{
34+ return new OrientationLock();
35+}
36+
34void SessionPlugin::registerTypes(const char *uri)
37void SessionPlugin::registerTypes(const char *uri)
35{
38{
36 qmlRegisterType<QAbstractItemModel>();
39 qmlRegisterType<QAbstractItemModel>();
37
40
38 Q_ASSERT(uri == QLatin1String("Unity.Session"));
41 Q_ASSERT(uri == QLatin1String("Unity.Session"));
39 qmlRegisterSingletonType<DBusUnitySessionService>(uri, 0, 1, "DBusUnitySessionService", dbusunitysessionservice_provider);
42 qmlRegisterSingletonType<DBusUnitySessionService>(uri, 0, 1, "DBusUnitySessionService", dbusunitysessionservice_provider);
43+ qmlRegisterSingletonType<OrientationLock>(uri, 0, 1, "OrientationLock", orientationlock_provider);
40}
44}
plugins/Utils/windowkeysfilter.cpp+1
@@ -31,6 +31,7 @@ bool WindowKeysFilter::eventFilter(QObject *watched, QEvent *event)
31{
31{
32 Q_ASSERT(!m_filteredWindow.isNull());
32 Q_ASSERT(!m_filteredWindow.isNull());
33 Q_ASSERT(watched == static_cast<QObject*>(m_filteredWindow.data()));
33 Q_ASSERT(watched == static_cast<QObject*>(m_filteredWindow.data()));
34+ Q_UNUSED(watched);
34
35
35 if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) {
36 if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) {
36 // Let QML see this event and decide if it does not want it
37 // Let QML see this event and decide if it does not want it
po/ja.po+15−15
@@ -8,14 +8,14 @@ msgstr ""
8"Project-Id-Version: unity\n"
8"Project-Id-Version: unity\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2014-08-26 18:24+0200\n"
10"POT-Creation-Date: 2014-08-26 18:24+0200\n"
11−"PO-Revision-Date: 2014-09-01 06:20+0000\n"
11+"PO-Revision-Date: 2014-09-18 09:47+0000\n"
12"Last-Translator: Mitsuya Shibata <mty.shibata@gmail.com>\n"
12"Last-Translator: Mitsuya Shibata <mty.shibata@gmail.com>\n"
13"Language-Team: Japanese <ja@li.org>\n"
13"Language-Team: Japanese <ja@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-09-02 07:20+0000\n"
17+"X-Launchpad-Export-Date: 2014-09-19 08:11+0000\n"
18−"X-Generator: Launchpad (build 17192)\n"
18+"X-Generator: Launchpad (build 17196)\n"
19
19
20#: plugins/LightDM/Greeter.cpp:130
20#: plugins/LightDM/Greeter.cpp:130
21msgid "Password: "
21msgid "Password: "
@@ -36,7 +36,7 @@ msgstr "ログアウト"
36
36
37#: qml/Components/Dialogs.qml:71
37#: qml/Components/Dialogs.qml:71
38msgid "Are you sure you want to log out?"
38msgid "Are you sure you want to log out?"
39−msgstr ""
39+msgstr "本当にログアウトしますか?"
40
40
41#: qml/Components/Dialogs.qml:73 qml/Components/Dialogs.qml:97
41#: qml/Components/Dialogs.qml:73 qml/Components/Dialogs.qml:97
42#: qml/Components/Dialogs.qml:122
42#: qml/Components/Dialogs.qml:122
@@ -54,7 +54,7 @@ msgstr "シャットダウン"
54
54
55#: qml/Components/Dialogs.qml:95
55#: qml/Components/Dialogs.qml:95
56msgid "Are you sure you want to shut down?"
56msgid "Are you sure you want to shut down?"
57−msgstr ""
57+msgstr "本当にシャットダウンしますか?"
58
58
59#: qml/Components/Dialogs.qml:119
59#: qml/Components/Dialogs.qml:119
60msgid "Reboot"
60msgid "Reboot"
@@ -62,17 +62,17 @@ msgstr "再起動"
62
62
63#: qml/Components/Dialogs.qml:120
63#: qml/Components/Dialogs.qml:120
64msgid "Are you sure you want to reboot?"
64msgid "Are you sure you want to reboot?"
65−msgstr ""
65+msgstr "本当に再起動しますか?"
66
66
67#: qml/Components/Dialogs.qml:144
67#: qml/Components/Dialogs.qml:144
68msgid "Power"
68msgid "Power"
69−msgstr ""
69+msgstr "電源"
70
70
71#: qml/Components/Dialogs.qml:145
71#: qml/Components/Dialogs.qml:145
72msgid ""
72msgid ""
73"Are you sure you would like\n"
73"Are you sure you would like\n"
74"to power off?"
74"to power off?"
75−msgstr ""
75+msgstr "本当に電源を切りますか?"
76
76
77#: qml/Components/Dialogs.qml:147
77#: qml/Components/Dialogs.qml:147
78msgid "Power off"
78msgid "Power off"
@@ -249,11 +249,11 @@ msgstr "パスワードを表示"
249
249
250#: qml/Notifications/NotificationMenuItemFactory.qml:120
250#: qml/Notifications/NotificationMenuItemFactory.qml:120
251msgid "Enter SIM PIN"
251msgid "Enter SIM PIN"
252−msgstr ""
252+msgstr "SIM PINの入力"
253
253
254#: qml/Notifications/NotificationMenuItemFactory.qml:121
254#: qml/Notifications/NotificationMenuItemFactory.qml:121
255msgid "Sorry, incorrect PIN"
255msgid "Sorry, incorrect PIN"
256−msgstr ""
256+msgstr "PINが違います"
257
257
258#: qml/Panel/ActiveCallHint.qml:77
258#: qml/Panel/ActiveCallHint.qml:77
259msgid "Tap to return to call..."
259msgid "Tap to return to call..."
@@ -297,19 +297,19 @@ msgstr "ローミング"
297
297
298#: qml/Shell.qml:279
298#: qml/Shell.qml:279
299msgid "Enter your passphrase"
299msgid "Enter your passphrase"
300−msgstr ""
300+msgstr "パスフレーズの入力"
301
301
302#: qml/Shell.qml:280
302#: qml/Shell.qml:280
303msgid "Sorry, incorrect passphrase"
303msgid "Sorry, incorrect passphrase"
304−msgstr ""
304+msgstr "パスフレーズが違います"
305
305
306#: qml/Shell.qml:282
306#: qml/Shell.qml:282
307msgid "Enter your passcode"
307msgid "Enter your passcode"
308−msgstr ""
308+msgstr "パスコードの入力"
309
309
310#: qml/Shell.qml:283
310#: qml/Shell.qml:283
311msgid "Sorry, incorrect passcode"
311msgid "Sorry, incorrect passcode"
312−msgstr "ごめんなさい、誤ったパスコードです"
312+msgstr "パスコードが違います"
313
313
314#: qml/Shell.qml:286
314#: qml/Shell.qml:286
315#, qt-format
315#, qt-format
@@ -319,7 +319,7 @@ msgstr "%1を入力してください"
319#: qml/Shell.qml:287
319#: qml/Shell.qml:287
320#, qt-format
320#, qt-format
321msgid "Sorry, incorrect %1"
321msgid "Sorry, incorrect %1"
322−msgstr ""
322+msgstr "%1が違います"
323
323
324#: qml/Shell.qml:323
324#: qml/Shell.qml:323
325msgid "Sorry, incorrect passphrase."
325msgid "Sorry, incorrect passphrase."
po/unity8.pot+36−32
@@ -8,7 +8,7 @@ msgid ""
8msgstr ""
8msgstr ""
9"Project-Id-Version: unity8\n"
9"Project-Id-Version: unity8\n"
10"Report-Msgid-Bugs-To: \n"
10"Report-Msgid-Bugs-To: \n"
11−"POT-Creation-Date: 2014-09-03 14:48+0100\n"
11+"POT-Creation-Date: 2014-09-18 20:12+0800\n"
12"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14"Language-Team: LANGUAGE <LL@li.org>\n"
14"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -21,12 +21,12 @@ msgstr ""
21msgid "Password: "
21msgid "Password: "
22msgstr ""
22msgstr ""
23
23
24−#: plugins/Unity/Launcher/launcheritem.cpp:43
24+#: plugins/Unity/Launcher/launcheritem.cpp:44
25−#: plugins/Unity/Launcher/launcheritem.cpp:73
25+#: plugins/Unity/Launcher/launcheritem.cpp:74
26msgid "Pin shortcut"
26msgid "Pin shortcut"
27msgstr ""
27msgstr ""
28
28
29−#: plugins/Unity/Launcher/launcheritem.cpp:73
29+#: plugins/Unity/Launcher/launcheritem.cpp:74
30msgid "Unpin shortcut"
30msgid "Unpin shortcut"
31msgstr ""
31msgstr ""
32
32
@@ -79,6 +79,10 @@ msgid "Power off"
79msgstr ""
79msgstr ""
80
80
81#: qml/Components/Dialogs.qml:157
81#: qml/Components/Dialogs.qml:157
82+msgid "Restart"
83+msgstr ""
84+
85+#: qml/Components/Dialogs.qml:167
82msgid "Cancel"
86msgid "Cancel"
83msgstr ""
87msgstr ""
84
88
@@ -153,28 +157,28 @@ msgstr ""
153msgid "Hello"
157msgid "Hello"
154msgstr ""
158msgstr ""
155
159
156−#: qml/Dash/GenericScopeView.qml:365 qml/Dash/GenericScopeView.qml:484
160+#: qml/Dash/GenericScopeView.qml:364 qml/Dash/GenericScopeView.qml:483
157msgid "See less"
161msgid "See less"
158msgstr ""
162msgstr ""
159
163
160−#: qml/Dash/GenericScopeView.qml:365
164+#: qml/Dash/GenericScopeView.qml:364
161msgid "See all"
165msgid "See all"
162msgstr ""
166msgstr ""
163
167
164−#: qml/Dash/GenericScopeView.qml:428 qml/Dash/PageHeader.qml:259
168+#: qml/Dash/GenericScopeView.qml:427 qml/Dash/PageHeader.qml:258
165#: qml/Panel/SearchIndicator.qml:27
169#: qml/Panel/SearchIndicator.qml:27
166msgid "Search"
170msgid "Search"
167msgstr ""
171msgstr ""
168
172
169−#: qml/Dash/PageHeader.qml:269
173+#: qml/Dash/PageHeader.qml:268
170msgid "Settings"
174msgid "Settings"
171msgstr ""
175msgstr ""
172
176
173−#: qml/Dash/PageHeader.qml:276
177+#: qml/Dash/PageHeader.qml:275
174msgid "Remove from Favorites"
178msgid "Remove from Favorites"
175msgstr ""
179msgstr ""
176
180
177−#: qml/Dash/PageHeader.qml:276
181+#: qml/Dash/PageHeader.qml:275
178msgid "Add to Favorites"
182msgid "Add to Favorites"
179msgstr ""
183msgstr ""
180
184
@@ -202,11 +206,11 @@ msgstr ""
202msgid "Manage Dash"
206msgid "Manage Dash"
203msgstr ""
207msgstr ""
204
208
205−#: qml/Dash/ScopesOverview.qml:422
209+#: qml/Dash/ScopesOverview.qml:428
206msgid "Done"
210msgid "Done"
207msgstr ""
211msgstr ""
208
212
209−#: qml/Dash/ScopesOverview.qml:448
213+#: qml/Dash/ScopesOverview.qml:454
210msgid "Store"
214msgid "Store"
211msgstr ""
215msgstr ""
212
216
@@ -242,15 +246,15 @@ msgstr ""
242msgid "Speaking..."
246msgid "Speaking..."
243msgstr ""
247msgstr ""
244
248
245−#: qml/Notifications/NotificationMenuItemFactory.qml:105
249+#: qml/Notifications/NotificationMenuItemFactory.qml:108
246msgid "Show password"
250msgid "Show password"
247msgstr ""
251msgstr ""
248
252
249−#: qml/Notifications/NotificationMenuItemFactory.qml:120
253+#: qml/Notifications/NotificationMenuItemFactory.qml:124
250msgid "Enter SIM PIN"
254msgid "Enter SIM PIN"
251msgstr ""
255msgstr ""
252
256
253−#: qml/Notifications/NotificationMenuItemFactory.qml:121
257+#: qml/Notifications/NotificationMenuItemFactory.qml:125
254msgid "Sorry, incorrect PIN"
258msgid "Sorry, incorrect PIN"
255msgstr ""
259msgstr ""
256
260
@@ -262,27 +266,27 @@ msgstr ""
262msgid "Conference"
266msgid "Conference"
263msgstr ""
267msgstr ""
264
268
265−#: qml/Panel/Indicators/MenuItemFactory.qml:735
269+#: qml/Panel/Indicators/MenuItemFactory.qml:651
266msgid "In queue…"
270msgid "In queue…"
267msgstr ""
271msgstr ""
268
272
269−#: qml/Panel/Indicators/MenuItemFactory.qml:739
273+#: qml/Panel/Indicators/MenuItemFactory.qml:655
270msgid "Downloading"
274msgid "Downloading"
271msgstr ""
275msgstr ""
272
276
273−#: qml/Panel/Indicators/MenuItemFactory.qml:741
277+#: qml/Panel/Indicators/MenuItemFactory.qml:657
274msgid "Paused, tap to resume"
278msgid "Paused, tap to resume"
275msgstr ""
279msgstr ""
276
280
277−#: qml/Panel/Indicators/MenuItemFactory.qml:743
281+#: qml/Panel/Indicators/MenuItemFactory.qml:659
278msgid "Canceled"
282msgid "Canceled"
279msgstr ""
283msgstr ""
280
284
281−#: qml/Panel/Indicators/MenuItemFactory.qml:745
285+#: qml/Panel/Indicators/MenuItemFactory.qml:661
282msgid "Finished"
286msgid "Finished"
283msgstr ""
287msgstr ""
284
288
285−#: qml/Panel/Indicators/MenuItemFactory.qml:747
289+#: qml/Panel/Indicators/MenuItemFactory.qml:663
286msgid "Failed, tap to retry"
290msgid "Failed, tap to retry"
287msgstr ""
291msgstr ""
288
292
@@ -294,51 +298,51 @@ msgstr ""
294msgid "Roaming"
298msgid "Roaming"
295msgstr ""
299msgstr ""
296
300
297−#: qml/Shell.qml:280
301+#: qml/Shell.qml:292
298msgid "Enter your passphrase"
302msgid "Enter your passphrase"
299msgstr ""
303msgstr ""
300
304
301−#: qml/Shell.qml:281
305+#: qml/Shell.qml:293
302msgid "Sorry, incorrect passphrase"
306msgid "Sorry, incorrect passphrase"
303msgstr ""
307msgstr ""
304
308
305−#: qml/Shell.qml:283
309+#: qml/Shell.qml:295
306msgid "Enter your passcode"
310msgid "Enter your passcode"
307msgstr ""
311msgstr ""
308
312
309−#: qml/Shell.qml:284
313+#: qml/Shell.qml:296
310msgid "Sorry, incorrect passcode"
314msgid "Sorry, incorrect passcode"
311msgstr ""
315msgstr ""
312
316
313−#: qml/Shell.qml:287
317+#: qml/Shell.qml:299
314#, qt-format
318#, qt-format
315msgid "Enter your %1"
319msgid "Enter your %1"
316msgstr ""
320msgstr ""
317
321
318−#: qml/Shell.qml:288
322+#: qml/Shell.qml:300
319#, qt-format
323#, qt-format
320msgid "Sorry, incorrect %1"
324msgid "Sorry, incorrect %1"
321msgstr ""
325msgstr ""
322
326
323−#: qml/Shell.qml:323
327+#: qml/Shell.qml:335
324msgid "Sorry, incorrect passphrase."
328msgid "Sorry, incorrect passphrase."
325msgstr ""
329msgstr ""
326
330
327−#: qml/Shell.qml:324
331+#: qml/Shell.qml:336
328msgid "Sorry, incorrect passcode."
332msgid "Sorry, incorrect passcode."
329msgstr ""
333msgstr ""
330
334
331−#: qml/Shell.qml:325
335+#: qml/Shell.qml:337
332msgid "This will be your last attempt."
336msgid "This will be your last attempt."
333msgstr ""
337msgstr ""
334
338
335−#: qml/Shell.qml:327
339+#: qml/Shell.qml:339
336msgid ""
340msgid ""
337"If passphrase is entered incorrectly, your phone will conduct a factory "
341"If passphrase is entered incorrectly, your phone will conduct a factory "
338"reset and all personal data will be deleted."
342"reset and all personal data will be deleted."
339msgstr ""
343msgstr ""
340
344
341−#: qml/Shell.qml:328
345+#: qml/Shell.qml:340
342msgid ""
346msgid ""
343"If passcode is entered incorrectly, your phone will conduct a factory reset "
347"If passcode is entered incorrectly, your phone will conduct a factory reset "
344"and all personal data will be deleted."
348"and all personal data will be deleted."
qml/Components/Dialogs.qml+10
@@ -153,6 +153,16 @@ Item {
153 }
153 }
154 color: UbuntuColors.red
154 color: UbuntuColors.red
155 }
155 }
156+ Button {
157+ text: i18n.tr("Restart")
158+ onClicked: {
159+ dBusUnitySessionServiceConnection.closeAllApps();
160+ DBusUnitySessionService.Reboot();
161+ PopupUtils.close(dialoguePower);
162+ d.dialogShown = false;
163+ }
164+ color: UbuntuColors.green
165+ }
156 Button {
166 Button {
157 text: i18n.tr("Cancel")
167 text: i18n.tr("Cancel")
158 onClicked: {
168 onClicked: {
qml/Dash/CardCarousel.qml+1−1
@@ -49,7 +49,7 @@ DashRenderer {
49 objectName: "carouselDelegate" + index
49 objectName: "carouselDelegate" + index
50
50
51 function clicked() { cardCarousel.clicked(index, model.result, loader.item, model) }
51 function clicked() { cardCarousel.clicked(index, model.result, loader.item, model) }
52− function pressAndHold() { cardCarousel.pressAndHold(index, model) }
52+ function pressAndHold() { cardCarousel.pressAndHold(index, model.result, model) }
53
53
54 sourceComponent: cardTool.cardComponent
54 sourceComponent: cardTool.cardComponent
55 onLoaded: {
55 onLoaded: {
qml/Dash/CardGrid.qml+1−1
@@ -74,7 +74,7 @@ DashRenderer {
74 Connections {
74 Connections {
75 target: loader.item
75 target: loader.item
76 onClicked: root.clicked(index, result, loader.item, model)
76 onClicked: root.clicked(index, result, loader.item, model)
77− onPressAndHold: root.pressAndHold(index, model)
77+ onPressAndHold: root.pressAndHold(index, result, model)
78 }
78 }
79 }
79 }
80 }
80 }
qml/Dash/CardHorizontalList.qml+1−1
@@ -55,7 +55,7 @@ DashRenderer {
55 Connections {
55 Connections {
56 target: loader.item
56 target: loader.item
57 onClicked: root.clicked(index, result, loader.item, model)
57 onClicked: root.clicked(index, result, loader.item, model)
58− onPressAndHold: root.pressAndHold(index, model)
58+ onPressAndHold: root.pressAndHold(index, result, model)
59 }
59 }
60 }
60 }
61 }
61 }
qml/Dash/CardVerticalJournal.qml+1−1
@@ -62,7 +62,7 @@ DashRenderer {
62 Connections {
62 Connections {
63 target: loader.item
63 target: loader.item
64 onClicked: root.clicked(index, result, loader.item, model)
64 onClicked: root.clicked(index, result, loader.item, model)
65− onPressAndHold: root.pressAndHold(index, model)
65+ onPressAndHold: root.pressAndHold(index, result, model)
66 }
66 }
67 }
67 }
68 }
68 }
qml/Dash/DashApplication.qml+2−2
@@ -19,8 +19,8 @@ import Ubuntu.Components 1.0
19import Ubuntu.Thumbnailer 0.1 // Register support for image://thumbnailer/ and image://albumart/
19import Ubuntu.Thumbnailer 0.1 // Register support for image://thumbnailer/ and image://albumart/
20
20
21MainView {
21MainView {
22− width: units.gu(40)
22+ width: applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
23− height: units.gu(71)
23+ height: applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(68)
24
24
25 useDeprecatedToolbar: false
25 useDeprecatedToolbar: false
26
26
qml/Dash/DashNavigation.qml+2−3
@@ -37,9 +37,8 @@ Item {
37 // FIXME this is only here for highlight purposes (see Background.qml, too)
37 // FIXME this is only here for highlight purposes (see Background.qml, too)
38 readonly property var background: backgroundItem
38 readonly property var background: backgroundItem
39
39
40+ visible: height != 0
40− visible: navigationButton.currentNavigation || altNavigationButton.currentNavigation
41+ height: navigationButton.currentNavigation || altNavigationButton.currentNavigation ? units.gu(5) : 0
41−
42− height: visible ? units.gu(5) : 0
43
42
44 QtObject {
43 QtObject {
45 id: d
44 id: d
qml/Dash/DashRenderer.qml+3−2
@@ -40,13 +40,14 @@ Item {
40
40
41 /// Emitted when the user clicked on an item
41 /// Emitted when the user clicked on an item
42 /// @param index is the index of the clicked item
42 /// @param index is the index of the clicked item
43− /// @param result result model of the cliked item, used for activation
43+ /// @param result result model of the clicked item, used for activation
44 /// @param item item that has been clicked
44 /// @param item item that has been clicked
45 /// @param itemModel model of the item
45 /// @param itemModel model of the item
46 signal clicked(int index, var result, var item, var itemModel)
46 signal clicked(int index, var result, var item, var itemModel)
47
47
48 /// Emitted when the user pressed and held on an item
48 /// Emitted when the user pressed and held on an item
49 /// @param index is the index of the held item
49 /// @param index is the index of the held item
50+ /// @param result result model of the clicked item, used for activation
50 /// @param itemModel model of the item
51 /// @param itemModel model of the item
51− signal pressAndHold(int index, var itemModel)
52+ signal pressAndHold(int index, var result, var itemModel)
52}
53}
qml/Dash/GenericScopeView.qml+85−35
@@ -47,6 +47,10 @@ FocusScope {
47
47
48 signal backClicked()
48 signal backClicked()
49
49
50+ onScopeChanged: {
51+ floatingSeeLess.companionBase = null;
52+ }
53+
50 function positionAtBeginning() {
54 function positionAtBeginning() {
51 categoryView.positionAtBeginning()
55 categoryView.positionAtBeginning()
52 }
56 }
@@ -66,13 +70,17 @@ FocusScope {
66 // so it's not implemented
70 // so it's not implemented
67 scope.activate(result)
71 scope.activate(result)
68 } else {
72 } else {
73+ if (scope.preview(result)) {
69− openPreview(index, resultsModel, limitedCategoryItemCount);
74+ openPreview(index, resultsModel, limitedCategoryItemCount);
75+ }
70 }
76 }
71 }
77 }
72
78
73− function itemPressedAndHeld(index, itemModel, resultsModel, limitedCategoryItemCount) {
79+ function itemPressedAndHeld(index, result, itemModel, resultsModel, limitedCategoryItemCount) {
74 if (itemModel.uri.indexOf("scope://") !== 0) {
80 if (itemModel.uri.indexOf("scope://") !== 0) {
81+ if (scope.preview(result)) {
75− openPreview(index, resultsModel, limitedCategoryItemCount);
82+ openPreview(index, resultsModel, limitedCategoryItemCount);
83+ }
76 }
84 }
77 }
85 }
78
86
@@ -142,6 +150,7 @@ FocusScope {
142 objectName: "categoryListView"
150 objectName: "categoryListView"
143
151
144 x: subPageLoader.open ? -width : 0
152 x: subPageLoader.open ? -width : 0
153+ visible: x != -width
145 Behavior on x { UbuntuNumberAnimation { } }
154 Behavior on x { UbuntuNumberAnimation { } }
146 width: parent.width
155 width: parent.width
147 height: floatingSeeLess.visible ? parent.height - floatingSeeLess.height + floatingSeeLess.yOffset
156 height: floatingSeeLess.visible ? parent.height - floatingSeeLess.height + floatingSeeLess.yOffset
@@ -152,11 +161,54 @@ FocusScope {
152 forceNoClip: subPageLoader.open
161 forceNoClip: subPageLoader.open
153 pixelAligned: true
162 pixelAligned: true
154
163
155− property Item expandedCategoryItem: null
164+ property string expandedCategoryId: ""
165+ property int runMaximizeAfterSizeChanges: 0
156
166
157 readonly property bool pageHeaderTotallyVisible: scopeView.showPageHeader &&
167 readonly property bool pageHeaderTotallyVisible: scopeView.showPageHeader &&
158 ((headerItemShownHeight == 0 && categoryView.contentY <= categoryView.originY) || (headerItemShownHeight == pageHeaderLoader.item.height))
168 ((headerItemShownHeight == 0 && categoryView.contentY <= categoryView.originY) || (headerItemShownHeight == pageHeaderLoader.item.height))
159
169
170+ onExpandedCategoryIdChanged: {
171+ var firstCreated = firstCreatedIndex();
172+ var shrinkingAny = false;
173+ var shrinkHeightDifference = 0;
174+ for (var i = 0; i < createdItemCount(); ++i) {
175+ var baseItem = item(firstCreated + i);
176+ if (baseItem.expandable) {
177+ var shouldExpand = baseItem.category === expandedCategoryId;
178+ if (shouldExpand != baseItem.expanded) {
179+ var animate = false;
180+ if (!subPageLoader.open) {
181+ var animateShrinking = !shouldExpand && baseItem.y + baseItem.item.collapsedHeight + baseItem.seeAllButton.height < categoryView.height;
182+ var animateGrowing = shouldExpand && baseItem.y + baseItem.height < categoryView.height;
183+ animate = shrinkingAny || animateShrinking || animateGrowing;
184+ }
185+
186+ if (!shouldExpand) {
187+ shrinkingAny = true;
188+ shrinkHeightDifference = baseItem.item.expandedHeight - baseItem.item.collapsedHeight;
189+ }
190+
191+ if (shouldExpand && !subPageLoader.open) {
192+ if (!shrinkingAny) {
193+ categoryView.maximizeVisibleArea(firstCreated + i, baseItem.item.expandedHeight + baseItem.seeAllButton.height);
194+ } else {
195+ // If the space that shrinking is smaller than the one we need to grow we'll call maximizeVisibleArea
196+ // after the shrink/grow animation ends
197+ var growHeightDifference = baseItem.item.expandedHeight - baseItem.item.collapsedHeight;
198+ if (growHeightDifference > shrinkHeightDifference) {
199+ runMaximizeAfterSizeChanges = 2;
200+ } else {
201+ runMaximizeAfterSizeChanges = 0;
202+ }
203+ }
204+ }
205+
206+ baseItem.expand(shouldExpand, animate);
207+ }
208+ }
209+ }
210+ }
211+
160 delegate: ListItems.Base {
212 delegate: ListItems.Base {
161 id: baseItem
213 id: baseItem
162 objectName: "dashCategory" + category
214 objectName: "dashCategory" + category
@@ -194,7 +246,7 @@ FocusScope {
194 // This can happen with the VJ that doesn't know how height it will be on creation
246 // This can happen with the VJ that doesn't know how height it will be on creation
195 // so doesn't set expandable until a bit too late for onLoaded
247 // so doesn't set expandable until a bit too late for onLoaded
196 if (expandable) {
248 if (expandable) {
197− var shouldExpand = baseItem === categoryView.expandedCategoryItem;
249+ var shouldExpand = baseItem.category === categoryView.expandedCategoryId;
198 baseItem.expand(shouldExpand, false /*animate*/);
250 baseItem.expand(shouldExpand, false /*animate*/);
199 }
251 }
200 }
252 }
@@ -215,9 +267,23 @@ FocusScope {
215 id: heightBehaviour
267 id: heightBehaviour
216 enabled: false
268 enabled: false
217 animation: UbuntuNumberAnimation {
269 animation: UbuntuNumberAnimation {
270+ duration: UbuntuAnimation.FastDuration
218 onRunningChanged: {
271 onRunningChanged: {
219 if (!running) {
272 if (!running) {
220 heightBehaviour.enabled = false
273 heightBehaviour.enabled = false
274+ if (categoryView.runMaximizeAfterSizeChanges > 0) {
275+ categoryView.runMaximizeAfterSizeChanges--;
276+ if (categoryView.runMaximizeAfterSizeChanges == 0) {
277+ var firstCreated = categoryView.firstCreatedIndex();
278+ for (var i = 0; i < categoryView.createdItemCount(); ++i) {
279+ var baseItem = categoryView.item(firstCreated + i);
280+ if (baseItem.category === categoryView.expandedCategoryId) {
281+ categoryView.maximizeVisibleArea(firstCreated + i, baseItem.item.expandedHeight + baseItem.seeAllButton.height);
282+ break;
283+ }
284+ }
285+ }
286+ }
221 }
287 }
222 }
288 }
223 }
289 }
@@ -245,7 +311,7 @@ FocusScope {
245 item.objectName = Qt.binding(function() { return categoryId })
311 item.objectName = Qt.binding(function() { return categoryId })
246 item.scopeStyle = scopeView.scopeStyle;
312 item.scopeStyle = scopeView.scopeStyle;
247 if (baseItem.expandable) {
313 if (baseItem.expandable) {
248− var shouldExpand = baseItem === categoryView.expandedCategoryItem;
314+ var shouldExpand = baseItem.category === categoryView.expandedCategoryId;
249 baseItem.expand(shouldExpand, false /*animate*/);
315 baseItem.expand(shouldExpand, false /*animate*/);
250 }
316 }
251 updateDelegateCreationRange();
317 updateDelegateCreationRange();
@@ -269,7 +335,7 @@ FocusScope {
269 }
335 }
270
336
271 onPressAndHold: {
337 onPressAndHold: {
272− scopeView.itemPressedAndHeld(index, itemModel, target.model, categoryItemCount());
338+ scopeView.itemPressedAndHeld(index, result, itemModel, target.model, categoryItemCount());
273 }
339 }
274
340
275 function categoryItemCount() {
341 function categoryItemCount() {
@@ -282,27 +348,6 @@ FocusScope {
282 }
348 }
283 Connections {
349 Connections {
284 target: categoryView
350 target: categoryView
285− onExpandedCategoryItemChanged: {
286− collapseAllButExpandedCategory();
287− }
288− function collapseAllButExpandedCategory() {
289− var item = rendererLoader.item;
290− if (baseItem.expandable) {
291− var shouldExpand = baseItem === categoryView.expandedCategoryItem;
292− if (shouldExpand != baseItem.expanded) {
293− // If the filter animation will be seen start it, otherwise, just flip the switch
294− var shrinkingVisible = !shouldExpand && y + item.collapsedHeight + seeAll.height < categoryView.height;
295− var growingVisible = shouldExpand && y + height < categoryView.height;
296− if (!subPageLoader.open || shouldExpand) {
297− var animate = shrinkingVisible || growingVisible;
298− baseItem.expand(shouldExpand, animate)
299− if (shouldExpand && !subPageLoader.open) {
300− categoryView.maximizeVisibleArea(index, item.expandedHeight + seeAll.height);
301− }
302− }
303− }
304− }
305− }
306 onOriginYChanged: rendererLoader.updateDelegateCreationRange();
351 onOriginYChanged: rendererLoader.updateDelegateCreationRange();
307 onContentYChanged: rendererLoader.updateDelegateCreationRange();
352 onContentYChanged: rendererLoader.updateDelegateCreationRange();
308 onHeightChanged: rendererLoader.updateDelegateCreationRange();
353 onHeightChanged: rendererLoader.updateDelegateCreationRange();
@@ -349,13 +394,15 @@ FocusScope {
349 left: parent.left
394 left: parent.left
350 right: parent.right
395 right: parent.right
351 }
396 }
352− height: seeAllLabel.visible ? seeAllLabel.font.pixelSize + units.gu(4) : 0
397+ height: baseItem.expandable && !baseItem.headerLink ? seeAllLabel.font.pixelSize + units.gu(4) : 0
398+ visible: height != 0
353
399
354 onClicked: {
400 onClicked: {
355− if (categoryView.expandedCategoryItem !== baseItem) {
401+ if (categoryView.expandedCategoryId !== baseItem.category) {
356− categoryView.expandedCategoryItem = baseItem;
402+ categoryView.expandedCategoryId = baseItem.category;
403+ floatingSeeLess.companionBase = baseItem;
357 } else {
404 } else {
358− categoryView.expandedCategoryItem = null;
405+ categoryView.expandedCategoryId = "";
359 }
406 }
360 }
407 }
361
408
@@ -369,7 +416,6 @@ FocusScope {
369 fontSize: "small"
416 fontSize: "small"
370 font.weight: Font.Bold
417 font.weight: Font.Bold
371 color: scopeStyle ? scopeStyle.foreground : Theme.palette.normal.baseText
418 color: scopeStyle ? scopeStyle.foreground : Theme.palette.normal.baseText
372− visible: baseItem.expandable && !baseItem.headerLink
373 }
419 }
374 }
420 }
375
421
@@ -455,7 +501,7 @@ FocusScope {
455 objectName: "floatingSeeLess"
501 objectName: "floatingSeeLess"
456
502
457 property Item companionTo: companionBase ? companionBase.seeAllButton : null
503 property Item companionTo: companionBase ? companionBase.seeAllButton : null
458− property Item companionBase: categoryView.expandedCategoryItem
504+ property Item companionBase: null
459 property bool showBecausePosition: false
505 property bool showBecausePosition: false
460 property real yOffset: 0
506 property real yOffset: 0
461
507
@@ -467,7 +513,7 @@ FocusScope {
467 height: seeLessLabel.font.pixelSize + units.gu(4)
513 height: seeLessLabel.font.pixelSize + units.gu(4)
468 visible: companionTo && showBecausePosition
514 visible: companionTo && showBecausePosition
469
515
470− onClicked: categoryView.expandedCategoryItem = null;
516+ onClicked: categoryView.expandedCategoryId = "";
471
517
472 function updateVisibility() {
518 function updateVisibility() {
473 var companionPos = companionTo.mapToItem(floatingSeeLess, 0, 0);
519 var companionPos = companionTo.mapToItem(floatingSeeLess, 0, 0);
@@ -476,6 +522,10 @@ FocusScope {
476 var posToBase = floatingSeeLess.mapToItem(companionBase, 0, -yOffset).y;
522 var posToBase = floatingSeeLess.mapToItem(companionBase, 0, -yOffset).y;
477 yOffset = Math.max(0, companionBase.item.collapsedHeight - posToBase);
523 yOffset = Math.max(0, companionBase.item.collapsedHeight - posToBase);
478 yOffset = Math.min(yOffset, height);
524 yOffset = Math.min(yOffset, height);
525+
526+ if (!showBecausePosition && categoryView.expandedCategoryId === "") {
527+ companionBase = null;
528+ }
479 }
529 }
480
530
481 Label {
531 Label {
qml/Dash/PageHeader.qml+4−3
@@ -37,7 +37,7 @@ Item {
37 property ListModel searchHistory: SearchHistoryModel
37 property ListModel searchHistory: SearchHistoryModel
38 property alias searchQuery: searchTextField.text
38 property alias searchQuery: searchTextField.text
39 property alias searchHint: searchTextField.placeholderText
39 property alias searchHint: searchTextField.placeholderText
40− property alias showSignatureLine: bottomBorder.visible
40+ property bool showSignatureLine: true
41
41
42 property alias bottomItem: bottomContainer.children
42 property alias bottomItem: bottomContainer.children
43 property int paginationCount: 0
43 property int paginationCount: 0
@@ -132,7 +132,7 @@ Item {
132 objectName: "headerContainer"
132 objectName: "headerContainer"
133 clip: contentY < height
133 clip: contentY < height
134 anchors { left: parent.left; top: parent.top; right: parent.right }
134 anchors { left: parent.left; top: parent.top; right: parent.right }
135− height: units.gu(6.5)
135+ height: units.gu(7)
136 contentHeight: headersColumn.height
136 contentHeight: headersColumn.height
137 interactive: false
137 interactive: false
138 contentY: showSearch ? 0 : height
138 contentY: showSearch ? 0 : height
@@ -287,7 +287,7 @@ Item {
287 id: imageComponent
287 id: imageComponent
288
288
289 Item {
289 Item {
290− anchors { fill: parent; topMargin: units.gu(1); bottomMargin: units.gu(1) }
290+ anchors { fill: parent; topMargin: units.gu(1.5); bottomMargin: units.gu(1.5) }
291 clip: true
291 clip: true
292 Image {
292 Image {
293 objectName: "titleImage"
293 objectName: "titleImage"
@@ -346,6 +346,7 @@ Item {
346
346
347 Rectangle {
347 Rectangle {
348 id: bottomBorder
348 id: bottomBorder
349+ visible: showSignatureLine
349 anchors {
350 anchors {
350 top: headerContainer.bottom
351 top: headerContainer.bottom
351 left: parent.left
352 left: parent.left
qml/Dash/Previews/PreviewPayments.qml+6
@@ -63,6 +63,12 @@ PreviewWidget {
63 paymentButton.opacity = 1;
63 paymentButton.opacity = 1;
64 root.triggered(widgetId, "purchaseError", source);
64 root.triggered(widgetId, "purchaseError", source);
65 }
65 }
66+ onPurchaseCancelled: {
67+ paymentButton.opacity = 1;
68+ // DO NOT emit the signal right now, as it causes a new
69+ // preview to be requested, when that is not what we want.
70+ //root.triggered(widgetId, "purchaseCancelled", source);
71+ }
66 }
72 }
67 }
73 }
68
74
qml/Dash/ScopesOverview.qml+1−1
@@ -203,7 +203,7 @@ Item {
203 }
203 }
204 width: parent.width
204 width: parent.width
205 clip: true
205 clip: true
206− title: i18n.tr("Manage Dash")
206+ title: i18n.tr("Manage Scopes")
207 scopeStyle: overviewScopeStyle
207 scopeStyle: overviewScopeStyle
208 showSignatureLine: false
208 showSignatureLine: false
209 searchEntryEnabled: true
209 searchEntryEnabled: true
qml/Launcher/Launcher.qml+1−2
@@ -243,10 +243,9 @@ Item {
243 onDraggingChanged: {
243 onDraggingChanged: {
244 if (!dragging) {
244 if (!dragging) {
245 if (distance > panel.width / 2) {
245 if (distance > panel.width / 2) {
246+ root.switchToNextState("visible")
246 if (distance > minimizeDistance) {
247 if (distance > minimizeDistance) {
247 root.dash();
248 root.dash();
248− } else {
249− root.switchToNextState("visible")
250 }
249 }
251 } else {
250 } else {
252 root.switchToNextState("")
251 root.switchToNextState("")
qml/Launcher/LauncherDelegate.qml+4−2
@@ -21,7 +21,8 @@ Item {
21 id: root
21 id: root
22
22
23 property string iconName
23 property string iconName
24− property int count: -1
24+ property int count: 0
25+ property bool countVisible: false
25 property int progress: -1
26 property int progress: -1
26 property bool itemFocused: false
27 property bool itemFocused: false
27 property real maxAngle: 0
28 property real maxAngle: 0
@@ -80,11 +81,12 @@ Item {
80 width: Math.min(root.itemWidth, Math.max(units.gu(2), countLabel.implicitWidth + units.gu(1)))
81 width: Math.min(root.itemWidth, Math.max(units.gu(2), countLabel.implicitWidth + units.gu(1)))
81 height: units.gu(2)
82 height: units.gu(2)
82 color: UbuntuColors.orange
83 color: UbuntuColors.orange
83− visible: root.count > 0
84+ visible: root.countVisible
84 borderSource: "none"
85 borderSource: "none"
85
86
86 Label {
87 Label {
87 id: countLabel
88 id: countLabel
89+ objectName: "countLabel"
88 text: root.count
90 text: root.count
89 anchors.centerIn: parent
91 anchors.centerIn: parent
90 // FIXME: verticalCenter seems to be off wee bit and QML doesn't have a centerLine
92 // FIXME: verticalCenter seems to be off wee bit and QML doesn't have a centerLine
qml/Launcher/LauncherPanel.qml+1
@@ -156,6 +156,7 @@ Rectangle {
156 height: itemHeight
156 height: itemHeight
157 iconName: model.icon
157 iconName: model.icon
158 count: model.count
158 count: model.count
159+ countVisible: model.countVisible
159 progress: model.progress
160 progress: model.progress
160 clipCorner: model.pinned
161 clipCorner: model.pinned
161 itemFocused: model.focused
162 itemFocused: model.focused
qml/Notifications/Notification.qml+2−2
@@ -82,7 +82,7 @@ Item {
82
82
83 // FIXME: using onCompleted because of LP: #1354406 workaround, has to be onOpacityChanged really
83 // FIXME: using onCompleted because of LP: #1354406 workaround, has to be onOpacityChanged really
84 Component.onCompleted: {
84 Component.onCompleted: {
85− if (opacity == 1.0 && hints["suppress-sound"] != "true" && sound.source) {
85+ if (opacity == 1.0 && hints["suppress-sound"] != "true" && sound.source != "") {
86 sound.play();
86 sound.play();
87 }
87 }
88 }
88 }
@@ -262,7 +262,7 @@ Item {
262 fontSize: "small"
262 fontSize: "small"
263 color: type == Notification.SnapDecision ? sdFontColor : Theme.palette.selected.backgroundText
263 color: type == Notification.SnapDecision ? sdFontColor : Theme.palette.selected.backgroundText
264 wrapMode: Text.WordWrap
264 wrapMode: Text.WordWrap
265− maximumLineCount: 2
265+ maximumLineCount: type == Notification.SnapDecision ? 12 : 2
266 elide: Text.ElideRight
266 elide: Text.ElideRight
267 textFormat: Text.PlainText
267 textFormat: Text.PlainText
268 }
268 }
qml/Shell.qml+33−1
@@ -15,6 +15,7 @@
15 */
15 */
16
16
17import QtQuick 2.0
17import QtQuick 2.0
18+import QtQuick.Window 2.0
18import AccountsService 0.1
19import AccountsService 0.1
19import GSettings 1.0
20import GSettings 1.0
20import Unity.Application 0.1
21import Unity.Application 0.1
@@ -61,6 +62,22 @@ Item {
61 property int failedLoginsDelayAttempts: 7 // number of failed logins
62 property int failedLoginsDelayAttempts: 7 // number of failed logins
62 property int failedLoginsDelaySeconds: 5 * 60 // seconds of forced waiting
63 property int failedLoginsDelaySeconds: 5 * 60 // seconds of forced waiting
63
64
65+ property int orientation
66+ readonly property int deviceOrientationAngle: Screen.angleBetween(Screen.primaryOrientation, Screen.orientation)
67+ onDeviceOrientationAngleChanged: {
68+ if (!OrientationLock.enabled) {
69+ orientation = Screen.orientation;
70+ }
71+ }
72+ readonly property bool orientationLockEnabled: OrientationLock.enabled
73+ onOrientationLockEnabledChanged: {
74+ if (orientationLockEnabled) {
75+ OrientationLock.savedOrientation = Screen.orientation;
76+ } else {
77+ orientation = Screen.orientation;
78+ }
79+ }
80+
64 function activateApplication(appId) {
81 function activateApplication(appId) {
65 if (ApplicationManager.findApplication(appId)) {
82 if (ApplicationManager.findApplication(appId)) {
66 ApplicationManager.requestFocusApplication(appId);
83 ApplicationManager.requestFocusApplication(appId);
@@ -85,6 +102,12 @@ Item {
85
102
86 Component.onCompleted: {
103 Component.onCompleted: {
87 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
104 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
105+ if (ApplicationManager.count > 0) {
106+ ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
107+ }
108+ if (orientationLockEnabled) {
109+ orientation = OrientationLock.savedOrientation;
110+ }
88 }
111 }
89
112
90 GSettings {
113 GSettings {
@@ -207,6 +230,11 @@ Item {
207 property: "inverseProgress"
230 property: "inverseProgress"
208 value: launcher.progress
231 value: launcher.progress
209 }
232 }
233+ Binding {
234+ target: applicationsDisplayLoader.item
235+ property: "orientation"
236+ value: shell.orientation
237+ }
210 }
238 }
211 }
239 }
212
240
@@ -536,7 +564,11 @@ Item {
536 available: edgeDemo.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && greeter.fakeActiveForApp === ""
564 available: edgeDemo.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && greeter.fakeActiveForApp === ""
537
565
538 onShowDashHome: showHome()
566 onShowDashHome: showHome()
567+ onDash: {
568+ if (ApplicationManager.focusedApplicationId != "unity8-dash") {
539− onDash: showDash()
569+ showDash()
570+ }
571+ }
540 onDashSwipeChanged: {
572 onDashSwipeChanged: {
541 if (dashSwipe) {
573 if (dashSwipe) {
542 dash.setCurrentScope("clickscope", false, true)
574 dash.setCurrentScope("clickscope", false, true)
qml/Stages/ApplicationWindow.qml+2
@@ -27,6 +27,7 @@ Item {
27
27
28 // to be set from outside
28 // to be set from outside
29 property QtObject application
29 property QtObject application
30+ property int orientation
30
31
31 QtObject {
32 QtObject {
32 id: d
33 id: d
@@ -111,6 +112,7 @@ Item {
111 id: sessionContainer
112 id: sessionContainer
112 session: application ? application.session : null
113 session: application ? application.session : null
113 anchors.fill: parent
114 anchors.fill: parent
115+ orientation: root.orientation
114
116
115 onSurfaceChanged: {
117 onSurfaceChanged: {
116 if (sessionContainer.surface) {
118 if (sessionContainer.surface) {
qml/Stages/PhoneStage.qml+9
@@ -18,6 +18,7 @@ import QtQuick 2.0
18import Ubuntu.Components 0.1
18import Ubuntu.Components 0.1
19import Ubuntu.Gestures 0.1
19import Ubuntu.Gestures 0.1
20import Unity.Application 0.1
20import Unity.Application 0.1
21+import Unity.Session 0.1
21import Utils 0.1
22import Utils 0.1
22import "../Components"
23import "../Components"
23
24
@@ -30,6 +31,7 @@ Rectangle {
30 property bool interactive
31 property bool interactive
31 property bool spreadEnabled: true // If false, animations and right edge will be disabled
32 property bool spreadEnabled: true // If false, animations and right edge will be disabled
32 property real inverseProgress: 0 // This is the progress for left edge drags, in pixels.
33 property real inverseProgress: 0 // This is the progress for left edge drags, in pixels.
34+ property int orientation: Qt.PortraitOrientation
33
35
34 color: "black"
36 color: "black"
35
37
@@ -372,6 +374,13 @@ Rectangle {
372 progress: appDelegate.progress - spreadView.positionMarker1
374 progress: appDelegate.progress - spreadView.positionMarker1
373 }
375 }
374
376
377+ Binding {
378+ target: appDelegate
379+ property: "orientation"
380+ when: appDelegate.interactive
381+ value: root.orientation
382+ }
383+
375 onClicked: {
384 onClicked: {
376 if (spreadView.phase == 2) {
385 if (spreadView.phase == 2) {
377 if (ApplicationManager.focusedApplicationId == ApplicationManager.get(index).appId) {
386 if (ApplicationManager.focusedApplicationId == ApplicationManager.get(index).appId) {
qml/Stages/SessionContainer.qml+7
@@ -24,12 +24,14 @@ Item {
24 readonly property var childSessions: session ? session.childSessions : null
24 readonly property var childSessions: session ? session.childSessions : null
25 readonly property alias surface: _surfaceContainer.surface
25 readonly property alias surface: _surfaceContainer.surface
26 property bool interactive: true
26 property bool interactive: true
27+ property int orientation
27
28
28 readonly property alias surfaceContainer: _surfaceContainer
29 readonly property alias surfaceContainer: _surfaceContainer
29 SurfaceContainer {
30 SurfaceContainer {
30 id: _surfaceContainer
31 id: _surfaceContainer
31 anchors.fill: parent
32 anchors.fill: parent
32 surface: session ? session.surface : null
33 surface: session ? session.surface : null
34+ orientation: root.orientation
33 }
35 }
34
36
35 Binding {
37 Binding {
@@ -74,6 +76,11 @@ Item {
74 target: item; when: item
76 target: item; when: item
75 property: "height"; value: root.height
77 property: "height"; value: root.height
76 }
78 }
79+
80+ Binding {
81+ target: item; when: item
82+ property: "orientation"; value: root.orientation
83+ }
77 }
84 }
78 }
85 }
79
86
qml/Stages/SpreadDelegate.qml+3
@@ -36,6 +36,7 @@ Item {
36 property alias swipeToCloseEnabled: dragArea.enabled
36 property alias swipeToCloseEnabled: dragArea.enabled
37 property bool closeable
37 property bool closeable
38 property alias application: appWindow.application
38 property alias application: appWindow.application
39+ property int orientation
39
40
40 Item {
41 Item {
41 objectName: "appWindowWithShadow"
42 objectName: "appWindowWithShadow"
@@ -56,12 +57,14 @@ Item {
56
57
57 ApplicationWindow {
58 ApplicationWindow {
58 id: appWindow
59 id: appWindow
60+ objectName: application ? "appWindow_" + application.appId : "appWindow_null"
59 anchors {
61 anchors {
60 fill: parent
62 fill: parent
61 topMargin: appWindow.fullscreen ? 0 : maximizedAppTopMargin
63 topMargin: appWindow.fullscreen ? 0 : maximizedAppTopMargin
62 }
64 }
63
65
64 interactive: root.interactive
66 interactive: root.interactive
67+ orientation: root.orientation
65 }
68 }
66 }
69 }
67
70
qml/Stages/SurfaceContainer.qml+5
@@ -22,6 +22,7 @@ Item {
22 objectName: "surfaceContainer"
22 objectName: "surfaceContainer"
23 property Item surface: null
23 property Item surface: null
24 property bool hadSurface: false
24 property bool hadSurface: false
25+ property int orientation
25
26
26 onSurfaceChanged: {
27 onSurfaceChanged: {
27 if (surface) {
28 if (surface) {
@@ -34,6 +35,10 @@ Item {
34 target: surface
35 target: surface
35 property: "anchors.fill"; value: root
36 property: "anchors.fill"; value: root
36 }
37 }
38+ Binding {
39+ target: surface
40+ property: "orientation"; value: root.orientation
41+ }
37
42
38 states: [
43 states: [
39 State {
44 State {
qml/Stages/TabletStage.qml+8
@@ -34,6 +34,7 @@ Rectangle {
34 property real maximizedAppTopMargin
34 property real maximizedAppTopMargin
35 property bool interactive
35 property bool interactive
36 property real inverseProgress: 0 // This is the progress for left edge drags, in pixels.
36 property real inverseProgress: 0 // This is the progress for left edge drags, in pixels.
37+ property int orientation: Qt.PortraitOrientation
37
38
38 onInverseProgressChanged: {
39 onInverseProgressChanged: {
39 // This can't be a simple binding because that would be triggered after this handler
40 // This can't be a simple binding because that would be triggered after this handler
@@ -535,6 +536,13 @@ Rectangle {
535 return progress;
536 return progress;
536 }
537 }
537
538
539+ Binding {
540+ target: spreadTile
541+ property: "orientation"
542+ when: spreadTile.interactive
543+ value: root.orientation
544+ }
545+
538 onClicked: {
546 onClicked: {
539 if (spreadView.phase == 2) {
547 if (spreadView.phase == 2) {
540 spreadView.snapTo(index);
548 spreadView.snapTo(index);
src/Dash/CMakeLists.txt+1
@@ -1,5 +1,6 @@
1set(DASH_SRCS
1set(DASH_SRCS
2 main.cpp
2 main.cpp
3+ ../ApplicationArguments.h
3 ../MouseTouchAdaptor.cpp
4 ../MouseTouchAdaptor.cpp
4 ../CachingNetworkManagerFactory.cpp
5 ../CachingNetworkManagerFactory.cpp
5)
6)
src/Dash/main.cpp+16−2
@@ -28,6 +28,7 @@
28
28
29#include <paths.h>
29#include <paths.h>
30#include "../MouseTouchAdaptor.h"
30#include "../MouseTouchAdaptor.h"
31+#include "../ApplicationArguments.h"
31#include "../CachingNetworkManagerFactory.h"
32#include "../CachingNetworkManagerFactory.h"
32
33
33int main(int argc, const char *argv[])
34int main(int argc, const char *argv[])
@@ -42,7 +43,11 @@ int main(int argc, const char *argv[])
42 "Allow the mouse to provide touch input");
43 "Allow the mouse to provide touch input");
43 parser.addOption(mousetouchOption);
44 parser.addOption(mousetouchOption);
44
45
46+ QCommandLineOption windowGeometryOption(QStringList() << "windowgeometry",
47+ "Specify the window geometry as [<width>x<height>]", "windowgeometry", "1");
48+ parser.addOption(windowGeometryOption);
49+
45− // FIXME Remove once we drop the need of the hint
50+ // FIXME Remove once we drop the need of the hint
46 QCommandLineOption desktopFileHintOption("desktop_file_hint",
51 QCommandLineOption desktopFileHintOption("desktop_file_hint",
47 "The desktop_file_hint option for QtMir", "hint_file");
52 "The desktop_file_hint option for QtMir", "hint_file");
48 parser.addOption(desktopFileHintOption);
53 parser.addOption(desktopFileHintOption);
@@ -52,6 +57,14 @@ int main(int argc, const char *argv[])
52 parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
57 parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
53 parser.process(*application);
58 parser.process(*application);
54
59
60+ ApplicationArguments qmlArgs;
61+ if (parser.isSet(windowGeometryOption) &&
62+ parser.value(windowGeometryOption).split('x').size() == 2)
63+ {
64+ QStringList geom = parser.value(windowGeometryOption).split('x');
65+ qmlArgs.setSize(geom.at(0).toInt(), geom.at(1).toInt());
66+ }
67+
55 if (getenv("QT_LOAD_TESTABILITY")) {
68 if (getenv("QT_LOAD_TESTABILITY")) {
56 QLibrary testLib(QLatin1String("qttestability"));
69 QLibrary testLib(QLatin1String("qttestability"));
57 if (testLib.load()) {
70 if (testLib.load()) {
@@ -72,7 +85,8 @@ int main(int argc, const char *argv[])
72
85
73 QQuickView* view = new QQuickView();
86 QQuickView* view = new QQuickView();
74 view->setResizeMode(QQuickView::SizeRootObjectToView);
87 view->setResizeMode(QQuickView::SizeRootObjectToView);
75− view->setTitle("Unity Dash");
88+ view->setTitle("Scopes");
89+ view->rootContext()->setContextProperty("applicationArguments", &qmlArgs);
76
90
77 // You will need this if you want to interact with touch-only components using a mouse
91 // You will need this if you want to interact with touch-only components using a mouse
78 // Needed only when manually testing on a desktop.
92 // Needed only when manually testing on a desktop.
tests/autopilot/unity8/shell/tests/__init__.py+4−21
@@ -432,12 +432,6 @@ class DashBaseTestCase(AutopilotTestCase):
432 binary_path = get_binary_path('unity8-dash')
432 binary_path = get_binary_path('unity8-dash')
433 dash_proxy = self.launch_dash(binary_path, self.environment)
433 dash_proxy = self.launch_dash(binary_path, self.environment)
434
434
435− if self.should_simulate_device():
436− # XXX Currently we have no way to launch the application with a
437− # specific size, so we must resize it after it's launched.
438− # --elopio - 2014-06-25
439− self.resize_window()
440−
441 self.dash_app = dash_helpers.DashApp(dash_proxy)
435 self.dash_app = dash_helpers.DashApp(dash_proxy)
442 self.dash = self.dash_app.dash
436 self.dash = self.dash_app.dash
443 self.wait_for_dash()
437 self.wait_for_dash()
@@ -470,18 +464,7 @@ class DashBaseTestCase(AutopilotTestCase):
470 simulate_device_fixture = self.useFixture(
464 simulate_device_fixture = self.useFixture(
471 toolkit_fixtures.SimulateDevice(
465 toolkit_fixtures.SimulateDevice(
472 self.app_width, self.app_height, self.grid_unit_px))
466 self.app_width, self.app_height, self.grid_unit_px))
473− self.app_width = simulate_device_fixture.app_width
467+ self.environment['GRID_UNIT_PX'] = simulate_device_fixture.grid_unit_px
474− self.app_height = simulate_device_fixture.app_height
468+ self.environment['ARGS'] = '-windowgeometry {0}x{1}'\
475−
469+ .format(simulate_device_fixture.app_width,
476− def resize_window(self):
470+ simulate_device_fixture.app_height)
477− application = self.process_manager.get_running_applications()[0]
478− window = application.get_windows()[0]
479− window.resize(self.app_width, self.app_height)
480−
481− def get_window_size():
482− _, _, window_width, window_height = window.geometry
483− return window_width, window_height
484−
485− self.assertThat(
486− get_window_size,
487− Eventually(Equals((self.app_width, self.app_height))))
tests/mocks/HudClient/fake_libhud_client.cpp+8−1
@@ -63,6 +63,7 @@ HudClientQuery *hud_client_query_new(const gchar *query)
63DeeModel *hud_client_query_get_results_model(HudClientQuery *cquery)
63DeeModel *hud_client_query_get_results_model(HudClientQuery *cquery)
64{
64{
65 Q_ASSERT(cquery == HudClientStub::m_query);
65 Q_ASSERT(cquery == HudClientStub::m_query);
66+ Q_UNUSED(cquery);
66 if (!resultsModel) {
67 if (!resultsModel) {
67 resultsModel = dee_sequence_model_new();
68 resultsModel = dee_sequence_model_new();
68 dee_model_set_schema_full(resultsModel, results_model_schema, G_N_ELEMENTS(results_model_schema));
69 dee_model_set_schema_full(resultsModel, results_model_schema, G_N_ELEMENTS(results_model_schema));
@@ -113,18 +114,21 @@ void hud_client_query_execute_toolbar_item(HudClientQuery *cquery, HudClientQuer
113{
114{
114 Q_ASSERT(cquery == HudClientStub::m_query);
115 Q_ASSERT(cquery == HudClientStub::m_query);
115 Q_UNUSED(timestamp);
116 Q_UNUSED(timestamp);
117+ Q_UNUSED(cquery);
116 HudClientStub::m_lastExecutedToolbarItem = item;
118 HudClientStub::m_lastExecutedToolbarItem = item;
117}
119}
118
120
119void hud_client_query_set_query(HudClientQuery *cquery, const char *query)
121void hud_client_query_set_query(HudClientQuery *cquery, const char *query)
120{
122{
121 Q_ASSERT(cquery == HudClientStub::m_query);
123 Q_ASSERT(cquery == HudClientStub::m_query);
124+ Q_UNUSED(cquery);
122 HudClientStub::m_lastSetQuery = QString::fromUtf8(query);
125 HudClientStub::m_lastSetQuery = QString::fromUtf8(query);
123}
126}
124
127
125void hud_client_query_execute_command(HudClientQuery *cquery, GVariant *command_key, guint timestamp)
128void hud_client_query_execute_command(HudClientQuery *cquery, GVariant *command_key, guint timestamp)
126{
129{
127 Q_ASSERT(cquery == HudClientStub::m_query);
130 Q_ASSERT(cquery == HudClientStub::m_query);
131+ Q_UNUSED(cquery);
128 Q_UNUSED(timestamp);
132 Q_UNUSED(timestamp);
129 for (uint i = 0; i < dee_model_get_n_rows(resultsModel); ++i) {
133 for (uint i = 0; i < dee_model_get_n_rows(resultsModel); ++i) {
130 DeeModelIter *iter = dee_model_get_iter_at_row(resultsModel, i);
134 DeeModelIter *iter = dee_model_get_iter_at_row(resultsModel, i);
@@ -144,6 +148,7 @@ void hud_client_query_execute_command(HudClientQuery *cquery, GVariant *command_
144HudClientParam *hud_client_query_execute_param_command(HudClientQuery *cquery, GVariant *command_key, guint timestamp)
148HudClientParam *hud_client_query_execute_param_command(HudClientQuery *cquery, GVariant *command_key, guint timestamp)
145{
149{
146 Q_ASSERT(cquery == HudClientStub::m_query);
150 Q_ASSERT(cquery == HudClientStub::m_query);
151+ Q_UNUSED(cquery);
147 Q_UNUSED(timestamp);
152 Q_UNUSED(timestamp);
148 for (uint i = 0; i < dee_model_get_n_rows(resultsModel); ++i) {
153 for (uint i = 0; i < dee_model_get_n_rows(resultsModel); ++i) {
149 DeeModelIter *iter = dee_model_get_iter_at_row(resultsModel, i);
154 DeeModelIter *iter = dee_model_get_iter_at_row(resultsModel, i);
@@ -165,12 +170,14 @@ HudClientParam *hud_client_query_execute_param_command(HudClientQuery *cquery, G
165void hud_client_query_voice_query(HudClientQuery *cquery)
170void hud_client_query_voice_query(HudClientQuery *cquery)
166{
171{
167 Q_ASSERT(cquery == HudClientStub::m_query);
172 Q_ASSERT(cquery == HudClientStub::m_query);
173+ Q_UNUSED(cquery);
168 // TODO We are not testing voice queries yet
174 // TODO We are not testing voice queries yet
169}
175}
170
176
171gboolean hud_client_query_toolbar_item_active(HudClientQuery *cquery, HudClientQueryToolbarItems item)
177gboolean hud_client_query_toolbar_item_active(HudClientQuery *cquery, HudClientQueryToolbarItems item)
172{
178{
173 Q_ASSERT(cquery == HudClientStub::m_query);
179 Q_ASSERT(cquery == HudClientStub::m_query);
180+ Q_UNUSED(cquery);
174 if (item == HUD_CLIENT_QUERY_TOOLBAR_HELP)
181 if (item == HUD_CLIENT_QUERY_TOOLBAR_HELP)
175 return HudClientStub::m_helpToolbarItemEnabled;
182 return HudClientStub::m_helpToolbarItemEnabled;
176
183
@@ -218,7 +225,7 @@ GActionGroup *hud_client_param_get_actions(HudClientParam *param)
218 if (!parametrizedActionGroup) {
225 if (!parametrizedActionGroup) {
219 GSimpleActionGroup *actionGroup = g_simple_action_group_new();
226 GSimpleActionGroup *actionGroup = g_simple_action_group_new();
220 GSimpleAction *action = g_simple_action_new("costAction", G_VARIANT_TYPE_DOUBLE);
227 GSimpleAction *action = g_simple_action_new("costAction", G_VARIANT_TYPE_DOUBLE);
221− g_simple_action_group_insert(actionGroup, G_ACTION(action));
228+ g_action_map_add_action(G_ACTION_MAP(actionGroup), G_ACTION(action));
222
229
223 g_signal_connect (action, "activate", G_CALLBACK (on_signal_activated), nullptr);
230 g_signal_connect (action, "activate", G_CALLBACK (on_signal_activated), nullptr);
224
231
tests/mocks/QtMultimedia/audio.cpp+1−1
@@ -77,7 +77,7 @@ void Audio::pause()
77
77
78void Audio::play()
78void Audio::play()
79{
79{
80− if (m_playbackState != PlayingState && m_source.isValid()) {
80+ if (m_playbackState != PlayingState) {
81 m_playbackState = PlayingState;
81 m_playbackState = PlayingState;
82 Q_EMIT playbackStateChanged(m_playbackState);
82 Q_EMIT playbackStateChanged(m_playbackState);
83
83
tests/mocks/Ubuntu/Payments/MockPayments.cpp+3−1
@@ -84,7 +84,9 @@ void MockPayments::start()
84
84
85void MockPayments::process()
85void MockPayments::process()
86{
86{
87− if (m_store_item_id == "com.example.invalid") {
87+ if (m_store_item_id == "com.example.cancel") {
88+ Q_EMIT purchaseCancelled();
89+ } else if (m_store_item_id == "com.example.invalid") {
88 Q_EMIT purchaseError("Purchase failed.");
90 Q_EMIT purchaseError("Purchase failed.");
89 } else {
91 } else {
90 Q_EMIT purchaseCompleted();
92 Q_EMIT purchaseCompleted();
tests/mocks/Ubuntu/Payments/MockPayments.h+1
@@ -51,6 +51,7 @@ Q_SIGNALS:
51
51
52 void purchaseError(const QString &error);
52 void purchaseError(const QString &error);
53 void purchaseCompleted();
53 void purchaseCompleted();
54+ void purchaseCancelled();
54
55
55private:
56private:
56 QString m_currency;
57 QString m_currency;
tests/mocks/Unity/Application/MirSurfaceItem.cpp+14
@@ -39,6 +39,7 @@ MirSurfaceItem::MirSurfaceItem(const QString& name,
39 , m_type(type)
39 , m_type(type)
40 , m_state(state)
40 , m_state(state)
41 , m_live(true)
41 , m_live(true)
42+ , m_orientation(Qt::PortraitOrientation)
42 , m_qmlItem(nullptr)
43 , m_qmlItem(nullptr)
43 , m_screenshotUrl(screenshot)
44 , m_screenshotUrl(screenshot)
44{
45{
@@ -108,6 +109,19 @@ void MirSurfaceItem::release()
108 }
109 }
109}
110}
110
111
112+void MirSurfaceItem::setOrientation(const Qt::ScreenOrientation orientation)
113+{
114+ if (m_orientation == orientation)
115+ return;
116+
117+ m_orientation = orientation;
118+
119+ QQmlProperty orientationProp(m_qmlItem, "orientation");
120+ orientationProp.write(QVariant::fromValue(orientation));
121+
122+ Q_EMIT orientationChanged();
123+}
124+
111void MirSurfaceItem::setSession(Session* session)
125void MirSurfaceItem::setSession(Session* session)
112{
126{
113 m_session = session;
127 m_session = session;
tests/mocks/Unity/Application/MirSurfaceItem.h+7
@@ -34,6 +34,7 @@ class MirSurfaceItem : public QQuickItem
34 Q_PROPERTY(State state READ state NOTIFY stateChanged)
34 Q_PROPERTY(State state READ state NOTIFY stateChanged)
35 Q_PROPERTY(QString name READ name CONSTANT)
35 Q_PROPERTY(QString name READ name CONSTANT)
36 Q_PROPERTY(bool live READ live NOTIFY liveChanged)
36 Q_PROPERTY(bool live READ live NOTIFY liveChanged)
37+ Q_PROPERTY(Qt::ScreenOrientation orientation READ orientation WRITE setOrientation NOTIFY orientationChanged DESIGNABLE false)
37
38
38public:
39public:
39 enum Type {
40 enum Type {
@@ -64,6 +65,9 @@ public:
64 State state() const { return m_state; }
65 State state() const { return m_state; }
65 QString name() const { return m_name; }
66 QString name() const { return m_name; }
66 bool live() const { return m_live; }
67 bool live() const { return m_live; }
68+ Qt::ScreenOrientation orientation() const { return m_orientation; }
69+
70+ void setOrientation(const Qt::ScreenOrientation orientation);
67
71
68 void setSession(Session* item);
72 void setSession(Session* item);
69 void setScreenshot(const QUrl& screenshot);
73 void setScreenshot(const QUrl& screenshot);
@@ -76,6 +80,7 @@ Q_SIGNALS:
76 void typeChanged(Type);
80 void typeChanged(Type);
77 void stateChanged(State);
81 void stateChanged(State);
78 void liveChanged(bool live);
82 void liveChanged(bool live);
83+ void orientationChanged();
79
84
80 void inputMethodRequested();
85 void inputMethodRequested();
81 void inputMethodDismissed();
86 void inputMethodDismissed();
@@ -104,6 +109,7 @@ private:
104 const Type m_type;
109 const Type m_type;
105 State m_state;
110 State m_state;
106 bool m_live;
111 bool m_live;
112+ Qt::ScreenOrientation m_orientation;
107
113
108 QQmlComponent *m_qmlContentComponent;
114 QQmlComponent *m_qmlContentComponent;
109 QQuickItem *m_qmlItem;
115 QQuickItem *m_qmlItem;
@@ -114,5 +120,6 @@ private:
114
120
115Q_DECLARE_METATYPE(MirSurfaceItem*)
121Q_DECLARE_METATYPE(MirSurfaceItem*)
116Q_DECLARE_METATYPE(QList<MirSurfaceItem*>)
122Q_DECLARE_METATYPE(QList<MirSurfaceItem*>)
123+Q_DECLARE_METATYPE(Qt::ScreenOrientation)
117
124
118#endif // MIRSURFACEITEM_H
125#endif // MIRSURFACEITEM_H
tests/mocks/Unity/Application/MirSurfaceItem.qml+12−2
@@ -21,12 +21,22 @@ Rectangle {
21 id: root
21 id: root
22 color: "pink"
22 color: "pink"
23
23
24− anchors.fill: parent
25−
26 implicitWidth: units.gu(40)
24 implicitWidth: units.gu(40)
27 implicitHeight: units.gu(70)
25 implicitHeight: units.gu(70)
28
26
27+ rotation: {
28+ if (orientation == Qt.PortraitOrientation) return 0;
29+ else if (orientation == Qt.LandscapeOrientation) return 90;
30+ else if (orientation == Qt.InvertedPortraitOrientation) return 180;
31+ else return 270;
32+ }
33+ x: parent ? (parent.width - width) / 2 : 0
34+ y: parent ? (parent.height - height) / 2 : 0
35+ width: parent ? (rotation == 0 || rotation == 180 ? parent.width : parent.height) : implicitWidth
36+ height: parent ? (rotation == 0 || rotation == 180 ? parent.height : parent.width) : implicitHeight
37+
29 property alias screenshotSource: screenshotImage.source
38 property alias screenshotSource: screenshotImage.source
39+ property int orientation: Qt.PortraitOrientation
30
40
31 property bool wantInputMethod: false
41 property bool wantInputMethod: false
32
42
tests/mocks/Unity/Launcher/CMakeLists.txt+1−1
@@ -1,4 +1,4 @@
1−pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=3)
1+pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=4)
2
2
3include_directories(
3include_directories(
4 ${CMAKE_CURRENT_SOURCE_DIR}
4 ${CMAKE_CURRENT_SOURCE_DIR}
tests/mocks/Unity/Launcher/MockLauncherItem.cpp+14
@@ -35,6 +35,7 @@ MockLauncherItem::MockLauncherItem(const QString &appId, const QString& desktopF
35 m_recent(false),
35 m_recent(false),
36 m_progress(-1),
36 m_progress(-1),
37 m_count(0),
37 m_count(0),
38+ m_countVisible(false),
38 m_focused(false),
39 m_focused(false),
39 m_quickList(new MockQuickListModel(this))
40 m_quickList(new MockQuickListModel(this))
40{
41{
@@ -131,6 +132,19 @@ void MockLauncherItem::setCount(int count)
131 }
132 }
132}
133}
133
134
135+bool MockLauncherItem::countVisible() const
136+{
137+ return m_countVisible;
138+}
139+
140+void MockLauncherItem::setCountVisible(bool countVisible)
141+{
142+ if (m_countVisible != countVisible) {
143+ m_countVisible = countVisible;
144+ Q_EMIT countVisibleChanged(countVisible);
145+ }
146+}
147+
134bool MockLauncherItem::focused() const
148bool MockLauncherItem::focused() const
135{
149{
136 return m_focused;
150 return m_focused;
tests/mocks/Unity/Launcher/MockLauncherItem.h+18−17
@@ -32,32 +32,30 @@ class MockLauncherItem: public LauncherItemInterface
32public:
32public:
33 MockLauncherItem(const QString &appId, const QString& desktopFile, const QString& name, const QString& icon, QObject* parent = 0);
33 MockLauncherItem(const QString &appId, const QString& desktopFile, const QString& name, const QString& icon, QObject* parent = 0);
34
34
35− QString appId() const;
35+ QString appId() const override;
36 QString desktopFile() const;
36 QString desktopFile() const;
37− QString name() const;
37+ QString name() const override;
38− QString icon() const;
38+ QString icon() const override;
39
39
40− bool pinned() const;
40+ bool pinned() const override;
41− void setPinned(bool pinned);
41+ bool running() const override;
42+ bool recent() const override;
43+ int progress() const override;
44+ int count() const override;
45+ bool countVisible() const override;
46+ bool focused() const;
42
47
43− bool running() const;
48+ unity::shell::launcher::QuickListModelInterface *quickList() const;
44− void setRunning(bool running);
45
49
46− bool recent() const;
50+private:
51+ void setPinned(bool pinned);
52+ void setRunning(bool running);
47 void setRecent(bool recent);
53 void setRecent(bool recent);
48−
49− int progress() const;
50 void setProgress(int progress);
54 void setProgress(int progress);
51−
52− int count() const;
53 void setCount(int count);
55 void setCount(int count);
54−
56+ void setCountVisible(bool countVisible);
55− bool focused() const;
56 void setFocused(bool focused);
57 void setFocused(bool focused);
57
58
58− unity::shell::launcher::QuickListModelInterface *quickList() const;
59−
60−private:
61 QString m_appId;
59 QString m_appId;
62 QString m_desktopFile;
60 QString m_desktopFile;
63 QString m_name;
61 QString m_name;
@@ -67,8 +65,11 @@ private:
67 bool m_recent;
65 bool m_recent;
68 int m_progress;
66 int m_progress;
69 int m_count;
67 int m_count;
68+ bool m_countVisible;
70 bool m_focused;
69 bool m_focused;
71 MockQuickListModel *m_quickList;
70 MockQuickListModel *m_quickList;
71+
72+ friend class MockLauncherModel;
72};
73};
73
74
74#endif // MOCKLAUNCHERITEM_H
75#endif // MOCKLAUNCHERITEM_H
tests/mocks/Unity/Launcher/MockLauncherModel.cpp+10
@@ -33,26 +33,32 @@ MockLauncherModel::MockLauncherModel(QObject* parent): LauncherModelInterface(pa
33 m_list.append(item);
33 m_list.append(item);
34 item = new MockLauncherItem("gallery-app", "/usr/share/applications/gallery-app.desktop", "Gallery", "gallery", this);
34 item = new MockLauncherItem("gallery-app", "/usr/share/applications/gallery-app.desktop", "Gallery", "gallery", this);
35 item->setProgress(50);
35 item->setProgress(50);
36+ item->setCountVisible(true);
36 m_list.append(item);
37 m_list.append(item);
37 item = new MockLauncherItem("facebook-webapp", "/usr/share/applications/facebook-webapp.desktop", "Facebook", "facebook", this);
38 item = new MockLauncherItem("facebook-webapp", "/usr/share/applications/facebook-webapp.desktop", "Facebook", "facebook", this);
38 item->setProgress(150);
39 item->setProgress(150);
39 m_list.append(item);
40 m_list.append(item);
40 item = new MockLauncherItem("webbrowser-app", "/usr/share/applications/webbrowser-app.desktop", "Browser", "browser", this);
41 item = new MockLauncherItem("webbrowser-app", "/usr/share/applications/webbrowser-app.desktop", "Browser", "browser", this);
41 item->setCount(1);
42 item->setCount(1);
43+ item->setCountVisible(true);
42 m_list.append(item);
44 m_list.append(item);
43 item = new MockLauncherItem("twitter-webapp", "/usr/share/applications/twitter-webapp.desktop", "Twitter", "twitter", this);
45 item = new MockLauncherItem("twitter-webapp", "/usr/share/applications/twitter-webapp.desktop", "Twitter", "twitter", this);
44 item->setCount(12);
46 item->setCount(12);
47+ item->setCountVisible(true);
45 item->setPinned(true);
48 item->setPinned(true);
46 m_list.append(item);
49 m_list.append(item);
47 item = new MockLauncherItem("gmail-webapp", "/usr/share/applications/gmail-webapp.desktop", "GMail", "gmail", this);
50 item = new MockLauncherItem("gmail-webapp", "/usr/share/applications/gmail-webapp.desktop", "GMail", "gmail", this);
48 item->setCount(123);
51 item->setCount(123);
52+ item->setCountVisible(true);
49 m_list.append(item);
53 m_list.append(item);
50 item = new MockLauncherItem("ubuntu-weather-app", "/usr/share/applications/ubuntu-weather-app.desktop", "Weather", "weather", this);
54 item = new MockLauncherItem("ubuntu-weather-app", "/usr/share/applications/ubuntu-weather-app.desktop", "Weather", "weather", this);
51 item->setCount(1234567890);
55 item->setCount(1234567890);
56+ item->setCountVisible(true);
52 m_list.append(item);
57 m_list.append(item);
53 item = new MockLauncherItem("notes-app", "/usr/share/applications/notes-app.desktop", "Notepad", "notepad", this);
58 item = new MockLauncherItem("notes-app", "/usr/share/applications/notes-app.desktop", "Notepad", "notepad", this);
54 item->setProgress(50);
59 item->setProgress(50);
55 item->setCount(5);
60 item->setCount(5);
61+ item->setCountVisible(true);
56 item->setFocused(true);
62 item->setFocused(true);
57 item->setPinned(true);
63 item->setPinned(true);
58 m_list.append(item);
64 m_list.append(item);
@@ -76,6 +82,8 @@ QVariant MockLauncherModel::data(const QModelIndex& index, int role) const
76 LauncherItemInterface *item = m_list.at(index.row());
82 LauncherItemInterface *item = m_list.at(index.row());
77 switch(role)
83 switch(role)
78 {
84 {
85+ case RoleAppId:
86+ return item->appId();
79 case RoleName:
87 case RoleName:
80 return item->name();
88 return item->name();
81 case RoleIcon:
89 case RoleIcon:
@@ -90,6 +98,8 @@ QVariant MockLauncherModel::data(const QModelIndex& index, int role) const
90 return item->progress();
98 return item->progress();
91 case RoleCount:
99 case RoleCount:
92 return item->count();
100 return item->count();
101+ case RoleCountVisible:
102+ return item->countVisible();
93 case RoleFocused:
103 case RoleFocused:
94 return item->focused();
104 return item->focused();
95 }
105 }
tests/mocks/Unity/fake_categories.cpp+95−136
@@ -29,25 +29,12 @@ Categories::Categories(int category_count, QObject* parent)
29
29
30int Categories::rowCount(const QModelIndex& /*parent*/) const
30int Categories::rowCount(const QModelIndex& /*parent*/) const
31{
31{
32− return m_category_count + m_specialCategories.count();
32+ return m_category_count;
33}
33}
34
34
35−void Categories::addSpecialCategory(QString const& categoryId, QString const& name, QString const& icon, QString const& rawTemplate, QObject* countObject)
35+void Categories::addSpecialCategory(QString const& /*categoryId*/, QString const& /*name*/, QString const& /*icon*/, QString const& /*rawTemplate*/, QObject* /*countObject*/)
36{
36{
37− CategoryData catData;
37+ qFatal("Using un-implemented Categories::addSpecialCategory");
38− catData.categoryId = categoryId;
39− catData.name = name;
40− catData.icon = icon;
41− catData.rawTemplate = rawTemplate;
42− catData.countObject = countObject;
43−
44− beginInsertRows(QModelIndex(), 0, 0);
45− m_specialCategories.prepend(catData);
46− endInsertRows();
47−
48− if (countObject) {
49− connect(countObject, SIGNAL(countChanged()), this, SLOT(countChanged()));
50− }
51}
38}
52
39
53bool Categories::overrideCategoryJson(QString const& /* categoryId */, QString const& /* json */)
40bool Categories::overrideCategoryJson(QString const& /* categoryId */, QString const& /* json */)
@@ -55,23 +42,6 @@ bool Categories::overrideCategoryJson(QString const& /* categoryId */, QString c
55 qFatal("Using un-implemented Categories::overrideCategoryJson");
42 qFatal("Using un-implemented Categories::overrideCategoryJson");
56}
43}
57
44
58−void Categories::countChanged()
59−{
60− QObject* countObject = sender();
61−
62− for (int i = 0; i < m_specialCategories.count(); ++i) {
63− const CategoryData &catData = m_specialCategories.at(i);
64− if (catData.countObject == countObject) {
65− QVector<int> roles;
66− roles.append(RoleCount);
67−
68− QModelIndex changedIndex(index(i));
69− dataChanged(changedIndex, changedIndex, roles);
70− break;
71− }
72− }
73−}
74−
75QVariant
45QVariant
76Categories::data(const QModelIndex& index, int role) const
46Categories::data(const QModelIndex& index, int role) const
77{
47{
@@ -79,115 +49,74 @@ Categories::data(const QModelIndex& index, int role) const
79 return QVariant();
49 return QVariant();
80 }
50 }
81
51
82− ResultsModel *resultsModel = m_resultsModels[index.row()];
52+ ResultsModel *resultsModel = resultModel(index.row());
83− if (!resultsModel) {
84− resultsModel = new ResultsModel(15, index.row());
85− m_resultsModels[index.row()] = resultsModel;
86− }
87− if (index.row() < m_specialCategories.count()) {
88− const CategoryData &catData = m_specialCategories.at(index.row());
89− switch (role) {
53+ switch (role) {
90− case RoleCategoryId:
54+ case RoleCategoryId:
91− return catData.categoryId;
55+ return QString("%1").arg(index.row());
92− case RoleName:
56+ case RoleName:
93− return catData.name;
57+ return QString("Category %1").arg(index.row());
94− case RoleIcon:
58+ case RoleIcon:
95− return catData.icon;
59+ return "gtk-apply";
96− case RoleRawRendererTemplate:
60+ case RoleRawRendererTemplate:
97− return catData.rawTemplate;
61+ qFatal("Using un-implemented RoleRawRendererTemplate Categories role");
62+ return QVariant();
98− case RoleRenderer:
63+ case RoleRenderer:
99− {
64+ {
100− QVariantMap map;
65+ QVariantMap map;
101− if (index.row() % 2 == 0) {
66+ if (index.row() % 2 == 0) {
102− map["category-layout"] = "grid";
67+ map["category-layout"] = "grid";
103− } else {
68+ } else {
104− map["category-layout"] = "carousel";
69+ map["category-layout"] = "carousel";
105− map["overlay"] = true;
106− }
107− map["card-size"] = "small";
108− return map;
109 }
70 }
110− case RoleComponents:
71+ if (index.row() == 18) {
111− {
72+ map["category-layout"] = "horizontal-list";
112− QVariantMap map, artMap, attributeMap;
73+ }
113− artMap["aspect-ratio"] = "1.0";
74+ if (index.row() == 19) {
114− artMap["field"] = "art";
75+ map["category-layout"] = "grid";
115− map["art"] = artMap;
76+ map["collapsed-rows"] = 0;
116− map["title"] = "HOLA";
77+ }
117− attributeMap["field"] = "attribute";
78+ map["card-size"] = "small";
118− map["attributes"] = attributeMap;
79+
119− return map;
80+ map["category-layout"] = m_layouts.value(index.row(), map["category-layout"].toString());
81+
82+ if (map["category-layout"] == "carousel") {
83+ map["overlay"] = true;
120 }
84 }
121− case RoleHeaderLink:
85+
122− return QString();
123− case RoleResults:
124− return QVariant();
86+ return map;
125− case RoleCount:
126− return catData.countObject->property("count");
127− default:
128− qFatal("Using un-implemented Categories role");
129− return QVariant();
130 }
87 }
131− } else {
88+ case RoleComponents:
132− switch (role) {
133− case RoleCategoryId:
134− return QString("%1").arg(index.row());
135− case RoleName:
136− return QString("Category %1").arg(index.row());
137− case RoleIcon:
138− return "gtk-apply";
139− case RoleRawRendererTemplate:
140− qFatal("Using un-implemented RoleRawRendererTemplate Categories role");
141− return QVariant();
142− case RoleRenderer:
143− {
89+ {
144− QVariantMap map;
90+ QVariantMap map, artMap, attributeMap;
145− if (index.row() % 2 == 0) {
91+ if (index.row() % 2 != 0) {
146− map["category-layout"] = "grid";
92+ artMap["aspect-ratio"] = QString("1.%1").arg(index.row());
147− } else {
93+ } else {
148− map["category-layout"] = "carousel";
94+ artMap["aspect-ratio"] = "1.0";
149− map["card-size"] = "medium";
150− map["overlay"] = true;
151− }
152− if (index.row() == 18) {
153− map["category-layout"] = "horizontal-list";
154− }
155− if (index.row() == 19) {
156− map["category-layout"] = "grid";
157− map["collapsed-rows"] = 0;
158− }
159− map["card-size"] = "small";
160− return map;
161 }
95 }
162− case RoleComponents:
163− {
164− QVariantMap map, artMap, attributeMap;
165− if (index.row() % 2 != 0) {
166− artMap["aspect-ratio"] = QString("1.%1").arg(index.row());
167− } else {
168− artMap["aspect-ratio"] = "1.0";
169− }
170− artMap["field"] = "art";
96+ artMap["field"] = "art";
171− map["art"] = artMap;
97+ map["art"] = artMap;
172− map["title"] = "HOLA";
98+ map["title"] = "HOLA";
173− map["subtitle"] = "HOLA";
99+ map["subtitle"] = "HOLA";
174− attributeMap["field"] = "attribute";
100+ attributeMap["field"] = "attribute";
175− map["attributes"] = attributeMap;
101+ map["attributes"] = attributeMap;
176− return map;
102+ return map;
103+ }
104+ case RoleHeaderLink:
105+ {
106+ QString res;
107+ if (index.row() == 1 || index.row() == 4) {
108+ res = QString("scope://query/1");
177 }
109 }
178− case RoleHeaderLink:
110+ res = m_headerLinks.value(index.row(), res);
179− if (index.row() == 1 || index.row() == 4) {
180− return QString("scope://query/1");
181− }
182− return QString();
183− case RoleResults:
184− return QVariant::fromValue(resultsModel);
185− case RoleCount:
186− return resultsModel->rowCount();
187− default:
188− qFatal("Using un-implemented Categories role");
189− return QVariant();
111+ return res;
190 }
112 }
113+ case RoleResults:
114+ return QVariant::fromValue(resultsModel);
115+ case RoleCount:
116+ return resultsModel->rowCount();
117+ default:
118+ qFatal("Using un-implemented Categories role");
119+ return QVariant();
191 }
120 }
192}
121}
193
122
@@ -196,3 +125,33 @@ Categories::data(int row, int role) const
196{
125{
197 return data(index(row, 0), role);
126 return data(index(row, 0), role);
198}
127}
128+
129+void Categories::setCount(int count)
130+{
131+ if (m_category_count != count) {
132+ beginResetModel(); // This is just for test setup so we can be lazy and reset
133+ m_category_count = count;
134+ endResetModel();
135+ }
136+}
137+
138+ResultsModel* Categories::resultModel(int row) const
139+{
140+ ResultsModel *result = m_resultsModels[row];
141+ if (!result) {
142+ Categories *that = const_cast<Categories*>(this);
143+ result = new ResultsModel(15, row, that);
144+ m_resultsModels[row] = result;
145+ }
146+ return result;
147+}
148+
149+void Categories::setLayout(int row, const QString &layout)
150+{
151+ m_layouts[row] = layout;
152+}
153+
154+void Categories::setHeaderLink(int row, const QString &headerLink)
155+{
156+ m_headerLinks[row] = headerLink;
157+}
tests/mocks/Unity/fake_categories.h+7−12
@@ -39,22 +39,17 @@ public:
39
39
40 Q_INVOKABLE QVariant data(int row, int role) const;
40 Q_INVOKABLE QVariant data(int row, int role) const;
41
41
42−private Q_SLOTS:
42+ // For testing purposes
43− void countChanged();
43+ Q_INVOKABLE void setCount(int count);
44+ Q_INVOKABLE ResultsModel* resultModel(int row) const;
45+ Q_INVOKABLE void setLayout(int row, const QString &layout);
46+ Q_INVOKABLE void setHeaderLink(int row, const QString &headerLink);
44
47
45private:
48private:
46 mutable QHash<int, ResultsModel*> m_resultsModels;
49 mutable QHash<int, ResultsModel*> m_resultsModels;
50+ QHash<int, QString> m_layouts;
51+ QHash<int, QString> m_headerLinks;
47 int m_category_count;
52 int m_category_count;
48−
49− struct CategoryData {
50− QString categoryId;
51− QString name;
52− QString icon;
53− QString rawTemplate;
54− QObject* countObject;
55− };
56−
57− QList<CategoryData> m_specialCategories;
58};
53};
59
54
60#endif // FAKE_CATEGORIES_H
55#endif // FAKE_CATEGORIES_H
tests/mocks/Unity/fake_resultsmodel.cpp+9
@@ -44,6 +44,15 @@ void ResultsModel::setCategoryId(QString const& /*id*/)
44 qFatal("Calling un-implemented ResultsModel::setCategoryId");
44 qFatal("Calling un-implemented ResultsModel::setCategoryId");
45}
45}
46
46
47+void ResultsModel::setResultCount(int result_count)
48+{
49+ if (m_result_count != result_count) {
50+ beginResetModel(); // This is just for test setup so we can be lazy and reset
51+ m_result_count = result_count;
52+ endResetModel();
53+ }
54+}
55+
47int ResultsModel::rowCount(const QModelIndex& parent) const
56int ResultsModel::rowCount(const QModelIndex& parent) const
48{
57{
49 Q_UNUSED(parent);
58 Q_UNUSED(parent);
tests/mocks/Unity/fake_resultsmodel.h+3
@@ -41,6 +41,9 @@ public:
41 /* setters */
41 /* setters */
42 void setCategoryId(QString const& id) override;
42 void setCategoryId(QString const& id) override;
43
43
44+ // For testing purposes
45+ Q_INVOKABLE void setResultCount(int result_count);
46+
44private:
47private:
45 int m_result_count;
48 int m_result_count;
46 int m_categoryId;
49 int m_categoryId;
tests/mocks/Unity/fake_scope.cpp+26−4
@@ -28,7 +28,7 @@ Scope::Scope(Scopes* parent) : Scope("MockScope5", "Mock Scope", false, parent)
28{
28{
29}
29}
30
30
31−Scope::Scope(QString const& id, QString const& name, bool favorite, Scopes* parent, int categories)
31+Scope::Scope(QString const& id, QString const& name, bool favorite, Scopes* parent, int categories, bool returnNullPreview)
32 : unity::shell::scopes::ScopeInterface(parent)
32 : unity::shell::scopes::ScopeInterface(parent)
33 , m_id(id)
33 , m_id(id)
34 , m_name(name)
34 , m_name(name)
@@ -41,6 +41,7 @@ Scope::Scope(QString const& id, QString const& name, bool favorite, Scopes* pare
41 , m_categories(new Categories(categories, this))
41 , m_categories(new Categories(categories, this))
42 , m_openScope(nullptr)
42 , m_openScope(nullptr)
43 , m_settings(new SettingsModel(this))
43 , m_settings(new SettingsModel(this))
44+ , m_returnNullPreview(returnNullPreview)
44{
45{
45}
46}
46
47
@@ -145,6 +146,23 @@ void Scope::setFavorite(const bool favorite)
145 Q_EMIT favoriteChanged();
146 Q_EMIT favoriteChanged();
146 }
147 }
147}
148}
149+
150+void Scope::setId(const QString &id)
151+{
152+ if (id != m_id) {
153+ m_id = id;
154+ Q_EMIT idChanged();
155+ }
156+}
157+
158+void Scope::setName(const QString &name)
159+{
160+ if (name != m_name) {
161+ m_name = name;
162+ Q_EMIT nameChanged();
163+ }
164+}
165+
148void Scope::setSearchInProgress(const bool inProg)
166void Scope::setSearchInProgress(const bool inProg)
149{
167{
150 if (inProg != m_searching) {
168 if (inProg != m_searching) {
@@ -175,9 +193,13 @@ PreviewStack* Scope::preview(QVariant const& result)
175{
193{
176 Q_UNUSED(result);
194 Q_UNUSED(result);
177
195
196+ if (m_returnNullPreview) {
197+ return nullptr;
198+ } else {
178− // This probably leaks, do we don't care
199+ // This probably leaks, do we don't care
179− // it's a test after all
200+ // it's a test after all
180− return new PreviewStack;
201+ return new PreviewStack;
202+ }
181}
203}
182
204
183void Scope::cancelActivation()
205void Scope::cancelActivation()
tests/mocks/Unity/fake_scope.h+5−1
@@ -32,7 +32,7 @@ class Scope : public unity::shell::scopes::ScopeInterface
32
32
33public:
33public:
34 Scope(Scopes* parent = 0);
34 Scope(Scopes* parent = 0);
35− Scope(QString const& id, QString const& name, bool favorite, Scopes* parent = 0, int categories = 20);
35+ Scope(QString const& id, QString const& name, bool favorite, Scopes* parent = 0, int categories = 20, bool returnNullPreview = false);
36
36
37 /* getters */
37 /* getters */
38 QString id() const override;
38 QString id() const override;
@@ -56,6 +56,8 @@ public:
56 void setFormFactor(const QString& form_factor) override;
56 void setFormFactor(const QString& form_factor) override;
57 void setActive(const bool) override;
57 void setActive(const bool) override;
58 void setFavorite(const bool) override;
58 void setFavorite(const bool) override;
59+ Q_INVOKABLE void setId(const QString &id); // This is not invokable in the Interface, here for testing benefits
60+ Q_INVOKABLE void setName(const QString &name); // This is not invokable in the Interface, here for testing benefits
59 Q_INVOKABLE void setSearchInProgress(const bool inProg); // This is not invokable in the Interface, here for testing benefits
61 Q_INVOKABLE void setSearchInProgress(const bool inProg); // This is not invokable in the Interface, here for testing benefits
60
62
61 Q_INVOKABLE void activate(QVariant const& result) override;
63 Q_INVOKABLE void activate(QVariant const& result) override;
@@ -97,6 +99,8 @@ protected:
97 unity::shell::scopes::CategoriesInterface* m_categories;
99 unity::shell::scopes::CategoriesInterface* m_categories;
98 unity::shell::scopes::ScopeInterface* m_openScope;
100 unity::shell::scopes::ScopeInterface* m_openScope;
99 unity::shell::scopes::SettingsModelInterface* m_settings;
101 unity::shell::scopes::SettingsModelInterface* m_settings;
102+
103+ bool m_returnNullPreview;
100};
104};
101
105
102#endif // FAKE_SCOPE_H
106#endif // FAKE_SCOPE_H
tests/mocks/Unity/fake_scopes.cpp+1
@@ -59,6 +59,7 @@ void Scopes::updateScopes()
59 addScope(new Scope("MockScope7", "MS7", false, this));
59 addScope(new Scope("MockScope7", "MS7", false, this));
60 addScope(new Scope("MockScope8", "MS8", false, this));
60 addScope(new Scope("MockScope8", "MS8", false, this));
61 addScope(new Scope("MockScope9", "MS9", false, this));
61 addScope(new Scope("MockScope9", "MS9", false, this));
62+ addScope(new Scope("NullPreviewScope", "NPS", false, this, 1, true));
62 m_scopesOverview = new ScopesOverview(this);
63 m_scopesOverview = new ScopesOverview(this);
63
64
64 if (!m_loaded) {
65 if (!m_loaded) {
tests/plugins/Dash/cardcreator/1.res+2−2
@@ -40,8 +40,8 @@ Item {
40 width = root.fixedArtShapeSize.width;
40 width = root.fixedArtShapeSize.width;
41 height = root.fixedArtShapeSize.height;
41 height = root.fixedArtShapeSize.height;
42 } else {
42 } else {
43− width = Qt.binding(function() { return !visible ? 0 : image.width });
43+ width = Qt.binding(function() { return image.status !== Image.Ready ? 0 : image.width });
44− height = Qt.binding(function() { return !visible ? 0 : image.height });
44+ height = Qt.binding(function() { return image.status !== Image.Ready ? 0 : image.height });
45 }
45 }
46 }
46 }
47 image: Image {
47 image: Image {
tests/plugins/Dash/cardcreator/3.res+3−3
@@ -40,9 +40,9 @@ Item {
40 width = root.fixedArtShapeSize.width;
40 width = root.fixedArtShapeSize.width;
41 height = root.fixedArtShapeSize.height;
41 height = root.fixedArtShapeSize.height;
42 } else {
42 } else {
43− width = Qt.binding(function() { return !visible ? 0 : image.width });
43+ width = Qt.binding(function() { return image.status !== Image.Ready ? 0 : image.width });
44− height = Qt.binding(function() { return !visible ? 0 : image.height });
44+ height = Qt.binding(function() { return image.status !== Image.Ready ? 0 : image.height });
45− }
45+ }
46 }
46 }
47 image: Image {
47 image: Image {
48 objectName: "artImage";
48 objectName: "artImage";
tests/plugins/Dash/cardcreator/5.res+3−3
@@ -40,9 +40,9 @@ Item {
40 width = root.fixedArtShapeSize.width;
40 width = root.fixedArtShapeSize.width;
41 height = root.fixedArtShapeSize.height;
41 height = root.fixedArtShapeSize.height;
42 } else {
42 } else {
43− width = Qt.binding(function() { return !visible ? 0 : image.width });
43+ width = Qt.binding(function() { return image.status !== Image.Ready ? 0 : image.width });
44− height = Qt.binding(function() { return !visible ? 0 : image.height });
44+ height = Qt.binding(function() { return image.status !== Image.Ready ? 0 : image.height });
45− }
45+ }
46 }
46 }
47 image: Image {
47 image: Image {
48 objectName: "artImage";
48 objectName: "artImage";
tests/plugins/Unity/Launcher/CMakeLists.txt+9−24
@@ -1,12 +1,11 @@
1pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
1pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
2−pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=3)
2+pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=4)
3pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=3)
3pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=3)
4
4
5include_directories(
5include_directories(
6 ${CMAKE_CURRENT_BINARY_DIR}
6 ${CMAKE_CURRENT_BINARY_DIR}
7 ${CMAKE_SOURCE_DIR}/plugins/AccountsService
7 ${CMAKE_SOURCE_DIR}/plugins/AccountsService
8 ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher
8 ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher
9− ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/backend
10 ${GSETTINGS_QT_INCLUDE_DIRS}
9 ${GSETTINGS_QT_INCLUDE_DIRS}
11 )
10 )
12
11
@@ -14,35 +13,21 @@ add_definitions(-DSM_BUSNAME=sessionBus)
14add_definitions(-DSRCDIR="${CMAKE_CURRENT_SOURCE_DIR}")
13add_definitions(-DSRCDIR="${CMAKE_CURRENT_SOURCE_DIR}")
15add_definitions(-DLAUNCHER_TESTING)
14add_definitions(-DLAUNCHER_TESTING)
16
15
17−### LauncherBackendTest
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)
21−add_test(NAME launcherbackendtest COMMAND ${testBackendCommand})
22−add_custom_target(launcherbackendtest ${testBackendCommand})
23−add_executable(launcherbackendtestExec
24− launcherbackendtest.cpp
25− ${CMAKE_SOURCE_DIR}/plugins/AccountsService/AccountsServiceDBusAdaptor.cpp
26− ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/backend/launcherbackend.cpp
27− ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/common/quicklistentry.cpp
28− )
29−target_link_libraries(launcherbackendtestExec ${GSETTINGS_QT_LDFLAGS})
30−qt5_use_modules(launcherbackendtestExec Test Core DBus)
31−
32### LauncherModelTest
16### LauncherModelTest
33−set(testModelCommand ${CMAKE_CURRENT_BINARY_DIR}/launchermodeltestExec
17+set(testModelCommand dbus-test-runner --task ${CMAKE_CURRENT_BINARY_DIR}/launchermodeltestExec
34− -o ${CMAKE_BINARY_DIR}/launchermodeltest.xml,xunitxml
18+ --parameter -o --parameter ${CMAKE_BINARY_DIR}/launchermodeltest.xml,xunitxml
35− -o -,txt)
19+ --parameter -o --parameter -,txt)
36add_test(NAME launchermodeltest COMMAND ${testModelCommand})
20add_test(NAME launchermodeltest COMMAND ${testModelCommand})
37add_custom_target(launchermodeltest ${testModelCommand})
21add_custom_target(launchermodeltest ${testModelCommand})
38add_executable(launchermodeltestExec
22add_executable(launchermodeltestExec
39 launchermodeltest.cpp
23 launchermodeltest.cpp
24+ gsettings.cpp
40 ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/launchermodel.cpp
25 ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/launchermodel.cpp
41 ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/launcheritem.cpp
26 ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/launcheritem.cpp
42 ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/quicklistmodel.cpp
27 ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/quicklistmodel.cpp
43− ${CMAKE_SOURCE_DIR}/plugins/AccountsService/AccountsServiceDBusAdaptor.cpp
28+ ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/dbusinterface.cpp
44− ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/backend/launcherbackend.cpp
29+ ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/desktopfilehandler.cpp
45− ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/common/quicklistentry.cpp
30+ ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/quicklistentry.cpp
46 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherItemInterface.h
31 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherItemInterface.h
47 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherModelInterface.h
32 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/LauncherModelInterface.h
48 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/QuickListModelInterface.h
33 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/QuickListModelInterface.h
@@ -50,7 +35,7 @@ add_executable(launchermodeltestExec
50 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
35 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
51 )
36 )
52target_link_libraries(launchermodeltestExec ${GSETTINGS_QT_LDFLAGS})
37target_link_libraries(launchermodeltestExec ${GSETTINGS_QT_LDFLAGS})
53−qt5_use_modules(launchermodeltestExec Test Core DBus Gui)
38+qt5_use_modules(launchermodeltestExec Test Core DBus Xml Gui)
54
39
55# copy .desktop files into build directory for shadow builds
40# copy .desktop files into build directory for shadow builds
56file(GLOB DESKTOP_FILES *.desktop)
41file(GLOB DESKTOP_FILES *.desktop)
tests/plugins/Unity/Launcher/gsettings.cppnew file+40
@@ -0,0 +1,40 @@
1+/*
2+ * Copyright 2014 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Authors:
17+ * Michael Zanetti <michael.zanetti@canonical.com>
18+ */
19+
20+#include "gsettings.h"
21+
22+#include <QDebug>
23+
24+// This is a mock implementation to not touch GSettings for real during tests
25+
26+GSettings::GSettings(QObject *parent):
27+ QObject(parent)
28+{
29+
30+}
31+
32+QStringList GSettings::storedApplications() const
33+{
34+ return QStringList();
35+}
36+
37+void GSettings::setStoredApplications(const QStringList &storedApplications)
38+{
39+ Q_UNUSED(storedApplications)
40+}
tests/plugins/Unity/Launcher/gsettings.hnew file+38
@@ -0,0 +1,38 @@
1+/*
2+ * Copyright 2014 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Authors:
17+ * Michael Zanetti <michael.zanetti@canonical.com>
18+ */
19+
20+#ifndef GSETTINGS_H
21+#define GSETTINGS_H
22+
23+#include <QObject>
24+#include <QStringList>
25+
26+// This is a mock implementation to not touch GSettings for real during tests
27+
28+class GSettings: public QObject
29+{
30+ Q_OBJECT
31+public:
32+ GSettings(QObject *parent = nullptr);
33+
34+ QStringList storedApplications() const;
35+ void setStoredApplications(const QStringList &storedApplications);
36+};
37+
38+#endif
tests/plugins/Unity/Launcher/launcherbackendtest.cppdeleted−239
@@ -1,239 +0,0 @@
1−/*
2− * Copyright 2013 Canonical Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU Lesser General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU Lesser General Public License for more details.
12− *
13− * You should have received a copy of the GNU Lesser General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− *
16− * Authors:
17− * Michael Terry <michael.terry@canonical.com>
18− */
19−
20−#include "launcherbackend.h"
21−
22−#include <QtTest>
23−#include <QDBusConnection>
24−#include <QDBusMessage>
25−#include <QDBusVariant>
26−
27−
28−class LauncherBackendTest : public QObject
29−{
30− Q_OBJECT
31−
32−private Q_SLOTS:
33−
34− void initTestCase()
35− {
36− // As we want to test absolute file paths in the .desktop file, we need to modify the
37− // sample file and replace the path in there with the current build dir.
38− QSettings settings(QDir::currentPath() + "/click-icon.desktop", QSettings::IniFormat);
39− settings.setValue("Desktop Entry/Path", QDir::currentPath());
40− }
41−
42− void testFileNames()
43− {
44− LauncherBackend backend;
45−
46− backend.setStoredApplications(QStringList() << "rel-icon" << "abs-icon" << "invalid");
47− QCOMPARE(backend.storedApplications(), QStringList() << "rel-icon" << "abs-icon");
48− }
49−
50− void testIcon_data() {
51− QTest::addColumn<QString>("appId");
52− QTest::addColumn<QString>("expectedIcon");
53−
54− // Needs to expand a relative icon to the absolute path
55− QTest::newRow("relative icon path") << "rel-icon" << QDir::currentPath() + "/rel-icon.svg";
56−
57− // In case an icon is not found on disk, it needs to fallback on image://theme/ for it
58− QTest::newRow("fallback on theme") << "abs-icon" << "image://theme//path/to/icon.png";
59−
60− // Click packages have a relative icon path but an absolute path as a separate entry
61− QTest::newRow("click package icon") << "click-icon" << QDir::currentPath() + "/click-icon.svg";
62− }
63−
64− void testIcon() {
65− QFETCH(QString, appId);
66− QFETCH(QString, expectedIcon);
67−
68− LauncherBackend backend;
69− backend.setStoredApplications(QStringList() << appId);
70−
71− QCOMPARE(backend.icon(appId), expectedIcon);
72− }
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− }
236−};
237−
238−QTEST_GUILESS_MAIN(LauncherBackendTest)
239−#include "launcherbackendtest.moc"
tests/plugins/Unity/Launcher/launchermodeltest.cpp+81−1
@@ -23,8 +23,12 @@
23
23
24#include "launcheritem.h"
24#include "launcheritem.h"
25#include "launchermodel.h"
25#include "launchermodel.h"
26+#include "dbusinterface.h"
26
27
27#include <QtTest>
28#include <QtTest>
29+#include <QDBusInterface>
30+#include <QDBusReply>
31+#include <QDomDocument>
28
32
29// This is a mock, specifically to test the LauncherModel
33// This is a mock, specifically to test the LauncherModel
30class MockApp: public unity::shell::application::ApplicationInfoInterface
34class MockApp: public unity::shell::application::ApplicationInfoInterface
@@ -287,10 +291,86 @@ private Q_SLOTS:
287 QCOMPARE(launcherModel->getUrlForAppId(QString()), QString());
291 QCOMPARE(launcherModel->getUrlForAppId(QString()), QString());
288 QCOMPARE(launcherModel->getUrlForAppId(""), QString());
292 QCOMPARE(launcherModel->getUrlForAppId(""), QString());
289 QCOMPARE(launcherModel->getUrlForAppId("no-name"), QString("application:///no-name.desktop"));
293 QCOMPARE(launcherModel->getUrlForAppId("no-name"), QString("application:///no-name.desktop"));
290− QCOMPARE(launcherModel->getUrlForAppId("com.test.good"), QString("appid://com.test.good/first-listed-app/current-user-version"));
294+ QCOMPARE(launcherModel->getUrlForAppId("com.test.good"), QString("application:///com.test.good.desktop"));
291 QCOMPARE(launcherModel->getUrlForAppId("com.test.good_application"), QString("appid://com.test.good/application/current-user-version"));
295 QCOMPARE(launcherModel->getUrlForAppId("com.test.good_application"), QString("appid://com.test.good/application/current-user-version"));
292 QCOMPARE(launcherModel->getUrlForAppId("com.test.good_application_1.2.3"), QString("appid://com.test.good/application/current-user-version"));
296 QCOMPARE(launcherModel->getUrlForAppId("com.test.good_application_1.2.3"), QString("appid://com.test.good/application/current-user-version"));
293 }
297 }
298+
299+ void testIntrospection() {
300+ QDBusInterface interface("com.canonical.Unity.Launcher", "/com/canonical/Unity/Launcher", "org.freedesktop.DBus.Introspectable");
301+ QDBusReply<QString> reply = interface.call("Introspect");
302+ QStringList nodes = extractNodes(reply.value());
303+ QCOMPARE(nodes.count(), launcherModel->rowCount());
304+
305+ appManager->addApplication(new MockApp("foobar"));
306+ reply = interface.call("Introspect");
307+ nodes = extractNodes(reply.value());
308+ QCOMPARE(nodes.contains("foobar"), true);
309+
310+ appManager->removeApplication(2);
311+ reply = interface.call("Introspect");
312+ nodes = extractNodes(reply.value());
313+ QCOMPARE(nodes.contains("foobar"), false);
314+ }
315+
316+ QStringList extractNodes(const QString &introspectionXml) {
317+ QXmlStreamReader introspectReply(introspectionXml);
318+
319+ QStringList ret;
320+ while (!introspectReply.atEnd() && !introspectReply.hasError()) {
321+ QXmlStreamReader::TokenType token = introspectReply.readNext();
322+
323+ if (token == QXmlStreamReader::StartElement) {
324+ if (introspectReply.name() == "node" && introspectReply.attributes().count() > 0) {
325+ ret << introspectReply.attributes().value("name").toString();
326+ }
327+ }
328+ }
329+ return ret;
330+ }
331+
332+ void testCountEmblems() {
333+ // Call GetAll on abs-icon
334+ QDBusInterface interface("com.canonical.Unity.Launcher", "/com/canonical/Unity/Launcher/abs_2Dicon", "org.freedesktop.DBus.Properties");
335+ QDBusReply<QVariantMap> reply = interface.call("GetAll");
336+ QVariantMap map = reply.value();
337+
338+ // Make sure GetAll returns a map with count and countVisible props
339+ QCOMPARE(map.contains("count"), true);
340+ QCOMPARE(map.contains("countVisible"), true);
341+
342+ // Make sure count is intitilized to 0 and non-visible
343+ QCOMPARE(map.value("count").toInt(), 0);
344+ QCOMPARE(map.value("countVisible").toBool(), false);
345+
346+ // Now make it visible and set it to 55 through D-Bus
347+ interface.call("Set", "com.canonical.Unity.Launcher.Item", "count", 55);
348+ interface.call("Set", "com.canonical.Unity.Launcher.Item", "countVisible", true);
349+
350+ // Fetch it again using GetAll
351+ reply = interface.call("GetAll");
352+ map = reply.value();
353+
354+ // Make sure values have changed on the D-Bus interface
355+ QCOMPARE(map.value("count").toInt(), 55);
356+ QCOMPARE(map.value("countVisible").toBool(), true);
357+
358+ // Now the item on the upper side of the API
359+ int index = launcherModel->findApplication("abs-icon");
360+ QCOMPARE(index >= 0, true);
361+
362+ // And make sure values have changed there as well
363+ QCOMPARE(launcherModel->get(index)->countVisible(), true);
364+ QCOMPARE(launcherModel->get(index)->count(), 55);
365+ }
366+
367+ void testRefresh() {
368+ QDBusInterface interface("com.canonical.Unity.Launcher", "/com/canonical/Unity/Launcher", "com.canonical.Unity.Launcher");
369+ QDBusReply<void> reply = interface.call("Refresh");
370+
371+ // Make sure the call to Refresh returned without error.
372+ QCOMPARE(reply.isValid(), true);
373+ }
294};
374};
295
375
296QTEST_GUILESS_MAIN(LauncherModelTest)
376QTEST_GUILESS_MAIN(LauncherModelTest)
tests/plugins/Utils/CMakeLists.txt−1
@@ -11,7 +11,6 @@ macro(run_tests)
11 -o ${CMAKE_BINARY_DIR}/${_test}.xml,xunitxml
11 -o ${CMAKE_BINARY_DIR}/${_test}.xml,xunitxml
12 -o -,txt)
12 -o -,txt)
13 add_test(NAME ${_test} COMMAND ${testCommand})
13 add_test(NAME ${_test} COMMAND ${testCommand})
14− set_tests_properties(${_test} PROPERTIES ENVIRONMENT "LC_ALL=C.UTF-8")
15 add_custom_target(${_test} env "LC_ALL=C.UTF-8" ${testCommand})
14 add_custom_target(${_test} env "LC_ALL=C.UTF-8" ${testCommand})
16 add_executable(${_test}Exec ${_test}.cpp modeltest.cpp)
15 add_executable(${_test}Exec ${_test}.cpp modeltest.cpp)
17 qt5_use_modules(${_test}Exec Test Core Qml)
16 qt5_use_modules(${_test}Exec Test Core Qml)
tests/qmltests/Dash/Previews/tst_PreviewPayments.qml+39
@@ -33,6 +33,10 @@ Rectangle {
33 "source": { "price" : 0.99, "currency": "USD", "store_item_id": "com.example.invalid" }
33 "source": { "price" : 0.99, "currency": "USD", "store_item_id": "com.example.invalid" }
34 }
34 }
35
35
36+ property var jsonPurchaseCancel: {
37+ "source": { "price" : 0.99, "currency": "USD", "store_item_id": "com.example.cancel" }
38+ }
39+
36 SignalSpy {
40 SignalSpy {
37 id: spy
41 id: spy
38 target: previewPayments
42 target: previewPayments
@@ -160,5 +164,40 @@ Rectangle {
160 compare(args[1], "purchaseError");
164 compare(args[1], "purchaseError");
161 compare(args[2], jsonPurchaseError["source"]);
165 compare(args[2], jsonPurchaseError["source"]);
162 }
166 }
167+
168+ function test_purchase_cancelled() {
169+ // The mock Payments triggers cancellation when com.example.cancel
170+ // is passed to it as store_item_id. Exercise it here
171+ previewPayments.widgetData = jsonPurchaseCancel;
172+
173+ var button = findChild(previewPayments, "paymentButton");
174+ var progress = findChild(previewPayments, "loadingBar");
175+ verify(button, "Button not found.");
176+ verify(progress, "Progress not found.");
177+
178+ mouseClick(button, button.width / 2, button.height / 2);
179+
180+ tryCompare(progress, "visible", true);
181+ tryCompare(progress, "opacity", 1);
182+ tryCompare(button, "visible", false);
183+ tryCompare(button, "opacity", 0);
184+
185+ paymentClient.process();
186+
187+ // Signal is not used at the moment, to avoid preview refresh.
188+ /*
189+ *spy.wait();
190+ *
191+ * var args = spy.signalArguments[0];
192+ * compare(args[0], "previewPayments");
193+ * compare(args[1], "purchaseCancelled");
194+ * compare(args[2], jsonPurchaseCancel["source"]);
195+ */
196+
197+ tryCompare(progress, "visible", false);
198+ tryCompare(progress, "opacity", 0);
199+ tryCompare(button, "visible", true);
200+ tryCompare(button, "opacity", 1);
201+ }
163 }
202 }
164}
203}
tests/qmltests/Dash/tst_Card.qml+3−3
@@ -29,9 +29,9 @@ Rectangle {
29
29
30 property string cardData: '
30 property string cardData: '
31 {
31 {
32− "art": "../../../tests/qmltests/Dash/artwork/music-player-design.png",
32+ "art": "'+Qt.resolvedUrl("artwork/music-player-design.png").toString()+'",
33− "mascot": "../../../tests/qmltests/Dash/artwork/avatar.png",
33+ "mascot": "'+Qt.resolvedUrl("artwork/avatar.png").toString()+'",
34− "emblem": "../../../tests/qmltests/Dash/artwork/emblem.png",
34+ "emblem": "'+Qt.resolvedUrl("artwork/emblem.png").toString()+'",
35 "title": "foo",
35 "title": "foo",
36 "subtitle": "bar",
36 "subtitle": "bar",
37 "summary": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
37 "summary": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
tests/qmltests/Dash/tst_GenericScopeView.qml+129−1
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright 2013 Canonical Ltd.
2+ * Copyright 2014 Canonical Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -43,6 +43,10 @@ Item {
43 onLoadedChanged: if (loaded) genericScopeView.scope = scopes.getScope(2);
43 onLoadedChanged: if (loaded) genericScopeView.scope = scopes.getScope(2);
44 }
44 }
45
45
46+ MockScope {
47+ id: mockScope
48+ }
49+
46 SignalSpy {
50 SignalSpy {
47 id: spy
51 id: spy
48 }
52 }
@@ -92,6 +96,17 @@ Item {
92 return findChild(genericScopeView, category);
96 return findChild(genericScopeView, category);
93 }
97 }
94
98
99+ function scrollToEnd()
100+ {
101+ var categoryListView = findChild(genericScopeView, "categoryListView");
102+ waitForRendering(categoryListView);
103+ while (!categoryListView.atYEnd) {
104+ mouseFlick(genericScopeView, genericScopeView.width/2, genericScopeView.height - units.gu(8),
105+ genericScopeView.width/2, genericScopeView.y)
106+ tryCompare(categoryListView, "moving", false);
107+ }
108+ }
109+
95 function test_isActive() {
110 function test_isActive() {
96 tryCompare(genericScopeView.scope, "isActive", false)
111 tryCompare(genericScopeView.scope, "isActive", false)
97 genericScopeView.isCurrent = true
112 genericScopeView.isCurrent = true
@@ -280,6 +295,7 @@ Item {
280 mouseClick(tile, tile.width / 2, tile.height / 2);
295 mouseClick(tile, tile.width / 2, tile.height / 2);
281 tryCompare(testCase.subPageLoader, "open", true);
296 tryCompare(testCase.subPageLoader, "open", true);
282 tryCompare(testCase.subPageLoader, "x", 0);
297 tryCompare(testCase.subPageLoader, "x", 0);
298+ tryCompare(findChild(genericScopeView, "categoryListView"), "visible", false);
283 }
299 }
284
300
285 function closePreview() {
301 function closePreview() {
@@ -288,6 +304,9 @@ Item {
288
304
289 tryCompare(testCase.subPageLoader, "open", false);
305 tryCompare(testCase.subPageLoader, "open", false);
290 tryCompare(testCase.subPageLoader, "visible", false);
306 tryCompare(testCase.subPageLoader, "visible", false);
307+ var categoryListView = findChild(genericScopeView, "categoryListView");
308+ tryCompare(categoryListView, "visible", true);
309+ tryCompare(categoryListView, "x", 0);
291 }
310 }
292
311
293 function test_previewOpenClose() {
312 function test_previewOpenClose() {
@@ -301,6 +320,34 @@ Item {
301 closePreview();
320 closePreview();
302 }
321 }
303
322
323+ function test_tryOpenNullPreview() {
324+ genericScopeView.scope = scopes.getScope("NullPreviewScope");
325+
326+ tryCompareFunction(function() {
327+ var cardGrid = findChild(genericScopeView, 0);
328+ if (cardGrid != null) {
329+ var tile = findChild(cardGrid, 0);
330+ return tile != null;
331+ }
332+ return false;
333+ },
334+ true);
335+ var tile = findChild(findChild(genericScopeView, 0), 0);
336+
337+ tryCompare(testCase.subPageLoader, "open", false);
338+ tryCompare(testCase.subPageLoader, "visible", false);
339+
340+ mouseClick(tile, tile.width / 2, tile.height / 2);
341+
342+ tryCompare(testCase.subPageLoader, "open", false);
343+ tryCompare(testCase.subPageLoader, "visible", false);
344+
345+ mousePress(tile, tile.width / 2, tile.height / 2);
346+ tryCompare(testCase.subPageLoader, "open", false);
347+ tryCompare(testCase.subPageLoader, "visible", false);
348+ mouseRelease(tile, tile.width / 2, tile.height / 2);
349+ }
350+
304 function test_showPreviewCarousel() {
351 function test_showPreviewCarousel() {
305 var category = scrollToCategory("dashCategory1");
352 var category = scrollToCategory("dashCategory1");
306
353
@@ -426,6 +473,87 @@ Item {
426 compare(image.source, data.logo, "Title image has the wrong source");
473 compare(image.source, data.logo, "Title image has the wrong source");
427 }
474 }
428
475
476+ function test_seeAllTwoCategoriesScenario1() {
477+ mockScope.setId("mockScope");
478+ mockScope.setName("Mock Scope");
479+ mockScope.categories.setCount(2);
480+ mockScope.categories.resultModel(0).setResultCount(50);
481+ mockScope.categories.resultModel(1).setResultCount(15);
482+ mockScope.categories.setLayout(0, "grid");
483+ mockScope.categories.setLayout(1, "grid");
484+ mockScope.categories.setHeaderLink(0, "");
485+ mockScope.categories.setHeaderLink(1, "");
486+ genericScopeView.scope = mockScope;
487+ waitForRendering(genericScopeView.categoryView);
488+
489+ var category0 = findChild(genericScopeView, "dashCategory0")
490+ var seeAll0 = findChild(category0, "seeAll")
491+
492+ waitForRendering(seeAll0);
493+ verify(category0.expandable);
494+ verify(!category0.expanded);
495+
496+ mouseClick(seeAll0, seeAll0.width / 2, seeAll0.height / 2);
497+ verify(category0.expanded);
498+ tryCompare(category0, "height", category0.item.expandedHeight + seeAll0.height);
499+ tryCompare(genericScopeView.categoryView, "contentY", units.gu(13.5));
500+
501+ scrollToEnd();
502+
503+ tryCompareFunction(function() { return findChild(genericScopeView, "dashCategory1") !== null; }, true);
504+ var category1 = findChild(genericScopeView, "dashCategory1")
505+ var seeAll1 = findChild(category1, "seeAll")
506+ verify(category1.expandable);
507+ verify(!category1.expanded);
508+
509+ mouseClick(seeAll1, seeAll1.width / 2, seeAll1.height / 2);
510+ verify(!category0.expanded);
511+ verify(category1.expanded);
512+ tryCompare(category1, "height", category1.item.expandedHeight + seeAll1.height);
513+ tryCompareFunction(function() {
514+ return genericScopeView.categoryView.contentY + category1.y + category1.height
515+ == genericScopeView.categoryView.contentHeight;}
516+ , true);
517+ }
518+
519+ function test_seeAllTwoCategoriesScenario2() {
520+ mockScope.setId("mockScope");
521+ mockScope.setName("Mock Scope");
522+ mockScope.categories.setCount(2);
523+ mockScope.categories.resultModel(0).setResultCount(15);
524+ mockScope.categories.resultModel(1).setResultCount(50);
525+ mockScope.categories.setLayout(0, "grid");
526+ mockScope.categories.setLayout(1, "grid");
527+ mockScope.categories.setHeaderLink(0, "");
528+ mockScope.categories.setHeaderLink(1, "");
529+ genericScopeView.scope = mockScope;
530+ waitForRendering(genericScopeView.categoryView);
531+
532+ var category0 = findChild(genericScopeView, "dashCategory0")
533+ var seeAll0 = findChild(category0, "seeAll")
534+
535+ waitForRendering(seeAll0);
536+ verify(category0.expandable);
537+ verify(!category0.expanded);
538+
539+ mouseClick(seeAll0, seeAll0.width / 2, seeAll0.height / 2);
540+ verify(category0.expanded);
541+ tryCompare(category0, "height", category0.item.expandedHeight + seeAll0.height);
542+
543+ scrollToEnd();
544+
545+ var category1 = findChild(genericScopeView, "dashCategory1")
546+ var seeAll1 = findChild(category1, "seeAll")
547+ verify(category1.expandable);
548+ verify(!category1.expanded);
549+
550+ mouseClick(seeAll1, seeAll1.width / 2, seeAll1.height / 2);
551+ verify(!category0.expanded);
552+ verify(category1.expanded);
553+ tryCompare(category1, "height", category1.item.expandedHeight + seeAll1.height);
554+ tryCompare(category1, "y", units.gu(5));
555+ }
556+
429 function test_favorite_data() {
557 function test_favorite_data() {
430 return [
558 return [
431 { tag: "People", id: "MockScope1", favorite: true },
559 { tag: "People", id: "MockScope1", favorite: true },
tests/qmltests/Launcher/tst_Launcher.qml+3−1
@@ -194,7 +194,9 @@ Item {
194 var launcherListView = findChild(launcher, "launcherListView");
194 var launcherListView = findChild(launcher, "launcherListView");
195 for (var i = 0; i < launcherListView.count; ++i) {
195 for (var i = 0; i < launcherListView.count; ++i) {
196 var delegate = findChild(launcherListView, "launcherDelegate" + i)
196 var delegate = findChild(launcherListView, "launcherDelegate" + i)
197− compare(findChild(delegate, "countEmblem").visible, LauncherModel.get(i).count > 0)
197+ compare(findChild(delegate, "countEmblem").visible, LauncherModel.get(i).countVisible)
198+ // Intentionally allow type coercion (string/number)
199+ compare(findChild(delegate, "countLabel").text == LauncherModel.get(i).count, true)
198 }
200 }
199 }
201 }
200
202
tests/qmltests/Notifications/tst_Notifications.qml+1−1
@@ -476,7 +476,7 @@ Row {
476 compare(buttonRow.visible, data.buttonRowVisible, "button visibility is incorrect")
476 compare(buttonRow.visible, data.buttonRowVisible, "button visibility is incorrect")
477
477
478 var audioItem = findInvisibleChild(notification, "sound")
478 var audioItem = findInvisibleChild(notification, "sound")
479− tryCompare(audioItem, "playbackState", data.hasSound ? Audio.PlayingState : Audio.StoppedState)
479+ compare(audioItem.playbackState, data.hasSound ? Audio.PlayingState : Audio.StoppedState, "Audio has wrong state")
480
480
481 if(data.buttonRowVisible) {
481 if(data.buttonRowVisible) {
482 var buttonCancel = findChild(buttonRow, "notify_button1")
482 var buttonCancel = findChild(buttonRow, "notify_button1")
tests/qmltests/Stages/RecursingChildSessionControl.qml+1−1
@@ -93,7 +93,7 @@ ColumnLayout {
93 onCheckedChanged: {
93 onCheckedChanged: {
94 if (checked) {
94 if (checked) {
95 root.session.createSurface();
95 root.session.createSurface();
96− } else if (root.session.surface) {
96+ } else if (root.session && root.session.surface) {
97 ApplicationTest.removeSurface(root.session.surface);
97 ApplicationTest.removeSurface(root.session.surface);
98 }
98 }
99 }
99 }
tests/qmltests/Stages/tst_ApplicationWindow.qml+30−12
@@ -50,22 +50,18 @@ Rectangle {
50 ApplicationWindow {
50 ApplicationWindow {
51 anchors.fill: parent
51 anchors.fill: parent
52 application: fakeApplication
52 application: fakeApplication
53+ orientation: Qt.PortraitOrientation
53 }
54 }
54 }
55 }
55− FocusScope {
56+ Loader {
56− id: appFocus
57+ id: applicationWindowLoader
57 anchors {
58 anchors {
58 top: parent.top
59 top: parent.top
59 bottom: parent.bottom
60 bottom: parent.bottom
60 left: parent.left
61 left: parent.left
61 }
62 }
62 width: units.gu(40)
63 width: units.gu(40)
63−
64− Loader {
65− id: applicationWindowLoader
66− anchors.fill: parent
67− sourceComponent: applicationWindowComponent
64+ sourceComponent: applicationWindowComponent
68− }
69 }
65 }
70
66
71 Rectangle {
67 Rectangle {
@@ -73,7 +69,7 @@ Rectangle {
73 anchors {
69 anchors {
74 top: parent.top
70 top: parent.top
75 bottom: parent.bottom
71 bottom: parent.bottom
76− left: appFocus.right
72+ left: applicationWindowLoader.right
77 right: parent.right
73 right: parent.right
78 }
74 }
79
75
@@ -147,6 +143,24 @@ Rectangle {
147 }
143 }
148 }
144 }
149
145
146+ Button {
147+ anchors { left: parent.left; right: parent.right }
148+ text: "Rotate device \u27F3"
149+ onClicked: {
150+ var orientation = applicationWindowLoader.item.orientation
151+ if (orientation == Qt.PortraitOrientation) {
152+ orientation = Qt.LandscapeOrientation;
153+ } else if (orientation == Qt.LandscapeOrientation) {
154+ orientation = Qt.InvertedPortraitOrientation;
155+ } else if (orientation == Qt.InvertedPortraitOrientation) {
156+ orientation = Qt.InvertedLandscapeOrientation;
157+ } else {
158+ orientation = Qt.PortraitOrientation;
159+ }
160+ applicationWindowLoader.item.orientation = orientation;
161+ }
162+ }
163+
150 }
164 }
151 }
165 }
152
166
@@ -384,15 +398,19 @@ Rectangle {
384 verify(stateGroup.state === "void");
398 verify(stateGroup.state === "void");
385 }
399 }
386
400
387− // should be forcing active focus when interactive changes,
388− // which will cause a FocusScope higher in the object hierarchy tp be given the focus
389 function test_forceActiveFocusFollowsInterative() {
401 function test_forceActiveFocusFollowsInterative() {
390 fakeApplication.createSession();
402 fakeApplication.createSession();
391 applicationWindowLoader.item.interactive = false;
403 applicationWindowLoader.item.interactive = false;
392 applicationWindowLoader.item.interactive = true;
404 applicationWindowLoader.item.interactive = true;
393 fakeSession.createSurface();
405 fakeSession.createSurface();
394
406
395− compare(appFocus.focus, true);
407+ compare(fakeSession.surface.activeFocus, true);
408+
409+ applicationWindowLoader.item.interactive = false;
410+ compare(fakeSession.surface.activeFocus, false);
411+
412+ applicationWindowLoader.item.interactive = true;
413+ compare(fakeSession.surface.activeFocus, true);
396 }
414 }
397 }
415 }
398}
416}
tests/qmltests/Stages/tst_PhoneStage.qml+58
@@ -31,6 +31,8 @@ Item {
31 anchors { fill: parent; rightMargin: units.gu(30) }
31 anchors { fill: parent; rightMargin: units.gu(30) }
32 dragAreaWidth: units.gu(2)
32 dragAreaWidth: units.gu(2)
33 maximizedAppTopMargin: units.gu(3) + units.dp(2)
33 maximizedAppTopMargin: units.gu(3) + units.dp(2)
34+ interactive: true
35+ orientation: Qt.PortraitOrientation
34 }
36 }
35
37
36 Binding {
38 Binding {
@@ -67,6 +69,21 @@ Item {
67 ApplicationManager.get(appList.selectedAppIndex).setState(ApplicationInfoInterface.Stopped);
69 ApplicationManager.get(appList.selectedAppIndex).setState(ApplicationInfoInterface.Stopped);
68 }
70 }
69 }
71 }
72+ Button {
73+ anchors { left: parent.left; right: parent.right }
74+ text: "Rotate device \u27F3"
75+ onClicked: {
76+ if (phoneStage.orientation == Qt.PortraitOrientation) {
77+ phoneStage.orientation = Qt.LandscapeOrientation;
78+ } else if (phoneStage.orientation == Qt.LandscapeOrientation) {
79+ phoneStage.orientation = Qt.InvertedPortraitOrientation;
80+ } else if (phoneStage.orientation == Qt.InvertedPortraitOrientation) {
81+ phoneStage.orientation = Qt.InvertedLandscapeOrientation;
82+ } else {
83+ phoneStage.orientation = Qt.PortraitOrientation;
84+ }
85+ }
86+ }
70 }
87 }
71 ListView {
88 ListView {
72 id: appList
89 id: appList
@@ -256,6 +273,46 @@ Item {
256 compare(ApplicationManager.focusedApplicationId, selectedApp.appId);
273 compare(ApplicationManager.focusedApplicationId, selectedApp.appId);
257 }
274 }
258
275
276+ function test_orientation_change_sent_to_focused_app() {
277+ phoneStage.orientation = Qt.PortraitOrientation;
278+ addApps(1);
279+
280+ var spreadView = findChild(phoneStage, "spreadView");
281+ var app = findChild(spreadView, "appDelegate0");
282+ tryCompare(app, "orientation", Qt.PortraitOrientation);
283+
284+ phoneStage.orientation = Qt.LandscapeOrientation;
285+ tryCompare(app, "orientation", Qt.LandscapeOrientation);
286+ }
287+
288+ function test_orientation_change_not_sent_to_apps_while_spread_open() {
289+ phoneStage.orientation = Qt.PortraitOrientation;
290+ addApps(1);
291+
292+ var spreadView = findChild(phoneStage, "spreadView");
293+ var app = findChild(spreadView, "appDelegate0");
294+ tryCompare(app, "orientation", Qt.PortraitOrientation);
295+
296+ goToSpread();
297+ phoneStage.orientation = Qt.LandscapeOrientation;
298+ tryCompare(app, "orientation", Qt.PortraitOrientation);
299+ }
300+
301+ function test_orientation_change_not_sent_to_unfocused_app_until_it_focused() {
302+ phoneStage.orientation = Qt.PortraitOrientation;
303+ addApps(1);
304+
305+ var spreadView = findChild(phoneStage, "spreadView");
306+ var app = findChild(spreadView, "appDelegate0");
307+
308+ goToSpread();
309+ phoneStage.orientation = Qt.LandscapeOrientation;
310+ tryCompare(app, "orientation", Qt.PortraitOrientation);
311+
312+ phoneStage.select(app.application.appId);
313+ tryCompare(app, "orientation", Qt.LandscapeOrientation);
314+ }
315+
259 function cleanup() {
316 function cleanup() {
260 while (ApplicationManager.count > 1) {
317 while (ApplicationManager.count > 1) {
261 var oldCount = ApplicationManager.count;
318 var oldCount = ApplicationManager.count;
@@ -263,6 +320,7 @@ Item {
263 ApplicationManager.stopApplication(ApplicationManager.get(closingIndex).appId)
320 ApplicationManager.stopApplication(ApplicationManager.get(closingIndex).appId)
264 tryCompare(ApplicationManager, "count", oldCount - 1)
321 tryCompare(ApplicationManager, "count", oldCount - 1)
265 }
322 }
323+ phoneStage.orientation = Qt.PortraitOrientation;
266 }
324 }
267 }
325 }
268}
326}
tests/qmltests/Stages/tst_SessionContainer.qml+63
@@ -41,6 +41,7 @@ Rectangle {
41 SessionContainer {
41 SessionContainer {
42 id: sessionContainer
42 id: sessionContainer
43 anchors.fill: parent
43 anchors.fill: parent
44+ orientation: Qt.PortraitOrientation
44 }
45 }
45 }
46 }
46
47
@@ -112,6 +113,24 @@ Rectangle {
112 session: sessionContainerLoader.item ? sessionContainerLoader.item.session : null
113 session: sessionContainerLoader.item ? sessionContainerLoader.item.session : null
113 }
114 }
114 }
115 }
116+
117+ Button {
118+ anchors { left: parent.left; right: parent.right }
119+ text: "Rotate device \u27F3"
120+ onClicked: {
121+ var orientation = sessionContainerLoader.item.orientation
122+ if (orientation == Qt.PortraitOrientation) {
123+ orientation = Qt.LandscapeOrientation;
124+ } else if (orientation == Qt.LandscapeOrientation) {
125+ orientation = Qt.InvertedPortraitOrientation;
126+ } else if (orientation == Qt.InvertedPortraitOrientation) {
127+ orientation = Qt.InvertedLandscapeOrientation;
128+ } else {
129+ orientation = Qt.PortraitOrientation;
130+ }
131+ sessionContainerLoader.item.orientation = orientation;
132+ }
133+ }
115 }
134 }
116 }
135 }
117
136
@@ -263,5 +282,49 @@ Rectangle {
263 // wait for animation to end
282 // wait for animation to end
264 tryCompareFunction(function() { return isContainerAnimating(childContainer); }, false);
283 tryCompareFunction(function() { return isContainerAnimating(childContainer); }, false);
265 }
284 }
285+
286+ function test_orientationPropagatedToChildren_data() {
287+ return [ { tag: "count=1", count: 1 },
288+ { tag: "count=4", count: 4 } ];
289+ }
290+
291+ /* Test orientation changes are propagated to all children immediately */
292+ function test_orientationPropagatedToChildren(data) {
293+ sessionCheckbox.checked = true;
294+ var rootSessionContainer = sessionContainerLoader.item;
295+ compare(rootSessionContainer.childSessions.count(), 0);
296+
297+ var i;
298+ var sessions = [];
299+ for (i = 0; i < data.count; i++) {
300+ var session = ApplicationTest.addChildSession(rootSessionContainer.session,
301+ "gallery");
302+ session.createSurface();
303+ rootSessionContainer.session.addChildSession(session);
304+
305+ // Check child SessionContainer has orientation matching the parent
306+ var delegate = findChild(rootSessionContainer, "childDelegate" + i);
307+ var childSessionContainer = findChild(delegate, "sessionContainer");
308+
309+ tryCompare(rootSessionContainer, "orientation", childSessionContainer.orientation);
310+
311+ sessions.push(session);
312+ }
313+
314+ // Change orientation and verify all children updated
315+ rootSessionContainer.orientation = Qt.LandscapeOrientation;
316+
317+ for (i = 0; i < data.count; i++) {
318+ var delegate = findChild(rootSessionContainer, "childDelegate" + i);
319+ var childSessionContainer = findChild(delegate, "sessionContainer");
320+
321+ tryCompare(rootSessionContainer, "orientation", childSessionContainer.orientation);
322+ }
323+
324+ // Clean up
325+ for (i = data.count-1; i >= 0; i--) {
326+ ApplicationTest.removeSession(sessions[i]);
327+ }
328+ }
266 }
329 }
267}
330}
tests/qmltests/tst_Shell.qml+103−28
@@ -21,6 +21,7 @@ import QtQuick 2.0
21import QtTest 1.0
21import QtTest 1.0
22import GSettings 1.0
22import GSettings 1.0
23import LightDM 0.1 as LightDM
23import LightDM 0.1 as LightDM
24+import Ubuntu.Components 1.1
24import Ubuntu.Telephony 0.1 as Telephony
25import Ubuntu.Telephony 0.1 as Telephony
25import Unity.Application 0.1
26import Unity.Application 0.1
26import Unity.Connectivity 0.1
27import Unity.Connectivity 0.1
@@ -29,10 +30,9 @@ import Powerd 0.1
29
30
30import "../../qml"
31import "../../qml"
31
32
32−Item {
33+Row {
33 id: root
34 id: root
34− width: shell.width
35+ spacing: 0
35− height: shell.height
36
36
37 QtObject {
37 QtObject {
38 id: applicationArguments
38 id: applicationArguments
@@ -50,13 +50,52 @@ Item {
50 }
50 }
51 }
51 }
52
52
53− Shell {
53+ Loader {
54− id: shell
54+ id: shellLoader
55+
56+ // Copied from Shell.qml
57+ property bool tablet: false
58+ width: tablet ? units.gu(160)
59+ : applicationArguments.hasGeometry() ? applicationArguments.width()
60+ : units.gu(40)
61+ height: tablet ? units.gu(100)
62+ : applicationArguments.hasGeometry() ? applicationArguments.height()
63+ : units.gu(71)
64+
65+ property bool itemDestroyed: false
66+ sourceComponent: Component {
67+ Shell {
68+ Component.onDestruction: {
69+ shellLoader.itemDestroyed = true;
70+ }
71+ }
72+ }
55 }
73 }
56
74
57− Component {
75+ Rectangle {
58− id: shellComponent
76+ color: "white"
59− Shell {}
77+ width: units.gu(30)
78+ height: shellLoader.height
79+
80+ Column {
81+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
82+ spacing: units.gu(1)
83+ Row {
84+ anchors { left: parent.left; right: parent.right }
85+ Button {
86+ text: "Show Greeter"
87+ onClicked: {
88+ if (shellLoader.status !== Loader.Ready)
89+ return;
90+
91+ var greeter = testCase.findChild(shellLoader.item, "greeter");
92+ if (!greeter.shown) {
93+ greeter.show();
94+ }
95+ }
96+ }
97+ }
98+ }
60 }
99 }
61
100
62 SignalSpy {
101 SignalSpy {
@@ -81,10 +120,13 @@ Item {
81 }
120 }
82
121
83 UT.UnityTestCase {
122 UT.UnityTestCase {
123+ id: testCase
84 name: "Shell"
124 name: "Shell"
85 when: windowShown
125 when: windowShown
86
126
127+ property Item shell: shellLoader.status === Loader.Ready ? shellLoader.item : null
128+
87− function initTestCase() {
129+ function init() {
88 swipeAwayGreeter();
130 swipeAwayGreeter();
89
131
90 sessionSpy.target = findChild(shell, "greeter")
132 sessionSpy.target = findChild(shell, "greeter")
@@ -92,17 +134,30 @@ Item {
92 }
134 }
93
135
94 function cleanup() {
136 function cleanup() {
95− // If a test invoked the greeter, make sure we swipe it away again
137+ shellLoader.itemDestroyed = false;
96− var greeter = findChild(shell, "greeter");
138+
97− if (greeter.shown) {
139+ shellLoader.active = false;
98− swipeAwayGreeter();
140+
99− }
141+ tryCompare(shellLoader, "status", Loader.Null);
142+ tryCompare(shellLoader, "item", null);
143+ // Loader.status might be Loader.Null and Loader.item might be null but the Loader
144+ // item might still be alive. So if we set Loader.active back to true
145+ // again right now we will get the very same Shell instance back. So no reload
146+ // actually took place. Likely because Loader waits until the next event loop
147+ // iteration to do its work. So to ensure the reload, we will wait until the
148+ // Shell instance gets destroyed.
149+ tryCompare(shellLoader, "itemDestroyed", true);
100
150
101 // kill all (fake) running apps
151 // kill all (fake) running apps
102 killApps(ApplicationManager);
152 killApps(ApplicationManager);
103
153
104− waitForUIToSettle();
154+ unlockAllModemsSpy.clear()
105− hideIndicators();
155+
156+ // reload our test subject to get it in a fresh state once again
157+ shellLoader.active = true;
158+
159+ tryCompare(shellLoader, "status", Loader.Ready);
160+ removeTimeConstraintsFromDirectionalDragAreas(shellLoader.item);
106 }
161 }
107
162
108 function killApps() {
163 function killApps() {
@@ -115,10 +170,11 @@ Item {
115
170
116 function test_leftEdgeDrag_data() {
171 function test_leftEdgeDrag_data() {
117 return [
172 return [
118− {tag: "without launcher", revealLauncher: false, swipeLength: units.gu(27), appHides: true},
173+ {tag: "without launcher", revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app", launcherHides: true},
119− {tag: "with launcher", revealLauncher: true, swipeLength: units.gu(27), appHides: true},
174+ {tag: "with launcher", revealLauncher: true, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app", launcherHides: true},
120− {tag: "small swipe", revealLauncher: false, swipeLength: units.gu(25), appHides: false},
175+ {tag: "small swipe", revealLauncher: false, swipeLength: units.gu(25), appHides: false, focusedApp: "dialer-app", launcherHides: false},
176+ {tag: "long swipe", revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "dialer-app", launcherHides: true},
121− {tag: "long swipe", revealLauncher: false, swipeLength: units.gu(27), appHides: true}
177+ {tag: "long swipe", revealLauncher: false, swipeLength: units.gu(27), appHides: true, focusedApp: "unity8-dash", launcherHides: false}
122 ];
178 ];
123 }
179 }
124
180
@@ -126,16 +182,22 @@ Item {
126 dragLauncherIntoView();
182 dragLauncherIntoView();
127 tapOnAppIconInLauncher();
183 tapOnAppIconInLauncher();
128 waitUntilApplicationWindowIsFullyVisible();
184 waitUntilApplicationWindowIsFullyVisible();
185+ ApplicationManager.focusApplication(data.focusedApp)
186+ waitUntilApplicationWindowIsFullyVisible();
129
187
130 if (data.revealLauncher) {
188 if (data.revealLauncher) {
131 dragLauncherIntoView();
189 dragLauncherIntoView();
132 }
190 }
133
191
134 swipeFromLeftEdge(data.swipeLength);
192 swipeFromLeftEdge(data.swipeLength);
135− if (data.appHides)
193+ if (data.appHides) {
136 waitUntilDashIsFocused();
194 waitUntilDashIsFocused();
137− else
195+ } else {
138 waitUntilApplicationWindowIsFullyVisible();
196 waitUntilApplicationWindowIsFullyVisible();
197+ }
198+
199+ var launcher = findChild(shell, "launcherPanel");
200+ tryCompare(launcher, "x", data.launcherHides ? -launcher.width : 0)
139 }
201 }
140
202
141 function test_suspend() {
203 function test_suspend() {
@@ -241,10 +303,27 @@ Item {
241 tryCompare(item, "visible", false);
303 tryCompare(item, "visible", false);
242 }
304 }
243
305
306+ // wait until any transition animation has finished
307+ function waitUntilTransitionsEnd(stateGroup) {
308+ var transitions = stateGroup.transitions;
309+ for (var i = 0; i < transitions.length; ++i) {
310+ var transition = transitions[i];
311+ tryCompare(transition, "running", false, 2000);
312+ }
313+ }
314+
315+ // Wait until the ApplicationWindow for the given Application object is fully loaded
316+ // (ie, the real surface has replaced the splash screen)
317+ function waitUntilAppWindowIsFullyLoaded(app) {
318+ var appWindow = findChild(shell, "appWindow_" + app.appId);
319+ var appWindowStateGroup = findInvisibleChild(appWindow, "applicationWindowStateGroup");
320+ tryCompareFunction(function() { return appWindowStateGroup.state === "surface" }, true);
321+ waitUntilTransitionsEnd(appWindowStateGroup);
322+ }
323+
244 function test_surfaceLosesFocusWhilePanelIsOpen() {
324 function test_surfaceLosesFocusWhilePanelIsOpen() {
245 var app = ApplicationManager.startApplication("dialer-app");
325 var app = ApplicationManager.startApplication("dialer-app");
246− // wait until the app is fully loaded (ie, real surface replaces splash screen)
326+ waitUntilAppWindowIsFullyLoaded(app);
247− tryCompareFunction(function() { return app.session !== null && app.session.surface !== null }, true);
248
327
249 tryCompare(app.session.surface, "focus", true);
328 tryCompare(app.session.surface, "focus", true);
250
329
@@ -465,13 +544,9 @@ Item {
465 }
544 }
466
545
467 function test_unlockAllModemsOnBoot() {
546 function test_unlockAllModemsOnBoot() {
468− unlockAllModemsSpy.clear()
469− // actually create an object so we notice the onCompleted signal
470− var greeter = shellComponent.createObject(root)
471 // TODO reenable when service ready (LP: #1361074)
547 // TODO reenable when service ready (LP: #1361074)
472 expectFail("", "Unlock on boot temporarily disabled");
548 expectFail("", "Unlock on boot temporarily disabled");
473 tryCompare(unlockAllModemsSpy, "count", 1)
549 tryCompare(unlockAllModemsSpy, "count", 1)
474− greeter.destroy()
475 }
550 }
476 }
551 }
477}
552}