on xenial help sign in
Merge from greeter-ux-fixes
aad7babMichael Terry
@@ -41,11 +41,13 @@ install_dependencies() {
41 sudo apt-get install devscripts \
41 sudo apt-get install devscripts \
42 equivs \
42 equivs \
43 gsettings-desktop-schemas \
43 gsettings-desktop-schemas \
44+ gsettings-ubuntu-schemas \
44 qmenumodel-qml \
45 qmenumodel-qml \
45 qtdeclarative5-dee-plugin \
46 qtdeclarative5-dee-plugin \
46 qtdeclarative5-gsettings1.0 \
47 qtdeclarative5-gsettings1.0 \
47 qtdeclarative5-ubuntu-ui-toolkit-plugin \
48 qtdeclarative5-ubuntu-ui-toolkit-plugin \
48 qtdeclarative5-ubuntu-settings-components \
49 qtdeclarative5-ubuntu-settings-components \
50+ qtdeclarative5-ubuntu-thumbnailer0.1 \
49 unity-notifications-impl \
51 unity-notifications-impl \
50 qtdeclarative5-xmllistmodel-plugin \
52 qtdeclarative5-xmllistmodel-plugin \
51 ubuntu-mobile-icons \
53 ubuntu-mobile-icons \
debian/changelog+56−2
@@ -1,8 +1,62 @@
1−unity8 (7.85+14.04.20140306-0ubuntu1) UNRELEASED; urgency=medium
1+unity8 (7.86+14.04.20140306-0ubuntu1) UNRELEASED; urgency=medium
2
2
3 * Bump version for Breaks due to unity8-greeter
3 * Bump version for Breaks due to unity8-greeter
4
4
5− -- Michael Terry <mterry@ubuntu.com> Wed, 12 Mar 2014 14:39:45 -0400
5+ -- Michael Terry <mterry@ubuntu.com> Wed, 02 Apr 2014 14:39:45 -0400
6+
7+unity8 (7.85+14.04.20140401.3-0ubuntu1) trusty; urgency=medium
8+
9+ [ Michał Sawicz ]
10+ * Bump version to ensure incompatibility with previous Unity.Application
11+ implementations.
12+ * We'll only have the unity-mir and mock Ubuntu.Application plugins
13+ now, no need for mangling the import paths.
14+
15+ [ Michal Hruby ]
16+ * Remove the albumart image provider. (LP: #1262711)
17+ * Don't reset search string after 2 seconds. (LP: #1297246)
18+
19+ [ James Henstridge ]
20+ * Remove the albumart image provider. (LP: #1262711)
21+
22+ [ Albert Astals ]
23+ * Carousel: Add test to make sure we only create the needed delegates
24+ and not more
25+ * LVWPH: Remove processEvents() call from updatePolish() It causes
26+ some reentrancy issues and in some times you end up in polishItems()
27+ with items that have been deleted because you called processEvents()
28+ This means i need a small tweak in itemGeometryChanged to not
29+ reposition items if we are inside a setContentHeight call and two
30+ small tweaks to tests since now things happen in a different order
31+ and numbers are different (though equivalent) (LP: #1297240)
32+ * Card.qml binding loops are gone. hooray \o/ Also made the aspect
33+ properties readonly
34+
35+ [ Mirco Müller ]
36+ * A potential fix for "Cannot read property 'state' of null"-failure
37+ on Jenkins with the VisualSnapDecisionsQueue QML-test of
38+ notifications.
39+
40+ [ Michael Terry ]
41+ * Pass user's preference for auto-brightness on to powerd. (LP:
42+ #1273174)
43+
44+ [ Michael Zanetti ]
45+ * Registers a dummy QObject as QTestRootObject in uqmlscene in order
46+ to fix make trySomething with Qt 5.2.
47+
48+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Tue, 01 Apr 2014 22:56:52 +0000
49+
50+unity8 (7.84+14.04.20140327.1-0ubuntu2) trusty; urgency=medium
51+
52+ * For now, have libunity-private depending on libunity-core-6.0-9 as the
53+ gsettings schema is here. The dependency wasn't direct and dropped from
54+ Touch image #271. Consequently, unity8 didn't start (gsettings
55+ segfaulting).
56+ Proper strategy will be to include the schema in another package to only
57+ pull it.
58+
59+ -- Didier Roche <didrocks@ubuntu.com> Tue, 01 Apr 2014 09:52:14 +0200
6
60
7unity8 (7.84+14.04.20140327.1-0ubuntu1) trusty; urgency=low
61unity8 (7.84+14.04.20140327.1-0ubuntu1) trusty; urgency=low
8
62
debian/control+14−7
@@ -18,7 +18,7 @@ Build-Depends: cmake,
18 liblightdm-qt5-3-dev,
18 liblightdm-qt5-3-dev,
19 libpulse-dev,
19 libpulse-dev,
20 libqmenumodel-dev (>= 0.2.7),
20 libqmenumodel-dev (>= 0.2.7),
21− libunity-api-dev (>= 7.80.4),
21+ libunity-api-dev (>= 7.80.6),
22 libunity-mir-dev,
22 libunity-mir-dev,
23 libupstart-dev,
23 libupstart-dev,
24 libusermetricsoutput1-dev,
24 libusermetricsoutput1-dev,
@@ -81,13 +81,16 @@ Depends: gsettings-desktop-schemas,
81 qtdeclarative5-dee-plugin,
81 qtdeclarative5-dee-plugin,
82 qtdeclarative5-gsettings1.0,
82 qtdeclarative5-gsettings1.0,
83 qtdeclarative5-ubuntu-settings-components,
83 qtdeclarative5-ubuntu-settings-components,
84+ qtdeclarative5-ubuntu-thumbnailer0.1 | ubuntu-thumbnailer-impl,
85+ ubuntu-thumbnailer-impl-0,
84 qtdeclarative5-ubuntu-ui-toolkit-plugin,
86 qtdeclarative5-ubuntu-ui-toolkit-plugin,
85 qtdeclarative5-unity-notifications-plugin | unity-notifications-impl,
87 qtdeclarative5-unity-notifications-plugin | unity-notifications-impl,
86 qtdeclarative5-xmllistmodel-plugin,
88 qtdeclarative5-xmllistmodel-plugin,
89+ unity-application-impl-2,
87 unity-greeter-session-broadcast,
90 unity-greeter-session-broadcast,
88 unity-launcher-impl-3,
91 unity-launcher-impl-3,
89 unity-notifications-impl-2,
92 unity-notifications-impl-2,
90− unity8-fake-env | qtubuntu-shell,
93+ unity8-fake-env | unity-application-impl,
91 unity8-private (= ${binary:Version}),
94 unity8-private (= ${binary:Version}),
92 unity8-private | unity-launcher-impl,
95 unity8-private | unity-launcher-impl,
93 unity-plugin-scopes | unity-scopes-impl,
96 unity-plugin-scopes | unity-scopes-impl,
@@ -99,7 +102,8 @@ Depends: gsettings-desktop-schemas,
99Recommends: unity-scope-click,
102Recommends: unity-scope-click,
100 unity-scope-mediascanner2,
103 unity-scope-mediascanner2,
101 unity-scope-scopes,
104 unity-scope-scopes,
102−Breaks: indicator-network (<< 0.5.1)
105+Breaks: indicator-network (<< 0.5.1),
106+ ubuntu-touch-session (<< 0.107),
103Replaces: ubuntu-touch-session (<< 0.82~)
107Replaces: ubuntu-touch-session (<< 0.82~)
104Description: Unity 8 shell
108Description: Unity 8 shell
105 The Unity 8 shell is the primary user interface for Ubuntu devices.
109 The Unity 8 shell is the primary user interface for Ubuntu devices.
@@ -112,8 +116,8 @@ Depends: dbus-x11,
112 unity8 (>= ${source:Version}),
116 unity8 (>= ${source:Version}),
113 ${misc:Depends},
117 ${misc:Depends},
114 ${shlibs:Depends},
118 ${shlibs:Depends},
115−Replaces: unity8 (<< 7.85),
119+Replaces: unity8 (<< 7.86),
116−Breaks: unity8 (<< 7.85),
120+Breaks: unity8 (<< 7.86),
117Description: Ubuntu QML greeter
121Description: Ubuntu QML greeter
118 The Ubuntu QML greeter is the primary greeter for Ubuntu devices.
122 The Ubuntu QML greeter is the primary greeter for Ubuntu devices.
119
123
@@ -150,7 +154,8 @@ Multi-Arch: same
150Pre-Depends: ${misc:Pre-Depends},
154Pre-Depends: ${misc:Pre-Depends},
151Depends: ${misc:Depends},
155Depends: ${misc:Depends},
152 ${shlibs:Depends},
156 ${shlibs:Depends},
153−Provides: qtubuntu-shell,
157+Provides: unity-application-impl,
158+ unity-application-impl-2,
154Description: Fake environment for running Unity 8 shell
159Description: Fake environment for running Unity 8 shell
155 Provides fake implementations of some QML modules used by Unity 8 shell
160 Provides fake implementations of some QML modules used by Unity 8 shell
156 (e.g Ubuntu.Application) so that you can run it in a sandboxed environment.
161 (e.g Ubuntu.Application) so that you can run it in a sandboxed environment.
@@ -159,8 +164,10 @@ Package: unity8-private
159Architecture: any
164Architecture: any
160Multi-Arch: same
165Multi-Arch: same
161Pre-Depends: ${misc:Pre-Depends},
166Pre-Depends: ${misc:Pre-Depends},
167+Depends: gsettings-ubuntu-schemas,
162−Depends: ${misc:Depends},
168+ ${misc:Depends},
163 ${shlibs:Depends},
169 ${shlibs:Depends},
170+ libunity-core-6.0-9,
164Provides: unity-launcher-impl,
171Provides: unity-launcher-impl,
165 unity-launcher-impl-3,
172 unity-launcher-impl-3,
166Description: Unity 8 private libs
173Description: Unity 8 private libs
debian/unity8.install+1−1
@@ -9,6 +9,6 @@ usr/share/unity8/Launcher
9usr/share/unity8/Notifications
9usr/share/unity8/Notifications
10usr/share/unity8/Panel
10usr/share/unity8/Panel
11usr/share/unity8/Shell.qml
11usr/share/unity8/Shell.qml
12−usr/share/unity8/SideStage
12+usr/share/unity8/Stages
13usr/share/unity8/graphics
13usr/share/unity8/graphics
14data/unity8.conf usr/share/upstart/sessions/
14data/unity8.conf usr/share/upstart/sessions/
plugins/DashViews/listviewwithpageheader.cpp+1−6
@@ -1097,7 +1097,7 @@ void ListViewWithPageHeader::itemGeometryChanged(QQuickItem * /*item*/, const QR
1097{
1097{
1098 const qreal heightDiff = newGeometry.height() - oldGeometry.height();
1098 const qreal heightDiff = newGeometry.height() - oldGeometry.height();
1099 if (heightDiff != 0) {
1099 if (heightDiff != 0) {
1100− if (oldGeometry.y() + oldGeometry.height() + m_clipItem->y() <= contentY() && !m_visibleItems.isEmpty()) {
1100+ if (!m_inContentHeightKeepHeaderShown && oldGeometry.y() + oldGeometry.height() + m_clipItem->y() <= contentY() && !m_visibleItems.isEmpty()) {
1101 ListItem *firstItem = m_visibleItems.first();
1101 ListItem *firstItem = m_visibleItems.first();
1102 firstItem->setY(firstItem->y() - heightDiff);
1102 firstItem->setY(firstItem->y() - heightDiff);
1103 adjustMinYExtent();
1103 adjustMinYExtent();
@@ -1293,11 +1293,6 @@ void ListViewWithPageHeader::updatePolish()
1293 refill();
1293 refill();
1294
1294
1295 if (m_contentHeightDirty) {
1295 if (m_contentHeightDirty) {
1296− // We need to make sure all bindings have updated otherwise
1297− // we may end up with bindings updating when we call setContentHeight
1298− // and then everything gets out of sync, i.e. testHeaderPositionBug1240118
1299− QCoreApplication::instance()->processEvents();
1300−
1301 qreal contentHeight;
1296 qreal contentHeight;
1302 if (m_visibleItems.isEmpty()) {
1297 if (m_visibleItems.isEmpty()) {
1303 contentHeight = m_headerItem ? m_headerItem->height() : 0;
1298 contentHeight = m_headerItem ? m_headerItem->height() : 0;
plugins/Utils/CMakeLists.txt+2−3
@@ -11,13 +11,12 @@ include_directories(
11)
11)
12
12
13set(QMLPLUGIN_SRC
13set(QMLPLUGIN_SRC
14− albumartprovider.cpp
15 bottombarvisibilitycommunicatorshell.cpp
14 bottombarvisibilitycommunicatorshell.cpp
16− mediaartcache.cpp
17 qlimitproxymodelqml.cpp
15 qlimitproxymodelqml.cpp
18 qsortfilterproxymodelqml.cpp
16 qsortfilterproxymodelqml.cpp
19 timeformatter.cpp
17 timeformatter.cpp
20 unitymenumodelpaths.cpp
18 unitymenumodelpaths.cpp
19+ easingcurve.cpp
21 plugin.cpp
20 plugin.cpp
22 )
21 )
23
22
@@ -33,7 +32,7 @@ target_link_libraries(Utils-qml ${GIO_LDFLAGS})
33# files directly in targets.
32# files directly in targets.
34set_target_properties(Utils-qml PROPERTIES COMPILE_FLAGS -fvisibility=default)
33set_target_properties(Utils-qml PROPERTIES COMPILE_FLAGS -fvisibility=default)
35
34
36−qt5_use_modules(Utils-qml Qml Quick DBus Network XmlPatterns Concurrent)
35+qt5_use_modules(Utils-qml Qml Quick DBus Network XmlPatterns Gui Concurrent)
37
36
38# export the qmldir qmltypes and plugin files
37# export the qmldir qmltypes and plugin files
39export_qmlfiles(Utils Utils)
38export_qmlfiles(Utils Utils)
plugins/Utils/albumartprovider.cppdeleted−236
@@ -1,236 +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: Jussi Pakkanen <jussi.pakkanen@canonical.com>
17− * Pawel Stolowski <pawel.stolowski@canonical.com>
18−*/
19−
20−#include "albumartprovider.h"
21−#include <QString>
22−#include <QNetworkAccessManager>
23−#include <QEventLoop>
24−#include <QNetworkRequest>
25−#include <QNetworkReply>
26−#include <QXmlQuery>
27−#include <QTemporaryFile>
28−#include <QBuffer>
29−#include <QDebug>
30−#include <QFile>
31−#include <QStringList>
32−#include <QByteArray>
33−#include <QImage>
34−#include <QUrl>
35−#include <QUrlQuery>
36−#include <QtConcurrent>
37−
38−using namespace std;
39−
40−const std::string AlbumArtProvider::DEFAULT_ALBUM_ART = "/usr/share/unity/icons/album_missing.png";
41−const std::string AlbumArtProvider::UNITY_LENS_SCHEMA = "com.canonical.Unity.Lenses";
42−
43−static QByteArray download(QString url) {
44− QScopedPointer<QNetworkAccessManager> am(new QNetworkAccessManager);
45− QNetworkReply *reply = am->get(QNetworkRequest(QUrl(url)));
46− QEventLoop loop;
47− QObject::connect(am.data(), &QNetworkAccessManager::finished, &loop, &QEventLoop::quit);
48− loop.exec();
49−
50− if (reply->error() != QNetworkReply::NoError) {
51− qWarning() << "Error downloading from the network:" << reply->errorString();
52− return QByteArray();
53− }
54− return reply->readAll();
55−}
56−
57−AlbumArtProvider::AlbumArtProvider()
58− : QQuickImageProvider(QQmlImageProviderBase::Image, QQmlImageProviderBase::ForceAsynchronousImageLoading),
59− m_settings(nullptr)
60−{
61− auto schemas = g_settings_list_schemas();
62− if (schemas) {
63− for (int i = 0; schemas[i]; i++) {
64− if (g_strcmp0(schemas[i], UNITY_LENS_SCHEMA.c_str()) == 0) {
65− m_settings = g_settings_new(UNITY_LENS_SCHEMA.c_str());
66− break;
67− }
68− }
69− }
70− if (m_settings == nullptr) {
71− qWarning() << "Missing" << QString::fromStdString(UNITY_LENS_SCHEMA) << "schema";
72− }
73−}
74−
75−AlbumArtProvider::~AlbumArtProvider()
76−{
77− if (m_settings)
78− g_object_unref(m_settings);
79−}
80−
81−std::string AlbumArtProvider::get_lastfm_url(const albuminfo &ai) {
82− QString artist = QString::fromStdString(ai.artist);
83− QString album = QString::fromStdString(ai.album);
84− // audioscrobbler wants it without any / (as it would break the URL)
85− artist.remove('/');
86− album.remove('/');
87−
88− /// @todo: this is the old API which will probably get axed at some point in the future
89− /// The new 2.0 API requires an API key, but supports JSON output, etc, so switching
90− /// to it should be done ASAP.
91− QString request = QString("http://ws.audioscrobbler.com/1.0/album/%1/%2/info.xml").arg(artist)
92− .arg(album)
93− .toHtmlEscaped();
94− QFuture<QByteArray> future = QtConcurrent::run(download, request);
95− QByteArray arr = future.result();
96− if(arr.size() == 0) {
97− return "";
98− }
99− QXmlQuery query;
100− QBuffer tmp;
101− tmp.setData(arr);
102− tmp.open(QIODevice::ReadOnly);
103− query.bindVariable("reply", &tmp);
104− query.setQuery("doc($reply)/album/coverart/large[1]/text()");
105− QString image;
106− query.evaluateTo(&image);
107− image = image.trimmed();
108−
109− // return empty if last.fm tries to offer their default
110− // fallback artwork
111− if (image.contains("catalogue/noimage")) {
112− return "";
113− }
114−
115− return image.toStdString();
116−}
117−
118−bool AlbumArtProvider::download_and_store(const std::string &image_url, const std::string &output_file) {
119− QString url = QString::fromStdString(image_url);
120− QString fileName = QString::fromStdString(output_file);
121− QFuture<QByteArray> future = QtConcurrent::run(download, url);
122− QByteArray arr = future.result();
123− if (arr.size() == 0) {
124− return false;
125− }
126− QFile file(fileName);
127− if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
128− qWarning() << "Could not open file for writing:" << file.errorString();
129− return false;
130− }
131− if (file.write(arr) == -1){
132− qWarning() << "Could not write the image:" << file.error();
133− return false;
134− }
135−
136− return true;
137−}
138−
139−void AlbumArtProvider::fix_format(const std::string &fname) {
140− // MediaArtSpec requires jpg. Convert to it if necessary.
141− FILE *f = fopen(fname.c_str(), "r");
142− if(!f)
143− return;
144− unsigned char buf[2];
145− fread(buf, 1, 2, f);
146− fclose(f);
147− if(buf[0] == 0xff && buf[1] == 0xd8) {
148− return;
149− }
150− QImage im(fname.c_str());
151− im.save(fname.c_str(), "JPEG");
152−}
153−
154−std::string AlbumArtProvider::get_image(const std::string &artist, const std::string &album) {
155− albuminfo info;
156−
157− info.artist = artist;
158− info.album = album;
159−
160− if(info.album.empty() || info.artist.empty()) {
161− return DEFAULT_ALBUM_ART;
162− }
163− if(cache.has_art(info.artist, info.album)) {
164− // Image may have expired from cache between these two lines.
165− // It might expire before we return. C'est la vie.
166− return cache.get_art_file(info.artist, info.album);
167− }
168−
169− if (m_settings != nullptr && g_strcmp0(g_settings_get_string(m_settings, "remote-content-search"), "all") !=0) {
170− qDebug() << "Remote content disabled";
171− return DEFAULT_ALBUM_ART;
172− }
173−
174− std::string image_url = get_lastfm_url(info);
175− if(image_url.empty()) {
176− return DEFAULT_ALBUM_ART;
177− }
178− QTemporaryFile tempFile;
179− tempFile.open();
180− tempFile.setAutoRemove(true);
181− QString fname = tempFile.fileName();
182− std::string tmpname = fname.toUtf8().data();
183−
184− if(!download_and_store(image_url, tmpname)) {
185− return DEFAULT_ALBUM_ART;
186− }
187− fix_format(tmpname.c_str());
188− QFile f(fname);
189− if(!f.open(QIODevice::ReadWrite)) {
190− return DEFAULT_ALBUM_ART;
191− }
192− QByteArray arr = f.readAll();
193− f.close();
194−
195− cache.add_art(info.artist, info.album, arr.data(), arr.size());
196− try {
197− std::string res = cache.get_art_file(info.artist, info.album);
198− if(res.empty())
199− return DEFAULT_ALBUM_ART;
200− return res;
201− } catch(...) {
202− }
203− return DEFAULT_ALBUM_ART;
204−}
205−
206−QImage AlbumArtProvider::requestImage(const QString &id, QSize *realSize, const QSize &requestedSize) {
207− Q_UNUSED(requestedSize)
208−
209− QUrlQuery query(id);
210− if (!query.hasQueryItem(QStringLiteral("artist")) || !query.hasQueryItem(QStringLiteral("album"))) {
211− qWarning() << "Invalid albumart uri:" << id;
212− return QImage(QString::fromStdString(DEFAULT_ALBUM_ART));
213− }
214− const QString artist = query.queryItemValue(QStringLiteral("artist"), QUrl::FullyEncoded);
215− const QString album = query.queryItemValue(QStringLiteral("album"), QUrl::FullyEncoded);
216−
217− std::string tgt_path;
218− try {
219− tgt_path = get_image(artist.toStdString(), album.toStdString());
220− if(!tgt_path.empty()) {
221− QImage image;
222− image.load(QString::fromStdString(tgt_path));
223− // FIXME: Rescale to requested size preserving aspect.
224− *realSize = image.size();
225− return image;
226− }
227− } catch(std::exception &e) {
228− qDebug() << "Album art loader failed: " << e.what();
229− } catch(...) {
230− qDebug() << "Unknown error when generating image.";
231− }
232−
233− QImage fallback(QString::fromStdString(DEFAULT_ALBUM_ART));
234− *realSize = fallback.size();
235− return fallback;
236−}
plugins/Utils/albumartprovider.hdeleted−56
@@ -1,56 +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: Jussi Pakkanen <jussi.pakkanen@canonical.com>
17−*/
18−
19−#ifndef ALBUMART_PROVIDER_H_
20−#define ALBUMART_PROVIDER_H_
21−
22−#include <string>
23−#include <stdexcept>
24−#include <QQuickImageProvider>
25−#include <gio/gio.h>
26−
27−#include "mediaartcache.h"
28−
29−struct albuminfo {
30− std::string artist;
31− std::string album;
32−};
33−
34−class AlbumArtProvider : public QQuickImageProvider
35−{
36−public:
37− AlbumArtProvider();
38− ~AlbumArtProvider();
39− QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize);
40−
41− std::string get_image(const std::string &artist, const std::string &album);
42−
43− const static std::string DEFAULT_ALBUM_ART;
44− const static std::string UNITY_LENS_SCHEMA;
45−private:
46− MediaArtCache cache;
47− GSettings *m_settings;
48−
49− void fix_format(const std::string &fname);
50−
51− std::string get_lastfm_url(const albuminfo &ai);
52−
53− bool download_and_store(const std::string &image_url, const std::string &output_file);
54−};
55−
56−#endif
plugins/Utils/easingcurve.cppnew file+67
@@ -0,0 +1,67 @@
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: Michael Zanetti <michael.zanetti@canonical.com>
17+*/
18+
19+#include "easingcurve.h"
20+
21+
22+EasingCurve::EasingCurve(QObject *parent):
23+ QObject(parent)
24+{
25+
26+}
27+
28+QEasingCurve::Type EasingCurve::type() const
29+{
30+ return m_easingCurve.type();
31+}
32+
33+void EasingCurve::setType(const QEasingCurve::Type &type)
34+{
35+ m_easingCurve.setType(type);
36+ Q_EMIT typeChanged();
37+}
38+
39+qreal EasingCurve::period() const
40+{
41+ return m_easingCurve.period();
42+}
43+
44+void EasingCurve::setPeriod(qreal period)
45+{
46+ m_easingCurve.setPeriod(period);
47+ Q_EMIT periodChanged();
48+}
49+
50+qreal EasingCurve::progress() const
51+{
52+ return m_progress;
53+}
54+
55+void EasingCurve::setProgress(qreal progress)
56+{
57+ if (m_progress != progress) {
58+ m_progress = progress;
59+ m_value = m_easingCurve.valueForProgress(m_progress);
60+ Q_EMIT progressChanged();
61+ }
62+}
63+
64+qreal EasingCurve::value() const
65+{
66+ return m_value;
67+}
plugins/Utils/easingcurve.hnew file+70
@@ -0,0 +1,70 @@
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: Michael Zanetti <michael.zanetti@canonical.com>
17+*/
18+
19+#ifndef EASINGCURVE_H
20+#define EASINGCURVE_H
21+
22+#include <QObject>
23+#include <QEasingCurve>
24+
25+/**
26+ * @brief The EasingCurve class
27+ *
28+ * This class exposes the QEasingCurve C++ API to QML.
29+ * This is useful for user interactive animations. While the QML Animation types
30+ * all require a "from", "to" and "duration", this one is based on "period" and
31+ * "progress". So you can control the position of the aimation by changing the
32+ * progress, also going back and forward in the animation. Depending on the type
33+ * of the easing curve, value will return the transformed progress.
34+ */
35+
36+class EasingCurve: public QObject
37+{
38+ Q_OBJECT
39+ Q_ENUMS(QEasingCurve::Type)
40+ Q_PROPERTY(QEasingCurve::Type type READ type WRITE setType NOTIFY typeChanged)
41+ Q_PROPERTY(qreal period READ period WRITE setPeriod NOTIFY periodChanged)
42+ Q_PROPERTY(qreal progress READ progress WRITE setProgress NOTIFY progressChanged)
43+ Q_PROPERTY(qreal value READ value NOTIFY progressChanged)
44+
45+public:
46+ EasingCurve(QObject *parent = 0);
47+
48+ QEasingCurve::Type type() const;
49+ void setType(const QEasingCurve::Type &type);
50+
51+ qreal period() const;
52+ void setPeriod(qreal period);
53+
54+ qreal progress() const;
55+ void setProgress(qreal progress);
56+
57+ qreal value() const;
58+
59+Q_SIGNALS:
60+ void typeChanged();
61+ void periodChanged();
62+ void progressChanged();
63+
64+private:
65+ QEasingCurve m_easingCurve;
66+ qreal m_progress;
67+ qreal m_value;
68+};
69+
70+#endif
plugins/Utils/mediaartcache.cppdeleted−178
@@ -1,178 +0,0 @@
1−/*
2− * This file is part of the Ubuntu TV Media Scanner
3− * Copyright (C) 2012-2013 Canonical Ltd.
4− *
5− * This program is free software: you can redistribute it and/or modify
6− * it under the terms of the GNU Lesser General Public License version 3 as
7− * published by the Free Software Foundation.
8− *
9− * This program is distributed in the hope that it will be useful,
10− * but WITHOUT ANY WARRANTY; without even the implied warranty of
11− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12− * GNU Lesser General Public License for more details.
13− *
14− * You should have received a copy of the GNU Lesser General Public License
15− * along with this program. If not, see <http://www.gnu.org/licenses/>.
16− *
17− * Authored by: Jussi Pakkanen <jussi.pakkanen@canonical.com>
18− */
19−
20−#include"mediaartcache.h"
21−
22−#include<dirent.h>
23−#include<sys/stat.h>
24−#include<fcntl.h>
25−#include<unistd.h>
26−#include<errno.h>
27−#include<stdio.h>
28−#include<string.h>
29−#include<stdlib.h>
30−#include<assert.h>
31−#include<utime.h>
32−
33−#include<glib.h>
34−
35−#include<vector>
36−#include<algorithm>
37−#include<stdexcept>
38−
39−using namespace std;
40−
41−static string md5(const string &str) {
42− const unsigned char *buf = (const unsigned char *)str.c_str();
43− char *normalized = g_utf8_normalize((const gchar*)buf, str.size(), G_NORMALIZE_ALL);
44− string final;
45− gchar *result;
46−
47− if(normalized) {
48− buf = (const unsigned char*)normalized;
49− }
50− gssize bytes = str.length();
51−
52− result = g_compute_checksum_for_data(G_CHECKSUM_MD5, buf, bytes);
53− final = result;
54− g_free((gpointer)normalized);
55− g_free(result);
56− return final;
57−}
58−
59−MediaArtCache::MediaArtCache() {
60− string xdg_base = g_get_user_cache_dir();
61−
62− if (xdg_base == "") {
63− string s("Could not determine cache dir.");
64− throw runtime_error(s);
65− }
66− int ec = mkdir(xdg_base.c_str(), S_IRUSR | S_IWUSR | S_IXUSR);
67− if (ec < 0 && errno != EEXIST) {
68− string s("Could not create base dir.");
69− throw runtime_error(s);
70− }
71− root_dir = xdg_base + "/media-art";
72− ec = mkdir(root_dir.c_str(), S_IRUSR | S_IWUSR | S_IXUSR);
73− if (ec < 0 && errno != EEXIST) {
74− string s("Could not create cache dir.");
75− throw runtime_error(s);
76− }
77−}
78−
79−bool MediaArtCache::has_art(const std::string &artist, const std::string &album) const {
80− string fname = get_art_file(artist, album);
81− return access(fname.c_str(), R_OK) == 0;
82−}
83−
84−void MediaArtCache::add_art(const std::string &artist, const std::string &album,
85− char *data, unsigned int datalen) {
86− string abs_fname = get_full_filename(artist, album);
87− GError *err = nullptr;
88− if(!g_file_set_contents(abs_fname.c_str(), data, datalen, &err)) {
89− string e("Could not write file ");
90− e += abs_fname;
91− e += ": ";
92− e += err->message;
93− g_error_free(err);
94− throw runtime_error(e);
95− }
96−}
97−
98−string MediaArtCache::get_art_file(const std::string &artist, const std::string &album) const {
99− string abs_fname = get_full_filename(artist, album);
100−
101− if (access(abs_fname.c_str(), R_OK) == 0) {
102− utime(abs_fname.c_str(), nullptr); // update access times to current time
103− return abs_fname;
104− }
105− return "";
106−}
107−
108−std::string MediaArtCache::get_full_filename(const std::string &artist, const std::string & album) const {
109− return root_dir + "/" + compute_base_name(artist, album);
110−}
111−
112−
113−std::string MediaArtCache::compute_base_name(const std::string &artist, const std::string &album) const {
114− string type = "album";
115− string h1 = md5(artist);
116− string h2 = md5(album);
117− return type + "-" + h1 + "-" + h2 + ".jpg";
118−}
119−
120−
121−void MediaArtCache::clear() const {
122− DIR *d = opendir(root_dir.c_str());
123− if(!d) {
124− string s = "Something went wrong.";
125− throw runtime_error(s);
126− }
127− struct dirent *entry, *de;
128− entry = (dirent*)malloc(sizeof(dirent) + NAME_MAX);
129− while(readdir_r(d, entry, &de) == 0 && de) {
130− string basename = entry->d_name;
131− if (basename == "." || basename == "..")
132− continue;
133− string fname = root_dir + "/" + basename;
134− if(remove(fname.c_str()) < 0) {
135− // This is not really an error worth
136− // halting everything for.
137− fprintf(stderr, "Could not delete file %s: %s.\n", fname.c_str(),
138− strerror(errno));
139− }
140− }
141− free(entry);
142− closedir(d);
143−}
144−
145−void MediaArtCache::prune() {
146− vector<pair<double, string>> mtimes;
147− DIR *d = opendir(root_dir.c_str());
148− if(!d) {
149− string s = "Something went wrong.";
150− throw runtime_error(s);
151− }
152− struct dirent *entry, *de;
153− entry = (dirent*)malloc(sizeof(dirent) + NAME_MAX);
154− while(readdir_r(d, entry, &de) == 0 && de) {
155− string basename = entry->d_name;
156− if (basename == "." || basename == "..")
157− continue;
158− string fname = root_dir + "/" + basename;
159− struct stat sbuf;
160− if(stat(fname.c_str(), &sbuf) != 0) {
161− continue;
162− }
163− // Use mtime because atime is not guaranteed to work if, for example
164− // the filesystem is mounted with noatime or relatime.
165− mtimes.push_back(make_pair(sbuf.st_mtim.tv_sec + sbuf.st_mtim.tv_nsec/1000000000.0, fname));
166− }
167− free(entry);
168− closedir(d);
169− if (mtimes.size() <= MAX_SIZE)
170− return;
171− sort(mtimes.begin(), mtimes.end());
172− for(size_t i=0; i < mtimes.size()-MAX_SIZE; i++) {
173− if(remove(mtimes[i].second.c_str()) < 0) {
174− fprintf(stderr, "Could not remove file %s: %s.\n",
175− mtimes[i].second.c_str(), strerror(errno));
176− }
177− }
178−}
plugins/Utils/mediaartcache.hdeleted−54
@@ -1,54 +0,0 @@
1−/*
2− * This file is part of the Ubuntu TV Media Scanner
3− * Copyright (C) 2012-2013 Canonical Ltd.
4− *
5− * This program is free software: you can redistribute it and/or modify
6− * it under the terms of the GNU Lesser General Public License version 3 as
7− * published by the Free Software Foundation.
8− *
9− * This program is distributed in the hope that it will be useful,
10− * but WITHOUT ANY WARRANTY; without even the implied warranty of
11− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12− * GNU Lesser General Public License for more details.
13− *
14− * You should have received a copy of the GNU Lesser General Public License
15− * along with this program. If not, see <http://www.gnu.org/licenses/>.
16− *
17− * Authored by: Jussi Pakkanen <jussi.pakkanen@canonical.com>
18− */
19−
20−#ifndef MEDIAARTCACHE_H
21−#define MEDIAARTCACHE_H
22−
23−#include<string>
24−
25−/*
26− * A class to store thumbnails for files according to
27− * https://wiki.gnome.org/MediaArtStorageSpec
28− *
29− * As this class deals mostly with the filesystem, all
30− * errors are reported with runtime_error exceptions.
31− */
32−
33−
34−class MediaArtCache {
35−private:
36− std::string root_dir;
37−
38− std::string compute_base_name(const std::string &artist, const std::string &album) const;
39− std::string get_full_filename(const std::string &artist, const std::string & album) const;
40−
41−public:
42− static const unsigned int MAX_SIZE = 200;
43−
44− MediaArtCache();
45− bool has_art(const std::string &artist, const std::string &album) const;
46− void add_art(const std::string &artist, const std::string &album,
47− char *data, unsigned int datalen);
48− std::string get_art_file(const std::string &artist, const std::string &album) const;
49− void clear() const;
50− void prune();
51− std::string get_cache_dir() const { return root_dir; }
52−};
53−
54−#endif
plugins/Utils/plugin.cpp+2−14
@@ -26,12 +26,12 @@
26#include "plugin.h"
26#include "plugin.h"
27
27
28// local
28// local
29−#include "albumartprovider.h"
30#include "bottombarvisibilitycommunicatorshell.h"
29#include "bottombarvisibilitycommunicatorshell.h"
31#include "qlimitproxymodelqml.h"
30#include "qlimitproxymodelqml.h"
32#include "qsortfilterproxymodelqml.h"
31#include "qsortfilterproxymodelqml.h"
33#include "timeformatter.h"
32#include "timeformatter.h"
34#include "unitymenumodelpaths.h"
33#include "unitymenumodelpaths.h"
34+#include "easingcurve.h"
35
35
36static const char* BOTTOM_BAR_VISIBILITY_COMMUNICATOR_DBUS_PATH = "/BottomBarVisibilityCommunicator";
36static const char* BOTTOM_BAR_VISIBILITY_COMMUNICATOR_DBUS_PATH = "/BottomBarVisibilityCommunicator";
37static const char* DBUS_SERVICE = "com.canonical.Shell.BottomBarVisibilityCommunicator";
37static const char* DBUS_SERVICE = "com.canonical.Shell.BottomBarVisibilityCommunicator";
@@ -46,25 +46,13 @@ void UtilsPlugin::registerTypes(const char *uri)
46 qmlRegisterType<TimeFormatter>(uri, 0, 1, "TimeFormatter");
46 qmlRegisterType<TimeFormatter>(uri, 0, 1, "TimeFormatter");
47 qmlRegisterType<GDateTimeFormatter>(uri, 0, 1, "GDateTimeFormatter");
47 qmlRegisterType<GDateTimeFormatter>(uri, 0, 1, "GDateTimeFormatter");
48 qmlRegisterUncreatableType<BottomBarVisibilityCommunicatorShell>(uri, 0, 1, "BottomBarVisibilityCommunicatorShell", "Can't create BottomBarVisibilityCommunicatorShell");
48 qmlRegisterUncreatableType<BottomBarVisibilityCommunicatorShell>(uri, 0, 1, "BottomBarVisibilityCommunicatorShell", "Can't create BottomBarVisibilityCommunicatorShell");
49+ qmlRegisterType<EasingCurve>(uri, 0, 1, "EasingCurve");
49}
50}
50
51
51void UtilsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
52void UtilsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
52{
53{
53 QQmlExtensionPlugin::initializeEngine(engine, uri);
54 QQmlExtensionPlugin::initializeEngine(engine, uri);
54
55
55− try
56− {
57− engine->addImageProvider(QLatin1String("albumart"), new AlbumArtProvider);
58− }
59− catch (const std::runtime_error &e)
60− {
61− qWarning() << "Failed to register image provider for albumart:" << e.what();
62− }
63− catch (...)
64− {
65− qWarning() << "Failed to register image provider for albumart (unknown error)";
66− }
67−
68 QDBusConnection::sessionBus().registerService(DBUS_SERVICE);
56 QDBusConnection::sessionBus().registerService(DBUS_SERVICE);
69 BottomBarVisibilityCommunicatorShell *bottomBarVisibilityCommunicator = &BottomBarVisibilityCommunicatorShell::instance();
57 BottomBarVisibilityCommunicatorShell *bottomBarVisibilityCommunicator = &BottomBarVisibilityCommunicatorShell::instance();
70 QDBusConnection::sessionBus().registerObject(BOTTOM_BAR_VISIBILITY_COMMUNICATOR_DBUS_PATH, bottomBarVisibilityCommunicator, QDBusConnection::ExportAllContents);
58 QDBusConnection::sessionBus().registerObject(BOTTOM_BAR_VISIBILITY_COMMUNICATOR_DBUS_PATH, bottomBarVisibilityCommunicator, QDBusConnection::ExportAllContents);
po/bg.po+4−21
@@ -8,14 +8,13 @@ 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-03-25 16:48+0100\n"
10"POT-Creation-Date: 2014-03-25 16:48+0100\n"
11−"PO-Revision-Date: 2014-01-28 19:22+0000\n"
11+"PO-Revision-Date: 2014-04-01 14:08+0000\n"
12"Last-Translator: Atanas Kovachki <Unknown>\n"
12"Last-Translator: Atanas Kovachki <Unknown>\n"
13"Language-Team: Bulgarian <bg@li.org>\n"
13"Language-Team: Bulgarian <bg@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−"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2014-03-28 06:48+0000\n"
17+"X-Launchpad-Export-Date: 2014-04-02 06:50+0000\n"
19"X-Generator: Launchpad (build 16967)\n"
18"X-Generator: Launchpad (build 16967)\n"
20
19
21#: plugins/Unity/Launcher/launcheritem.cpp:43
20#: plugins/Unity/Launcher/launcheritem.cpp:43
@@ -94,7 +93,7 @@ msgstr ""
94
93
95#: qml/Dash/DashApps.qml:38
94#: qml/Dash/DashApps.qml:38
96msgid "Recent"
95msgid "Recent"
97−msgstr ""
96+msgstr "Последни"
98
97
99#: qml/Dash/PreviewListView.qml:52
98#: qml/Dash/PreviewListView.qml:52
100#, qt-format
99#, qt-format
@@ -107,7 +106,7 @@ msgstr ""
107
106
108#: qml/Dash/Previews/PreviewActionCombo.qml:34
107#: qml/Dash/Previews/PreviewActionCombo.qml:34
109msgid "Less..."
108msgid "Less..."
110−msgstr ""
109+msgstr "По-малко..."
111
110
112#: qml/Dash/Previews/PreviewRatingInput.qml:81
111#: qml/Dash/Previews/PreviewRatingInput.qml:81
113msgid "Rate this"
112msgid "Rate this"
@@ -170,19 +169,3 @@ msgstr "Търсене"
170#, qt-format
169#, qt-format
171msgid "Please enter %1"
170msgid "Please enter %1"
172msgstr "Моля, въведете %1"
171msgstr "Моля, въведете %1"
173−
174−#~ msgid "Review"
175−#~ msgstr "Отзив"
176−
177−#, qt-format
178−#~ msgid "%1 review"
179−#~ msgid_plural "%1 reviews"
180−#~ msgstr[0] "%1 отзив"
181−#~ msgstr[1] "%1 отзива"
182−
183−#, qt-format
184−#~ msgid "(%1)"
185−#~ msgstr "(%1)"
186−
187−#~ msgid "Comments:"
188−#~ msgstr "Коментари:"
qml/CMakeLists.txt+1−1
@@ -13,7 +13,7 @@ set(QML_DIRS
13 Hud
13 Hud
14 Panel
14 Panel
15 Launcher
15 Launcher
16− SideStage
16+ Stages
17 Notifications
17 Notifications
18 )
18 )
19
19
qml/Components/ApplicationManagerWrapper.qmldeleted−169
@@ -1,169 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import Unity.Application 0.1
19−
20−Item {
21− id: root
22−
23− property alias mainStageApplications: mainStageModel
24− property alias sideStageApplications: sideStageModel
25− property variant mainStageFocusedApplication: null
26− property variant sideStageFocusedApplication: null
27− property bool sideStageEnabled: true
28− property bool keyboardVisible: ApplicationManager.keyboardVisible
29− property int keyboardHeight: ApplicationManager.keyboardHeight
30−
31− property bool fake: ApplicationManager.fake ? ApplicationManager.fake : false
32−
33− signal focusRequested(string appId)
34−
35− ApplicationsModelStageFiltered {
36− id: mainStageModel
37− stage: ApplicationInfo.MainStage
38− }
39−
40− ApplicationsModelStageFiltered {
41− id: sideStageModel
42− stage: ApplicationInfo.SideStage
43− }
44−
45− Connections {
46− target: ApplicationManager
47− onFocusedApplicationIdChanged: {
48− var app = ApplicationManager.findApplication(ApplicationManager.focusedApplicationId)
49− if (!app) { //nothing at all focused, so clear all
50− mainStageFocusedApplication = null;
51− sideStageFocusedApplication = null;
52− } else {
53− if (app.stage == ApplicationInfo.MainStage) {
54− mainStageFocusedApplication = app;
55− // possible the side stage app being unfocused fired this signal, so check for it
56− if (sideStageFocusedApplication && !sideStageFocusedApplication.focused)
57− sideStageFocusedApplication = null;
58− } else {
59− sideStageFocusedApplication = app;
60− // possible the main stage app being unfocused fired this signal, so check for it
61− if (mainStageFocusedApplication && !mainStageFocusedApplication.focused)
62− mainStageFocusedApplication = null;
63− }
64− }
65− }
66−
67− onFocusRequested: {
68− // if no side stage enabled, override application's stage parameter
69− var app = ApplicationManager.findApplication(appId);
70− if (app && app.stage === ApplicationInfo.SideStage && !sideStageEnabled) {
71− app.stage = ApplicationInfo.MainStage;
72− }
73−
74− root.focusRequested(appId);
75− }
76− }
77−
78− function activateApplication(desktopFile, argument) {
79− var appId;
80−
81− // HACK: Applications identified sometimes with with appId, but mostly with desktopFile.
82− // TODO: convert entire shell to use appId only.
83− if (desktopFile.indexOf(".desktop") >= 0) {
84− appId = desktopFileToAppId(desktopFile);
85− } else {
86− appId = desktopFile;
87− }
88−
89− var application = ApplicationManager.findApplication(appId);
90− if (application !== null) {
91− return application;
92− }
93−
94− var execFlags = sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
95−
96− if (argument) {
97− return ApplicationManager.startApplication(appId, execFlags, [argument]);
98− } else {
99− return ApplicationManager.startApplication(appId, execFlags);
100− }
101− }
102−
103− function stopApplication(application) {
104− var appId;
105−
106− // HACK: Applications identified sometimes with with appId, but mostly with desktopFile.
107− // TODO: convert entire shell to use appId only.
108− if (typeof application == "string") {
109− appId = application;
110− } else {
111− appId = desktopFileToAppId(application.desktopFile);
112− }
113−
114− ApplicationManager.stopApplication(appId);
115− }
116−
117− function focusApplication(application) {
118− if (application == null || application == undefined) {
119− return;
120− }
121−
122− ApplicationManager.focusApplication(application.appId);
123− }
124−
125− function unfocusCurrentApplication() {
126− ApplicationManager.unfocusCurrentApplication();
127− }
128−
129− function moveRunningApplicationStackPosition(from, to, stage) {
130− if (from == to || from < 0 || to < 0) return;
131−
132− if (stage == ApplicationInfo.SideStage) {
133− sideStageModel.move(from, to);
134− } else {
135− mainStageModel.move(from, to);
136− }
137− }
138−
139− function getApplicationFromDesktopFile(desktopFile, stage) {
140− var appId;
141−
142− // HACK: Applications identified sometimes with with appId, but mostly with desktopFile.
143− // TODO: convert entire shell to use appId only.
144− if (desktopFile.indexOf(".desktop") >= 0) {
145− appId = desktopFileToAppId(desktopFile);
146− } else {
147− appId = desktopFile;
148− }
149−
150− for (var i = 0, len = ApplicationManager.count; i < len; i++ ) {
151− var app = ApplicationManager.get(i);
152−
153− // if stage not specified, return whichever app running on either stage
154− if (app.appId == appId && (stage == undefined || app.stage == stage)) {
155− return app;
156− }
157− }
158− }
159−
160− function desktopFileToAppId(desktopFile) {
161− var right = desktopFile.lastIndexOf(".desktop");
162− var left = desktopFile.lastIndexOf("/");
163− if (left == -1 || right == -1 || left == right) {
164− console.log("ApplicationManagerWrapper: unable to extract appId from '" + desktopFile + "'");
165− return "";
166− }
167− return desktopFile.substring(left+1, right);
168− }
169−}
qml/Components/ApplicationScreenshot.qmldeleted−61
@@ -1,61 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import Ubuntu.Components 0.1
19−import Unity.Application 0.1
20−
21−Item {
22− id: applicationScreenshot
23−
24− property var application: null
25− property bool withBackground: false
26− property bool ready: applicationImage.ready || withBackground
27−
28− function setApplication(application) {
29− applicationScreenshot.application = application
30− }
31−
32− function clearApplication() {
33− applicationScreenshot.withBackground = false;
34− applicationScreenshot.application = null
35− applicationScreenshot.scheduleUpdate();
36− }
37−
38− function scheduleUpdate() {
39− applicationImage.scheduleUpdate()
40− }
41−
42− function updateFromCache() {
43− applicationImage.updateFromCache()
44− }
45−
46− Rectangle {
47− id: background
48− anchors.fill: parent
49− color: "white" // FIXME should use normal background color of Suru theme
50− visible: applicationScreenshot.withBackground
51− }
52−
53− ApplicationImage {
54− id: applicationImage
55− objectName: "screenshot image"
56− width: applicationScreenshot.application ? parent.width : 0
57− height: applicationScreenshot.application ? parent.height : 0
58− visible: applicationScreenshot.application != null && ready
59− source: ApplicationManager.findApplication((application) ? application.appId : "")
60− }
61−}
qml/Components/ApplicationsModelStageFiltered.qmldeleted−49
@@ -1,49 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import Utils 0.1
19−import Unity.Application 0.1
20−
21−SortFilterProxyModel {
22− id: root
23− property int stage
24−
25− function get(index) {
26− return model.get(mapRowToSource(index));
27− }
28−
29− function move(from, to) {
30− var realFrom = mapRowToSource(from);
31− if (realFrom == -1) {
32− console.log("ERROR; invalid from=" + from + " index in move operation");
33− return;
34− }
35− var realTo = mapRowToSource(to);
36− if (realTo == -1) {
37− // assuming wanting to move to end of list
38− realTo = model.count - 1;
39− }
40−
41− if (realFrom == realTo) return;
42− model.move(realFrom, realTo);
43− }
44−
45− model: ApplicationManager
46− dynamicSortFilter: true
47− filterRole: ApplicationManager.RoleStage
48− filterRegExp: RegExp(stage)
49−}
qml/Components/Carousel.qml+69−63
@@ -261,77 +261,83 @@ Item {
261 listView.gapToEndPhase,
261 listView.gapToEndPhase,
262 listView.translationXViewFactor)
262 listView.translationXViewFactor)
263
263
264+ delegate: tileWidth > 0 && tileHeight > 0 ? loaderComponent : undefined
265+
266+ Component {
267+ id: loaderComponent
268+
264− delegate: Loader {
269+ Loader {
265− property bool explicitlyScaled: explicitScaleFactor == carousel.selectedItemScaleFactor
270+ property bool explicitlyScaled: explicitScaleFactor == carousel.selectedItemScaleFactor
266− property real explicitScaleFactor: explicitScale ? carousel.selectedItemScaleFactor : 1.0
271+ property real explicitScaleFactor: explicitScale ? carousel.selectedItemScaleFactor : 1.0
267− readonly property bool explicitScale: (!listView.moving ||
272+ readonly property bool explicitScale: (!listView.moving ||
268− listView.realContentX <= 0 ||
273+ listView.realContentX <= 0 ||
269− listView.realContentX >= listView.realContentWidth - listView.realWidth) &&
274+ listView.realContentX >= listView.realContentWidth - listView.realWidth) &&
270− listView.newContentX === listView.disabledNewContentX &&
275+ listView.newContentX === listView.disabledNewContentX &&
271− index === listView.selectedIndex
276+ index === listView.selectedIndex
272− readonly property real cachedTiles: listView.realPathItemCount + carousel.drawBuffer / listView.tileWidth
277+ readonly property real cachedTiles: listView.realPathItemCount + carousel.drawBuffer / listView.tileWidth
273− readonly property real distance: listView.continuousIndex - index
278+ readonly property real distance: listView.continuousIndex - index
274− readonly property real itemTranslationScale: CarouselJS.getItemScale(0.5,
279+ readonly property real itemTranslationScale: CarouselJS.getItemScale(0.5,
275− (index + 0.5), // good approximation of scale while changing selected item
280+ (index + 0.5), // good approximation of scale while changing selected item
276− listView.count,
281+ listView.count,
277− listView.visibleTilesScaleFactor)
282+ listView.visibleTilesScaleFactor)
278− readonly property real itemScale: CarouselJS.getItemScale(distance,
283+ readonly property real itemScale: CarouselJS.getItemScale(distance,
279− listView.continuousIndex,
284+ listView.continuousIndex,
280− listView.count,
285+ listView.count,
281− listView.visibleTilesScaleFactor)
286+ listView.visibleTilesScaleFactor)
282− readonly property real translationX: CarouselJS.getItemTranslation(index,
287+ readonly property real translationX: CarouselJS.getItemTranslation(index,
283− listView.selectedIndex,
288+ listView.selectedIndex,
284− distance,
289+ distance,
285− itemScale,
290+ itemScale,
286− itemTranslationScale,
291+ itemTranslationScale,
287− listView.maximumItemTranslation)
292+ listView.maximumItemTranslation)
288−
293+
289− readonly property real xTransform: listView.viewTranslation + translationX * listView.scaleFactor
294+ readonly property real xTransform: listView.viewTranslation + translationX * listView.scaleFactor
290− readonly property real center: x - listView.contentX + xTransform - drawBuffer + (width/2)
295+ readonly property real center: x - listView.contentX + xTransform - drawBuffer + (width/2)
291−
296+
292− width: listView.tileWidth
297+ width: listView.tileWidth
293− height: listView.tileHeight
298+ height: listView.tileHeight
294− scale: itemScale * explicitScaleFactor
299+ scale: itemScale * explicitScaleFactor
295− sourceComponent: itemComponent
300+ sourceComponent: itemComponent
296− z: cachedTiles - Math.abs(index - listView.selectedIndex)
301+ z: cachedTiles - Math.abs(index - listView.selectedIndex)
297−
302+
298− transform: Translate {
303+ transform: Translate {
299− x: xTransform
304+ x: xTransform
300− }
305+ }
301
306
302− Behavior on explicitScaleFactor {
307+ Behavior on explicitScaleFactor {
303− SequentialAnimation {
308+ SequentialAnimation {
304− ScriptAction {
309+ ScriptAction {
305− script: if (!explicitScale)
310+ script: if (!explicitScale)
306− explicitlyScaled = false
311+ explicitlyScaled = false
307− }
312+ }
308− NumberAnimation {
313+ NumberAnimation {
309− duration: explicitScaleFactor === 1.0 ? 250 : 150
314+ duration: explicitScaleFactor === 1.0 ? 250 : 150
310− easing.type: Easing.InOutQuad
315+ easing.type: Easing.InOutQuad
311− }
316+ }
312− ScriptAction {
317+ ScriptAction {
313− script: if (explicitScale)
318+ script: if (explicitScale)
314− explicitlyScaled = true
319+ explicitlyScaled = true
320+ }
315 }
321 }
316 }
322 }
317− }
318
323
319− onLoaded: {
324+ onLoaded: {
320− item.explicitlyScaled = Qt.binding(function() { return explicitlyScaled; })
325+ item.explicitlyScaled = Qt.binding(function() { return explicitlyScaled; })
321− item.model = Qt.binding(function() { return model; })
326+ item.model = Qt.binding(function() { return model; })
322− }
327+ }
323
328
324− MouseArea {
329+ MouseArea {
325− id: mouseArea
330+ id: mouseArea
326
331
327− anchors.fill: parent
332+ anchors.fill: parent
328
333
329− onClicked: {
334+ onClicked: {
330− listView.itemClicked(index, item)
335+ listView.itemClicked(index, item)
331− }
336+ }
332
337
333− onPressAndHold: {
338+ onPressAndHold: {
334− listView.itemPressAndHold(index, item)
339+ listView.itemPressAndHold(index, item)
340+ }
335 }
341 }
336 }
342 }
337 }
343 }
qml/Components/ResponsiveFlowView.qml+3−10
@@ -27,9 +27,8 @@ Item {
27 property alias verticalSpacing: flow.verticalSpacing
27 property alias verticalSpacing: flow.verticalSpacing
28 property alias horizontalSpacing: flow.horizontalSpacing
28 property alias horizontalSpacing: flow.horizontalSpacing
29 property int referenceDelegateWidth
29 property int referenceDelegateWidth
30− property alias firstModel: repeater1.model
30+ property alias model: repeater.model
31− property alias secondModel: repeater2.model
32− property alias delegate: repeater1.delegate
31+ property alias delegate: repeater.delegate
33 readonly property int cellWidth: referenceDelegateWidth + horizontalSpacing
32 readonly property int cellWidth: referenceDelegateWidth + horizontalSpacing
34 readonly property int cellHeight: referenceDelegateWidth + verticalSpacing
33 readonly property int cellHeight: referenceDelegateWidth + verticalSpacing
35 property alias move: flow.move
34 property alias move: flow.move
@@ -69,13 +68,7 @@ Item {
69 property int margin: allocatableVerticalSpace - columns * horizontalSpacing
68 property int margin: allocatableVerticalSpace - columns * horizontalSpacing
70
69
71 Repeater {
70 Repeater {
72− id: repeater1
71+ id: repeater
73− model: (root.model) ? root.model[0] : null
74− }
75− Repeater {
76− id: repeater2
77− model: (root.model) ? root.model[1] : null
78− delegate: repeater1.delegate
79 }
72 }
80 }
73 }
81}
74}
qml/Dash/Apps/RunningApplicationTile.qml+14−24
@@ -23,7 +23,6 @@ import "../../Components/ListItems"
23AbstractButton {
23AbstractButton {
24 id: root
24 id: root
25 property var application
25 property var application
26− property bool __sideStageEnabled: shell.applicationManager.sideStageEnabled
27
26
28 signal requestedApplicationActivation(var application)
27 signal requestedApplicationActivation(var application)
29 signal requestedApplicationTermination(var application)
28 signal requestedApplicationTermination(var application)
@@ -34,7 +33,7 @@ AbstractButton {
34 // To avoid "binding loop" warning
33 // To avoid "binding loop" warning
35 height: shapedApplicationImage.height + labelContainer.height
34 height: shapedApplicationImage.height + labelContainer.height
36
35
37− width: shapedApplicationImage.width
36+ width: shapedApplicationImage.width <= units.gu(11) ? units.gu(11) : height
38
37
39 property bool terminationModeEnabled: false
38 property bool terminationModeEnabled: false
40
39
@@ -51,32 +50,23 @@ AbstractButton {
51 }
50 }
52 }
51 }
53
52
54− function updateScreenshotFromCache() {
55− applicationImage.updateFromCache();
56− }
57−
58− // FIXME: should use UbuntuShape from SDK
53+ UbuntuShape {
59− UbuntuShapeForItem {
60 id: shapedApplicationImage
54 id: shapedApplicationImage
61− anchors {
62− top: parent.top
63− horizontalCenter: parent.horizontalCenter
55+ anchors { top: parent.top; horizontalCenter: parent.horizontalCenter }
64− }
65
56
66− // FIXME: width and height should be defined according to the
67− // application window's aspect ratio.
68− width: (application.stage === ApplicationInfo.MainStage && __sideStageEnabled) ?
69− units.gu(22) : units.gu(11)
57+ height: units.gu(17)
70− height: (__sideStageEnabled) ? units.gu(22) : units.gu(19)
58+ width: applicationImage.width
71 radius: "medium"
59 radius: "medium"
72− image: applicationImage
73− }
74
60
75− ApplicationImage {
61+ image: Image {
76− id: applicationImage
62+ id: applicationImage
77− source: ApplicationManager.findApplication((application) ? application.appId : "")
63+ source: application.screenshot
78− width: shapedApplicationImage.width
64+ // height : width = ss.height : ss.width
79− height: shapedApplicationImage.height
65+ height: shapedApplicationImage.height
66+ fillMode: Image.PreserveAspectCrop
67+ width: Math.min(height, height * sourceSize.width / sourceSize.height)
68+ }
69+
80 }
70 }
81
71
82 UbuntuShape {
72 UbuntuShape {
qml/Dash/Apps/RunningApplicationsGrid.qml+5−20
@@ -18,6 +18,7 @@ import QtQuick 2.0
18import "../../Components"
18import "../../Components"
19
19
20import Ubuntu.Gestures 0.1
20import Ubuntu.Gestures 0.1
21+import Unity.Application 0.1
21
22
22ResponsiveFlowView {
23ResponsiveFlowView {
23 id: root
24 id: root
@@ -25,13 +26,7 @@ ResponsiveFlowView {
25
26
26 signal updateScreenshots
27 signal updateScreenshots
27 property alias enableHeightBehavior: heightBehaviour.enabled
28 property alias enableHeightBehavior: heightBehaviour.enabled
28− property bool enableHeightBehaviorOnNextCreation: firstModel.count + secondModel.count == 0
29+ property bool enableHeightBehaviorOnNextCreation: model.count === 0
29−
30− Connections {
31− target: shell
32− onDashShownChanged: if (shell.dashShown && shell.stageScreenshotsReady) updateScreenshots();
33− onStageScreenshotsReadyChanged: if (shell.dashShown && shell.stageScreenshotsReady) updateScreenshots();
34− }
35
30
36 Behavior on height {
31 Behavior on height {
37 id: heightBehaviour
32 id: heightBehaviour
@@ -40,13 +35,7 @@ ResponsiveFlowView {
40 }
35 }
41
36
42 Connections {
37 Connections {
43− target: root.firstModel
38+ target: root.model
44− onCountChanged: {
45− heightBehaviour.enabled = true;
46− }
47− }
48− Connections {
49− target: root.secondModel
50 onCountChanged: {
39 onCountChanged: {
51 heightBehaviour.enabled = true;
40 heightBehaviour.enabled = true;
52 }
41 }
@@ -85,17 +74,13 @@ ResponsiveFlowView {
85 root.terminationModeEnabled = true
74 root.terminationModeEnabled = true
86 }
75 }
87 onRequestedApplicationTermination: {
76 onRequestedApplicationTermination: {
88− shell.applicationManager.stopApplication(model.appId)
77+ ApplicationManager.stopApplication(model.appId)
89 }
78 }
90 onRequestedApplicationActivation: {
79 onRequestedApplicationActivation: {
91− shell.activateApplication(model.appId)
80+ ApplicationManager.requestFocusApplication(model.appId)
92 }
81 }
93
82
94 terminationModeEnabled: root.terminationModeEnabled
83 terminationModeEnabled: root.terminationModeEnabled
95−
96− Component.onCompleted: {
97− root.updateScreenshots.connect(updateScreenshotFromCache);
98− }
99 }
84 }
100 }
85 }
101
86
qml/Dash/Card.qml+32−12
@@ -16,6 +16,8 @@
16
16
17import QtQuick 2.0
17import QtQuick 2.0
18import Ubuntu.Components 0.1
18import Ubuntu.Components 0.1
19+// For image://albumart and image://thumbnailer image providers
20+import Ubuntu.Thumbnailer 0.1
19
21
20AbstractButton {
22AbstractButton {
21 id: root
23 id: root
@@ -71,23 +73,27 @@ AbstractButton {
71 id: artShape
73 id: artShape
72 radius: "medium"
74 radius: "medium"
73 objectName: "artShape"
75 objectName: "artShape"
74− width: {
75− if (!visible) return 0
76− return image.fillMode === Image.PreserveAspectCrop || aspect < image.aspect ? image.width : height * image.aspect
77− }
78− height: {
79− if (!visible) return 0
80− return image.fillMode === Image.PreserveAspectCrop || aspect > image.aspect ? image.height : width / image.aspect
81− }
82 anchors.horizontalCenter: template && template["card-layout"] === "horizontal" ? undefined : parent.horizontalCenter
76 anchors.horizontalCenter: template && template["card-layout"] === "horizontal" ? undefined : parent.horizontalCenter
83 anchors.left: template && template["card-layout"] === "horizontal" ? parent.left : undefined
77 anchors.left: template && template["card-layout"] === "horizontal" ? parent.left : undefined
84 visible: cardData && cardData["art"] || false
78 visible: cardData && cardData["art"] || false
85
79
86− property real aspect: components !== undefined ? components["art"]["aspect-ratio"] : 1
80+ readonly property real aspect: components !== undefined ? components["art"]["aspect-ratio"] : 1
81+ readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect
82+
83+ Component.onCompleted: updateWidthHeightBindings();
84+ onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings();
85+
86+ function updateWidthHeightBindings() {
87+ if (aspectSmallerThanImageAspect) {
88+ width = Qt.binding(function() { return !visible ? 0 : image.width });
89+ height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect });
90+ } else {
91+ width = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.width : height * image.aspect });
92+ height = Qt.binding(function() { return !visible ? 0 : image.height });
93+ }
94+ }
87
95
88 image: Image {
96 image: Image {
89− width: template && template["card-layout"] === "horizontal" ? height * artShape.aspect : root.width
90− height: template && template["card-layout"] === "horizontal" ? header.height : width / artShape.aspect
91 objectName: "artImage"
97 objectName: "artImage"
92 source: cardData && cardData["art"] || ""
98 source: cardData && cardData["art"] || ""
93 cache: true
99 cache: true
@@ -96,7 +102,21 @@ AbstractButton {
96 //sourceSize.height: height > width ? height : 0
102 //sourceSize.height: height > width ? height : 0
97 fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop
103 fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop
98
104
99− property real aspect: implicitWidth / implicitHeight
105+ readonly property real aspect: implicitWidth / implicitHeight
106+ readonly property bool isHorizontal: template && template["card-layout"] === "horizontal"
107+
108+ Component.onCompleted: updateWidthHeightBindings();
109+ onIsHorizontalChanged: updateWidthHeightBindings();
110+
111+ function updateWidthHeightBindings() {
112+ if (isHorizontal) {
113+ width = Qt.binding(function() { return height * artShape.aspect });
114+ height = Qt.binding(function() { return header.height });
115+ } else {
116+ width = Qt.binding(function() { return root.width });
117+ height = Qt.binding(function() { return width / artShape.aspect });
118+ }
119+ }
100 }
120 }
101 }
121 }
102
122
qml/Dash/DashApps.qml+4−5
@@ -17,6 +17,7 @@
17import QtQuick 2.0
17import QtQuick 2.0
18import Ubuntu.Components 0.1
18import Ubuntu.Components 0.1
19import Utils 0.1
19import Utils 0.1
20+import Unity.Application 0.1
20import "../Components"
21import "../Components"
21import "../Components/ListItems"
22import "../Components/ListItems"
22import "Apps"
23import "Apps"
@@ -25,17 +26,15 @@ GenericScopeView {
25 id: scopeView
26 id: scopeView
26 objectName: "DashApps"
27 objectName: "DashApps"
27
28
28− // FIXME: a way to aggregate these models would be ideal
29+ property var runningApps: ApplicationManager
29− property var mainStageApplicationsModel: shell.applicationManager.mainStageApplications
30− property var sideStageApplicationModel: shell.applicationManager.sideStageApplications
31
30
32 QtObject {
31 QtObject {
33 id: countObject
32 id: countObject
34− property int count: scopeView.scope.searchQuery.length == 0 ? (mainStageApplicationsModel.count + sideStageApplicationModel.count) : 0
33+ property int count: scopeView.scope.searchQuery.length == 0 ? scopeView.runningApps.count : 0
35 }
34 }
36
35
37 onScopeChanged: {
36 onScopeChanged: {
38 scopeView.scope.categories.addSpecialCategory("running.apps.category", i18n.tr("Recent"), "", "{ \"template\": { \"category-layout\": \"running-apps\" } }", countObject);
37 scopeView.scope.categories.addSpecialCategory("running.apps.category", i18n.tr("Recent"), "", "{ \"template\": { \"category-layout\": \"running-apps\" } }", countObject);
39− enableHeightBehaviorOnNextCreation = (mainStageApplicationsModel.count + sideStageApplicationModel.count == 0)
38+ enableHeightBehaviorOnNextCreation = scopeView.runningApps.count === 0
40 }
39 }
41}
40}
qml/Dash/GenericScopeView.qml+2−11
@@ -18,6 +18,7 @@ import QtQuick 2.0
18import Ubuntu.Components 0.1
18import Ubuntu.Components 0.1
19import Utils 0.1
19import Utils 0.1
20import Unity 0.2
20import Unity 0.2
21+import Unity.Application 0.1
21import "../Components"
22import "../Components"
22import "../Components/ListItems" as ListItems
23import "../Components/ListItems" as ListItems
23
24
@@ -38,11 +39,8 @@ FocusScope {
38 property bool enableHeightBehaviorOnNextCreation: false
39 property bool enableHeightBehaviorOnNextCreation: false
39 property var categoryView: categoryView
40 property var categoryView: categoryView
40
41
41− // FIXME delay the search so that daemons have time to settle, note that
42− // removing this will break ScopeView::test_changeScope
43 onScopeChanged: {
42 onScopeChanged: {
44 if (scope) {
43 if (scope) {
45− timer.restart();
46 scope.activateApplication.connect(activateApp);
44 scope.activateApplication.connect(activateApp);
47 }
45 }
48 }
46 }
@@ -57,12 +55,6 @@ FocusScope {
57 value: isCurrent && !previewListView.open
55 value: isCurrent && !previewListView.open
58 }
56 }
59
57
60− Timer {
61− id: timer
62− interval: 2000
63− onTriggered: scope.searchQuery = ""
64− }
65−
66 SortFilterProxyModel {
58 SortFilterProxyModel {
67 id: categoryFilter
59 id: categoryFilter
68 model: scope ? scope.categories : null
60 model: scope ? scope.categories : null
@@ -167,8 +159,7 @@ FocusScope {
167 if (source.toString().indexOf("Apps/RunningApplicationsGrid.qml") != -1) {
159 if (source.toString().indexOf("Apps/RunningApplicationsGrid.qml") != -1) {
168 // TODO: this is still a kludge :D Ideally add some kind of hook so that we
160 // TODO: this is still a kludge :D Ideally add some kind of hook so that we
169 // can do this from DashApps.qml or think a better way that needs no special casing
161 // can do this from DashApps.qml or think a better way that needs no special casing
170− item.firstModel = Qt.binding(function() { return mainStageApplicationsModel })
171− item.secondModel = Qt.binding(function() { return sideStageApplicationModel })
162+ item.model = Qt.binding(function() { return runningApps; })
172 item.canEnableTerminationMode = Qt.binding(function() { return scopeView.isCurrent })
163 item.canEnableTerminationMode = Qt.binding(function() { return scopeView.isCurrent })
173 } else {
164 } else {
174 item.model = Qt.binding(function() { return results })
165 item.model = Qt.binding(function() { return results })
qml/Dash/Previews/PreviewVideoPlayback.qml+1
@@ -16,6 +16,7 @@
16
16
17import QtQuick 2.0
17import QtQuick 2.0
18import Ubuntu.Components 0.1
18import Ubuntu.Components 0.1
19+import Ubuntu.Thumbnailer 0.1
19import "../../Components"
20import "../../Components"
20
21
21/*! \brief Preview widget for video.
22/*! \brief Preview widget for video.
qml/Launcher/Launcher.qml+2−1
@@ -29,7 +29,8 @@ Item {
29 property int dragAreaWidth: units.gu(1)
29 property int dragAreaWidth: units.gu(1)
30 property int minimizeDistance: units.gu(26)
30 property int minimizeDistance: units.gu(26)
31 property real progress: dragArea.dragging && dragArea.touchX > panelWidth ?
31 property real progress: dragArea.dragging && dragArea.touchX > panelWidth ?
32− (width * (dragArea.touchX-panelWidth) / (width - panelWidth)) : 0
32+ (width * (dragArea.touchX-panelWidth) / (width - panelWidth)) :
33+ (dragArea.dragging ? 0.001 : 0)
33
34
34 readonly property bool shown: panel.x > -panel.width
35 readonly property bool shown: panel.x > -panel.width
35
36
qml/Panel/Panel.qml+1−1
@@ -62,7 +62,7 @@ Item {
62 height: __panelMinusSeparatorLineHeight
62 height: __panelMinusSeparatorLineHeight
63 y: 0
63 y: 0
64
64
65− Behavior on y { StandardAnimation {duration: 500} }
65+ Behavior on y { StandardAnimation { duration: 500 } }
66 }
66 }
67
67
68 PanelSeparatorLine {
68 PanelSeparatorLine {
qml/Shell.qml+190−280
@@ -30,7 +30,6 @@ import "Panel"
30import "Hud"
30import "Hud"
31import "Components"
31import "Components"
32import "Bottombar"
32import "Bottombar"
33−import "SideStage"
34import "Notifications"
33import "Notifications"
35import Unity.Notifications 1.0 as NotificationBackend
34import Unity.Notifications 1.0 as NotificationBackend
36
35
@@ -40,20 +39,24 @@ BasicShell {
40 readonly property real panelHeight: panel.panelHeight
39 readonly property real panelHeight: panel.panelHeight
41
40
42 property bool dashShown: dash.shown
41 property bool dashShown: dash.shown
43− property bool stageScreenshotsReady: {
42+
44− if (sideStage.shown) {
43+ property bool sideStageEnabled: shell.width >= units.gu(100)
45− if (mainStage.applications.count > 0) {
44+ readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
46− return mainStage.usingScreenshots && sideStage.usingScreenshots;
45+
47− } else {
46+ function activateApplication(appId) {
48− return sideStage.usingScreenshots;
47+ if (ApplicationManager.findApplication(appId)) {
48+ ApplicationManager.requestFocusApplication(appId);
49+ stages.show(true);
50+ if (stages.locked && ApplicationManager.focusedApplicationId == appId) {
51+ applicationsDisplayLoader.item.select(appId);
49 }
52 }
50 } else {
53 } else {
51− return mainStage.usingScreenshots;
54+ var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
55+ ApplicationManager.startApplication(appId, execFlags);
56+ stages.show(false);
52 }
57 }
53 }
58 }
54
59
55− property var applicationManager: ApplicationManagerWrapper {}
56−
57 Binding {
60 Binding {
58 target: LauncherModel
61 target: LauncherModel
59 property: "applicationManager"
62 property: "applicationManager"
@@ -62,47 +65,6 @@ BasicShell {
62
65
63 Component.onCompleted: {
66 Component.onCompleted: {
64 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
67 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
65−
66− applicationManager.sideStageEnabled = Qt.binding(function() { return sideStage.enabled })
67−
68− // FIXME: if application focused before shell starts, shell draws on top of it only.
69− // We should detect already running applications on shell start and bring them to the front.
70− applicationManager.unfocusCurrentApplication();
71− }
72−
73− readonly property bool applicationFocused: !!applicationManager.mainStageFocusedApplication
74− || !!applicationManager.sideStageFocusedApplication
75− // Used for autopilot testing.
76− readonly property string currentFocusedAppId: ApplicationManager.focusedApplicationId
77−
78− readonly property bool fullscreenMode: {
79− if (mainStage.usingScreenshots) { // Window Manager animating so want to re-evaluate fullscreen mode
80− return mainStage.switchingFromFullscreenToFullscreen;
81− } else if (applicationManager.mainStageFocusedApplication) {
82− return applicationManager.mainStageFocusedApplication.fullscreen;
83− } else {
84− return false;
85− }
86− }
87−
88− function activateApplication(appId, argument) {
89− if (applicationManager) {
90− // For newly started applications, as it takes them time to draw their first frame
91− // we add a delay before we hide the animation screenshots to compensate.
92− var addDelay = !applicationManager.getApplicationFromDesktopFile(appId);
93−
94− var application;
95− application = applicationManager.activateApplication(appId, argument);
96− if (application == null) {
97− return;
98− }
99− if (application.stage == ApplicationInfo.MainStage || !sideStage.enabled) {
100− mainStage.activateApplication(appId, addDelay);
101− } else {
102− sideStage.activateApplication(appId, addDelay);
103− }
104− stages.show();
105− }
106 }
68 }
107
69
108 GSettings {
70 GSettings {
@@ -112,279 +74,224 @@ BasicShell {
112 backgroundSource: backgroundSettings.pictureUri
74 backgroundSource: backgroundSettings.pictureUri
113
75
114 Item {
76 Item {
115− id: underlay
77+ id: underlayClipper
116− objectName: "underlay"
117 anchors.fill: parent
78 anchors.fill: parent
79+ anchors.rightMargin: stages.overlayWidth
80+ clip: stages.overlayMode && !stages.painting
118
81
119− // Whether the underlay is fully covered by opaque UI elements.
82+ InputFilterArea {
120− property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
83+ anchors.fill: parent
121−
84+ blockInput: parent.clip
122− readonly property bool applicationRunning: ((mainStage.applications && mainStage.applications.count > 0)
85+ }
123− || (sideStage.applications && sideStage.applications.count > 0))
124−
125− // Whether the user should see the topmost application surface (if there's one at all).
126− readonly property bool applicationSurfaceShouldBeSeen: applicationRunning && !stages.fullyHidden
127− && !mainStage.usingScreenshots // but want sideStage animating over app surface
128
86
87+ Item {
88+ id: underlay
89+ objectName: "underlay"
90+ anchors.fill: parent
91+ anchors.rightMargin: -parent.anchors.rightMargin
129
92
93+ // Whether the underlay is fully covered by opaque UI elements.
94+ property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
130
95
131− // NB! Application surfaces are stacked behing the shell one. So they can only be seen by the user
96+ // Whether the user should see the topmost application surface (if there's one at all).
132− // through the translucent parts of the shell surface.
133− visible: !fullyCovered && !applicationSurfaceShouldBeSeen
97+ readonly property bool applicationSurfaceShouldBeSeen: stages.shown && !stages.painting && !stages.overlayMode
134
98
135− Rectangle {
99+ // NB! Application surfaces are stacked behind the shell one. So they can only be seen by the user
136− anchors.fill: parent
100+ // through the translucent parts of the shell surface.
137− color: "black"
101+ visible: !fullyCovered && !applicationSurfaceShouldBeSeen
138− opacity: dash.disappearingAnimationProgress
139− }
140
102
141− Image {
103+ CrossFadeImage {
142− anchors.fill: dash
104+ id: backgroundImage
143− source: shell.width > shell.height ? "Dash/graphics/paper_landscape.png" : "Dash/graphics/paper_portrait.png"
105+ objectName: "backgroundImage"
144− fillMode: Image.PreserveAspectCrop
145− horizontalAlignment: Image.AlignRight
146− verticalAlignment: Image.AlignTop
147− }
148
106
149− Dash {
107+ anchors.fill: parent
150− id: dash
108+ source: shell.background
151− objectName: "dash"
109+ fillMode: Image.PreserveAspectCrop
152−
153− hides: [stages, launcher, panel.indicators]
154− shown: disappearingAnimationProgress !== 1.0
155− enabled: disappearingAnimationProgress === 0.0 && edgeDemo.dashEnabled
156− // FIXME: unfocus all applications when going back to the dash
157− onEnabledChanged: {
158− if (enabled) {
159− shell.applicationManager.unfocusCurrentApplication()
160− }
161 }
110 }
162
111
163− anchors {
112+ Rectangle {
164− fill: parent
113+ anchors.fill: parent
165− topMargin: panel.panelHeight
114+ color: "black"
115+ opacity: dash.disappearingAnimationProgress
166 }
116 }
167
117
168− contentScale: 1.0 - 0.2 * disappearingAnimationProgress
118+ Image {
169− opacity: 1.0 - disappearingAnimationProgress
119+ anchors.fill: dash
170− property real disappearingAnimationProgress: stagesOuterContainer.showProgress
120+ source: shell.width > shell.height ? "Dash/graphics/paper_landscape.png" : "Dash/graphics/paper_portrait.png"
121+ fillMode: Image.PreserveAspectCrop
122+ horizontalAlignment: Image.AlignRight
123+ verticalAlignment: Image.AlignTop
124+ }
171
125
172− // FIXME: only necessary because stagesOuterContainer.showProgress
126+ Dash {
173− // is not animated
127+ id: dash
174− Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
128+ objectName: "dash"
175− }
176− }
177
129
178− Item {
130+ hides: [stages, launcher, panel.indicators]
179− id: stagesOuterContainer
131+ shown: disappearingAnimationProgress !== 1.0
132+ enabled: disappearingAnimationProgress === 0.0 && edgeDemo.dashEnabled
180
133
181− width: parent.width
134+ anchors {
182− height: parent.height
135+ fill: parent
183− x: launcher.progress
136+ topMargin: panel.panelHeight
184− Behavior on x {SmoothedAnimation{velocity: 600}}
137+ }
185
138
139+ contentScale: 1.0 - 0.2 * disappearingAnimationProgress
140+ opacity: 1.0 - disappearingAnimationProgress
141+ property real disappearingAnimationProgress: {
142+ if (stages.overlayMode) {
143+ return 0;
144+ } else {
186− property real showProgress:
145+ return stages.showProgress
187− MathUtils.clamp(1 - (x + stages.x) / shell.width, 0, 1)
146+ }
147+ }
188
148
189− Showable {
149+ // FIXME: only necessary because stages.showProgress is not animated
190− id: stages
150+ Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
191− objectName: "stages"
151+ }
152+ }
153+ }
192
154
193− x: width
155+ EdgeDragArea {
156+ id: stagesDragHandle
157+ direction: Direction.Leftwards
194
158
195− property bool fullyShown: shown && x == 0 && parent.x == 0
159+ anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
196− property bool fullyHidden: !shown && x == width
160+ width: shell.edgeSize
197− hides: [panel.indicators]
198− shown: false
199− opacity: 1.0
200− showAnimation: StandardAnimation { property: "x"; duration: 350; to: 0; easing.type: Easing.OutCubic }
201− hideAnimation: StandardAnimation { property: "x"; duration: 350; to: width; easing.type: Easing.OutCubic }
202
161
203− width: parent.width
162+ property real progress: stages.width
204− height: parent.height
205
163
206− // close the stages when no focused application remains
164+ onTouchXChanged: {
207− Connections {
165+ if (status == DirectionalDragArea.Recognized) {
208− target: shell.applicationManager
166+ if (ApplicationManager.count == 0) {
209− onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
167+ progress = Math.max(stages.width - stagesDragHandle.width + touchX, stages.width * .3)
210− onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
168+ } else {
211− ignoreUnknownSignals: true
169+ progress = stages.width - stagesDragHandle.width + touchX
170+ }
212 }
171 }
172+ }
213
173
214− function closeIfNoApplications() {
174+ onDraggingChanged: {
215− if (!shell.applicationManager.mainStageFocusedApplication
175+ if (!dragging) {
216− && !shell.applicationManager.sideStageFocusedApplication
176+ if (ApplicationManager.count > 0 && progress < stages.width - units.gu(10)) {
217− && shell.applicationManager.mainStageApplications.count == 0
218− && shell.applicationManager.sideStageApplications.count == 0) {
219− stages.hide();
177+ stages.show(true)
220 }
178 }
179+ stagesDragHandle.progress = stages.width;
221 }
180 }
181+ }
182+ }
222
183
223− // show the stages when an application gets the focus
184+ Item {
224− Connections {
185+ id: stages
225− target: shell.applicationManager
186+ objectName: "stages"
226− onMainStageFocusedApplicationChanged: {
187+ width: parent.width
227− if (shell.applicationManager.mainStageFocusedApplication) {
188+ height: parent.height
228− mainStage.show();
189+
229− stages.show();
190+ x: {
230− }
191+ if (shown) {
231− }
192+ if (overlayMode || locked) {
232− onSideStageFocusedApplicationChanged: {
193+ return 0;
233− if (shell.applicationManager.sideStageFocusedApplication) {
234− sideStage.show();
235− stages.show();
236− }
237 }
194 }
238− ignoreUnknownSignals: true
195+ return launcher.progress
196+ } else {
197+ return stagesDragHandle.progress
239 }
198 }
199+ }
240
200
241− Stage {
201+ Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.FastDuration } }
242− id: mainStage
243
202
244− anchors.fill: parent
203+ property bool shown: false
245− fullyShown: stages.fullyShown
246− fullyHidden: stages.fullyHidden
247− shouldUseScreenshots: !fullyShown
248− rightEdgeEnabled: !sideStage.enabled
249−
250− applicationManager: shell.applicationManager
251− rightEdgeDraggingAreaWidth: shell.edgeSize
252− normalApplicationY: shell.panelHeight
253−
254− shown: true
255− function show() {
256− stages.show();
257− }
258− function hide() {
259− }
260
204
261− // FIXME: workaround the fact that focusing a main stage application
205+ property real showProgress: overlayMode ? 0 : MathUtils.clamp(1 - x / shell.width, 0, 1)
262− // raises its surface on top of all other surfaces including the ones
206+
263− // that belong to side stage applications.
207+ property bool fullyShown: x == 0
264− onFocusedApplicationChanged: {
208+ property bool fullyHidden: x == width
265− if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
209+
266− shell.applicationManager.focusApplication(sideStage.focusedApplication);
210+ property bool painting: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.painting : false
267− }
211+ property bool fullscreen: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.fullscreen : false
268− }
212+ property bool overlayMode: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayMode : false
213+ property int overlayWidth: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayWidth : false
214+ property bool locked: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.locked : false
215+
216+ function show(focusApp) {
217+ shown = true;
218+ panel.indicators.hide();
219+ if (!ApplicationManager.focusedApplicationId && ApplicationManager.count > 0 && focusApp) {
220+ ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
269 }
221 }
222+ }
223+
224+ function hide() {
225+ shown = false;
226+ if (ApplicationManager.focusedApplicationId) {
227+ ApplicationManager.unfocusCurrentApplication();
228+ }
229+ }
270
230
271− SideStage {
231+ Connections {
272− id: sideStage
232+ target: ApplicationManager
273
233
274− applicationManager: shell.applicationManager
234+ onFocusRequested: {
275− rightEdgeDraggingAreaWidth: shell.edgeSize
235+ stages.show(true);
276− normalApplicationY: shell.panelHeight
236+ }
277
237
278− onShownChanged: {
238+ onFocusedApplicationIdChanged: {
279− if (!shown && mainStage.applications.count == 0) {
239+ if (ApplicationManager.focusedApplicationId.length > 0) {
240+ stages.show(false);
241+ } else {
242+ if (!stages.overlayMode) {
280 stages.hide();
243 stages.hide();
281 }
244 }
282 }
245 }
283− // FIXME: when hiding the side stage, refocus the main stage
284− // application so that it goes in front of the side stage
285− // application and hides it
286− onFullyShownChanged: {
287− if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
288− shell.applicationManager.focusApplication(mainStage.focusedApplication);
289− }
290− }
246+ }
291
247
292− enabled: shell.width >= units.gu(100)
248+ onApplicationAdded: {
293− visible: enabled
294− fullyShown: stages.fullyShown && shown
295− && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
296− shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
297−
298− available: enabled
299− hides: [launcher, panel.indicators]
300− shown: false
249+ stages.show(false);
301− showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
302− hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
303−
304− width: units.gu(40)
305− height: stages.height
306− handleExpanded: sideStageRevealer.pressed
307 }
250 }
308
251
309− Revealer {
252+ onApplicationRemoved: {
310− id: sideStageRevealer
253+ if (ApplicationManager.focusedApplicationId.length == 0) {
311−
254+ stages.hide();
312− enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
255+ }
313− && sideStage.available
314− direction: Qt.RightToLeft
315− openedValue: parent.width - sideStage.width
316− hintDisplacement: units.gu(3)
317− /* The size of the sidestage handle needs to be bigger than the
318− typical size used for edge detection otherwise it is really
319− hard to grab.
320− */
321− handleSize: sideStage.shown ? units.gu(4) : shell.edgeSize
322− closedValue: parent.width + sideStage.handleSizeCollapsed
323− target: sideStage
324− x: parent.width - width
325− width: sideStage.width + handleSize * 0.7
326− height: sideStage.height
327− orientation: Qt.Horizontal
328 }
256 }
257+ }
329
258
330− DragHandle {
259+ Binding {
331− id: stagesDragHandle
260+ target: ApplicationManager
261+ property: "suspended"
262+ value: !SessionManager.active
263+ }
332
264
333− anchors.top: parent.top
265+ Loader {
334− anchors.bottom: parent.bottom
266+ id: applicationsDisplayLoader
335− anchors.right: parent.left
267+ anchors.fill: parent
336
268
337− width: shell.edgeSize
269+ source: shell.sideStageEnabled ? "Stages/StageWithSideStage.qml" : "Stages/PhoneStage.qml"
338− direction: Direction.Leftwards
339− enabled: edgeDemo.dashEnabled
340− property bool haveApps: mainStage.applications.count > 0 || sideStage.applications.count > 0
341
270
342− maxTotalDragDistance: haveApps ? parent.width : parent.width * 0.7
271+ Binding {
343− // Make autocompletion impossible when !haveApps
272+ target: applicationsDisplayLoader.item
344− edgeDragEvaluator.minDragDistance: haveApps ? maxTotalDragDistance * 0.1 : Number.MAX_VALUE
273+ property: "moving"
274+ value: !stages.fullyShown
275+ }
276+ Binding {
277+ target: applicationsDisplayLoader.item
278+ property: "shown"
279+ value: stages.shown
280+ }
281+ Binding {
282+ target: applicationsDisplayLoader.item
283+ property: "dragAreaWidth"
284+ value: shell.edgeSize
345 }
285 }
346 }
286 }
347 }
287 }
348
288
349 InputFilterArea {
289 InputFilterArea {
350 anchors.fill: parent
290 anchors.fill: parent
351− blockInput: !applicationFocused || launcher.shown
291+ blockInput: ApplicationManager.focusedApplicationId.length === 0 || launcher.shown
352 || panel.indicators.shown || hud.shown
292 || panel.indicators.shown || hud.shown
353 }
293 }
354
294
355− Connections {
356− id: sessionConnection
357− target: SessionManager
358−
359− property var previousMainApp: null
360− property var previousSideApp: null
361−
362− function removeApplicationFocus() {
363− sessionConnection.previousMainApp = applicationManager.mainStageFocusedApplication;
364− sessionConnection.previousSideApp = applicationManager.sideStageFocusedApplication;
365− applicationManager.unfocusCurrentApplication();
366− }
367−
368− function restoreApplicationFocus() {
369− if (sessionConnection.previousMainApp) {
370− applicationManager.focusApplication(sessionConnection.previousMainApp);
371− sessionConnection.previousMainApp = null;
372− }
373− if (sessionConnection.previousSideApp) {
374− applicationManager.focusApplication(sessionConnection.previousSideApp);
375− sessionConnection.previousSideApp = null;
376− }
377− }
378−
379− onActiveChanged: {
380− if (SessionManager.active) {
381− restoreApplicationFocus();
382− } else {
383− removeApplicationFocus();
384− }
385− }
386− }
387−
388 Connections {
295 Connections {
389 id: powerConnection
296 id: powerConnection
390 target: Powerd
297 target: Powerd
@@ -425,7 +332,9 @@ BasicShell {
425 available: edgeDemo.panelEnabled
332 available: edgeDemo.panelEnabled
426 contentEnabled: edgeDemo.panelContentEnabled
333 contentEnabled: edgeDemo.panelContentEnabled
427 }
334 }
428− fullscreenMode: shell.fullscreenMode
335+ property string focusedAppId: ApplicationManager.focusedApplicationId
336+ property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
337+ fullscreenMode: focusedApplication && stages.fullscreen
429 searchVisible: dash.shown && dash.searchable
338 searchVisible: dash.shown && dash.searchable
430
339
431 InputFilterArea {
340 InputFilterArea {
@@ -451,9 +360,8 @@ BasicShell {
451 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
360 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
452
361
453 Connections {
362 Connections {
454− target: shell.applicationManager
363+ target: ApplicationManager
455− onMainStageFocusedApplicationChanged: hud.hide()
364+ onFocusedApplicationIdChanged: hud.hide()
456− onSideStageFocusedApplicationChanged: hud.hide()
457 }
365 }
458 }
366 }
459
367
@@ -478,7 +386,7 @@ BasicShell {
478 theHud: hud
386 theHud: hud
479 anchors.fill: parent
387 anchors.fill: parent
480 enabled: hud.available
388 enabled: hud.available
481− applicationIsOnForeground: applicationFocused
389+ applicationIsOnForeground: ApplicationManager.focusedApplicationId
482 }
390 }
483
391
484 InputFilterArea {
392 InputFilterArea {
@@ -509,9 +417,11 @@ BasicShell {
509 }
417 }
510
418
511 onDash: {
419 onDash: {
512− if (stages.shown) {
420+ if (stages.shown && !stages.overlayMode) {
513− stages.hide();
421+ if (!stages.locked) {
422+ stages.hide();
514− launcher.hide();
423+ launcher.hide();
424+ }
515 }
425 }
516 }
426 }
517 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
427 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
@@ -594,14 +504,14 @@ BasicShell {
594 anchors.bottom: parent.bottom
504 anchors.bottom: parent.bottom
595 anchors.left: parent.left
505 anchors.left: parent.left
596 anchors.right: parent.right
506 anchors.right: parent.right
597− height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
507+ height: ApplicationManager.keyboardVisible ? ApplicationManager.keyboardHeight : 0
598
508
599− enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
509+ enabled: ApplicationManager.keyboardVisible
600 }
510 }
601
511
602 Label {
512 Label {
603 anchors.centerIn: parent
513 anchors.centerIn: parent
604− visible: applicationManager.fake
514+ visible: ApplicationManager.fake ? ApplicationManager.fake : false
605 text: "EARLY ALPHA\nNOT READY FOR USE"
515 text: "EARLY ALPHA\nNOT READY FOR USE"
606 color: "lightgrey"
516 color: "lightgrey"
607 opacity: 0.2
517 opacity: 0.2
qml/SideStage/SideStage.qmldeleted−73
@@ -1,73 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import Unity.Application 0.1
19−import Ubuntu.Components 0.1
20−import "../Components"
21−
22−Stage {
23− id: stage
24−
25− type: ApplicationInfo.SideStage
26− property real handleSizeCollapsed: units.gu(2.5)
27− property real handleSizeExpanded: units.gu(4)
28− property bool handleExpanded
29−
30− /* FIXME: workaround so that when a main stage app goes fullscreen
31− the sidestage's top is not transparent. Proper fix would be to
32− resize the side stage app.
33− */
34− Rectangle {
35− id: backgroundBeneathPanel
36− anchors {
37− top: parent.top
38− left: parent.left
39− right: parent.right
40− }
41− height: shell.panelHeight
42− color: background.color
43− z: -1
44− }
45−
46− Rectangle {
47− id: background
48− anchors.fill: parent
49− color: "#2c2924"
50− z: -1
51− visible: stage.usingScreenshots
52− }
53−
54− SidestageHandle {
55− id: handle
56− objectName: "sideStageHandle"
57−
58− anchors {
59− top: parent.top
60− bottom: parent.bottom
61− left: parent.left
62− leftMargin: -width
63− }
64− width: handleExpanded ? handleSizeExpanded : handleSizeCollapsed
65− Behavior on width { NumberAnimation { easing.type: Easing.OutQuart} }
66− z: -1
67− }
68−
69− InputFilterArea {
70− anchors.fill: handle
71− blockInput: visible
72− }
73−}
qml/Stages/PhoneStage.qmlnew file+499
@@ -0,0 +1,499 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import Ubuntu.Components 0.1
19+import Ubuntu.Gestures 0.1
20+import Unity.Application 0.1
21+import Utils 0.1
22+import "../Components"
23+
24+Item {
25+ id: root
26+
27+ // Controls to be set from outside
28+ property bool shown: false
29+ property bool moving: false
30+ property int dragAreaWidth
31+
32+ // State information propagated to the outside
33+ readonly property bool painting: mainScreenshotImage.visible || fadeInScreenshotImage.visible || appSplash.visible || spreadView.visible
34+ property bool fullscreen: priv.focusedApplication ? priv.focusedApplication.fullscreen : false
35+ property bool locked: spreadView.visible
36+
37+ // Not used for PhoneStage, only useful for SideStage and similar
38+ property bool overlayMode: false
39+ property int overlayWidth: 0
40+
41+ function select(appId) {
42+ spreadView.snapTo(priv.indexOf(appId))
43+ }
44+
45+ onMovingChanged: {
46+ if (moving) {
47+ if (ApplicationManager.focusedApplicationId) {
48+ priv.requestNewScreenshot();
49+ } else {
50+ mainScreenshotImage.anchors.leftMargin = 0;
51+ mainScreenshotImage.source = ApplicationManager.get(0).screenshot;
52+ mainScreenshotImage.visible = true;
53+ }
54+ } else {
55+ mainScreenshotImage.visible = false;
56+ }
57+ }
58+
59+ Connections {
60+ target: ApplicationManager
61+
62+ onFocusRequested: {
63+ if (spreadView.visible) {
64+ spreadView.snapTo(priv.indexOf(appId));
65+ } else {
66+ priv.switchToApp(appId);
67+ }
68+ }
69+
70+ onFocusedApplicationIdChanged: {
71+ if (ApplicationManager.focusedApplicationId.length > 0) {
72+ if (priv.secondApplicationStarting || priv.applicationStarting) {
73+ appSplashTimer.restart();
74+ } else {
75+ var application = priv.focusedApplication;
76+ root.fullscreen = application.fullscreen;
77+ mainScreenshotImage.source = application.screenshot;
78+ }
79+ } else {
80+ spreadView.selectedIndex = -1;
81+ spreadView.phase = 0;
82+ spreadView.contentX = -spreadView.shift;
83+ }
84+ }
85+
86+ onApplicationAdded: {
87+ if (!priv.focusedApplication) {
88+ mainScreenshotImage.source = "";
89+ mainScreenshotImage.visible = false;
90+ priv.applicationStarting = true;
91+ } else {
92+ mainScreenshotImage.source = "";
93+ priv.newFocusedAppId = appId;
94+ priv.secondApplicationStarting = true;
95+ priv.requestNewScreenshot();
96+ }
97+
98+ if (spreadView.visible) {
99+ spreadView.snapTo(0);
100+ }
101+ }
102+
103+ onApplicationRemoved: {
104+ if (ApplicationManager.count == 0) {
105+ mainScreenshotImage.source = ""
106+ mainScreenshotImage.visible = false;
107+ } else {
108+ mainScreenshotImage.source = ApplicationManager.get(0).screenshot;
109+ }
110+ }
111+ }
112+
113+ QtObject {
114+ id: priv
115+
116+ property string focusedAppId: ApplicationManager.focusedApplicationId
117+ property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
118+ property url focusedScreenshot: focusedApplication ? focusedApplication.screenshot : ""
119+
120+ property bool waitingForScreenshot: false
121+
122+ property bool applicationStarting: false
123+ property bool secondApplicationStarting: false
124+
125+ property string newFocusedAppId
126+
127+ onFocusedScreenshotChanged: {
128+ if (root.moving && priv.waitingForScreenshot) {
129+ mainScreenshotImage.anchors.leftMargin = 0;
130+ mainScreenshotImage.source = priv.focusedScreenshot
131+ mainScreenshotImage.visible = true;
132+ } else if (priv.secondApplicationStarting && priv.waitingForScreenshot) {
133+ applicationSwitchingAnimation.start();
134+ }
135+ waitingForScreenshot = false;
136+ }
137+
138+ function requestNewScreenshot() {
139+ waitingForScreenshot = true;
140+ ApplicationManager.updateScreenshot(focusedAppId);
141+ }
142+
143+ function switchToApp(appId) {
144+ if (priv.focusedAppId) {
145+ priv.newFocusedAppId = appId;
146+ root.fullscreen = ApplicationManager.findApplication(appId).fullscreen;
147+ applicationSwitchingAnimation.start();
148+ } else {
149+ ApplicationManager.focusApplication(appId);
150+ }
151+ }
152+
153+ function indexOf(appId) {
154+ for (var i = 0; i < ApplicationManager.count; i++) {
155+ if (ApplicationManager.get(i).appId == appId) {
156+ return i;
157+ }
158+ }
159+ return -1;
160+ }
161+
162+ }
163+
164+ // FIXME: the signal connections seems to get lost.
165+ Connections {
166+ target: priv.focusedApplication
167+ onScreenshotChanged: priv.focusedScreenshot = priv.focusedApplication.screenshot
168+ }
169+ Binding {
170+ target: root
171+ property: "fullscreen"
172+ value: priv.focusedApplication ? priv.focusedApplication.fullscreen : false
173+ }
174+
175+ Timer {
176+ id: appSplashTimer
177+ // FIXME: We really need to show something meaningful in the app surface instead of guessing
178+ // when it might be ready
179+ interval: 500
180+ repeat: false
181+ onTriggered: {
182+ priv.applicationStarting = false;
183+ priv.secondApplicationStarting = false;
184+ }
185+ }
186+
187+ SequentialAnimation {
188+ id: applicationSwitchingAnimation
189+ // setup
190+ PropertyAction { target: mainScreenshotImage; property: "anchors.leftMargin"; value: 0 }
191+ PropertyAction { target: mainScreenshotImage; property: "source"; value: priv.focusedScreenshot }
192+ PropertyAction { targets: [mainScreenshotImage, fadeInScreenshotImage]; property: "visible"; value: true }
193+ PropertyAction { target: fadeInScreenshotImage; property: "source"; value: {
194+ var newFocusedApp = ApplicationManager.findApplication(priv.newFocusedAppId);
195+ return newFocusedApp ? newFocusedApp.screenshot : "" }
196+ }
197+ PropertyAction { target: fadeInScreenshotImage; property: "opacity"; value: 0 }
198+ PropertyAction { target: fadeInScreenshotImage; property: "scale"; value: .8 }
199+
200+
201+ // The actual animation
202+ ParallelAnimation {
203+ UbuntuNumberAnimation { target: mainScreenshotImage; property: "anchors.leftMargin"; to: root.width; duration: UbuntuAnimation.SlowDuration }
204+ UbuntuNumberAnimation { target: fadeInScreenshotImage; properties: "opacity,scale"; to: 1; duration: UbuntuAnimation.SlowDuration }
205+ }
206+
207+ // restore stuff
208+ ScriptAction { script: ApplicationManager.focusApplication(priv.newFocusedAppId); }
209+ PropertyAction { target: fadeInScreenshotImage; property: "visible"; value: false }
210+ PropertyAction { target: mainScreenshotImage; property: "visible"; value: false }
211+ }
212+
213+ // FIXME: Drop this and make the imageprovider show a splashscreen instead
214+ Rectangle {
215+ id: appSplash2
216+ anchors.fill: parent
217+ color: "white"
218+ visible: priv.secondApplicationStarting
219+ }
220+ Image {
221+ id: fadeInScreenshotImage
222+ anchors { left: parent.left; bottom: parent.bottom }
223+ width: parent.width
224+ scale: .7
225+ visible: false
226+ }
227+
228+ Rectangle {
229+ id: appSplash
230+ anchors.fill: parent
231+ color: "white"
232+ visible: priv.applicationStarting
233+ }
234+ Image {
235+ id: mainScreenshotImage
236+ anchors { left: parent.left; bottom: parent.bottom }
237+ width: parent.width
238+ visible: false
239+ }
240+
241+ EdgeDragArea {
242+ id: spreadDragArea
243+ direction: Direction.Leftwards
244+ enabled: ApplicationManager.count > 1 && spreadView.phase != 2
245+
246+ anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
247+ width: root.dragAreaWidth
248+
249+ // Sitting at the right edge of the screen, this EdgeDragArea directly controls the spreadView when
250+ // attachedToView is true. When the finger movement passes positionMarker3 we detach it from the
251+ // spreadView and make the spreadView snap to positionMarker4.
252+ property bool attachedToView: true
253+
254+ property var gesturePoints: new Array()
255+
256+ onTouchXChanged: {
257+ if (!dragging && !priv.waitingForScreenshot) {
258+ // Initial touch. Let's update the screenshot and reset the spreadView to the starting position.
259+ priv.requestNewScreenshot();
260+ spreadView.phase = 0;
261+ spreadView.contentX = -spreadView.shift;
262+ }
263+ if (dragging && attachedToView) {
264+ // Gesture recognized. Let's move the spreadView with the finger
265+ spreadView.contentX = -touchX - spreadView.shift;
266+ }
267+ if (attachedToView && spreadView.shiftedContentX >= spreadView.width * spreadView.positionMarker3) {
268+ // We passed positionMarker3. Detach from spreadView and snap it.
269+ attachedToView = false;
270+ spreadView.snap();
271+ }
272+ gesturePoints.push(touchX);
273+ }
274+
275+ onStatusChanged: {
276+ if (status == DirectionalDragArea.Recognized) {
277+ attachedToView = true;
278+ }
279+ }
280+
281+ onDraggingChanged: {
282+ if (dragging) {
283+ // Gesture recognized. Start recording this gesture
284+ gesturePoints = [];
285+ return;
286+ }
287+
288+ // Ok. The user released. Find out if it was a one-way movement.
289+ var oneWayFlick = true;
290+ var smallestX = spreadDragArea.width;
291+ for (var i = 0; i < gesturePoints.length; i++) {
292+ if (gesturePoints[i] >= smallestX) {
293+ oneWayFlick = false;
294+ break;
295+ }
296+ smallestX = gesturePoints[i];
297+ }
298+ gesturePoints = [];
299+
300+ if (oneWayFlick && spreadView.shiftedContentX > units.gu(2) &&
301+ spreadView.shiftedContentX < spreadView.positionMarker1 * spreadView.width) {
302+ // If it was a short one-way movement, do the Alt+Tab switch
303+ // no matter if we didn't cross positionMarker1 yet.
304+ spreadView.snapTo(1);
305+ } else if (!dragging && attachedToView) {
306+ // otherwise snap to the closest snap position we can find
307+ // (might be back to start, to app 1 or to spread)
308+ spreadView.snap();
309+ }
310+ }
311+ }
312+
313+ Rectangle {
314+ id: coverFlipBackground
315+ anchors.fill: parent
316+ color: "black"
317+ visible: spreadView.visible
318+ }
319+
320+ InputFilterArea {
321+ anchors.fill: root
322+ blockInput: spreadView.visible
323+ }
324+
325+ Flickable {
326+ id: spreadView
327+ objectName: "spreadView"
328+ anchors.fill: parent
329+ visible: spreadDragArea.status == DirectionalDragArea.Recognized || phase > 1 || snapAnimation.running
330+ contentWidth: spreadRow.width - shift
331+ contentX: -shift
332+
333+ // The flickable needs to fill the screen in order to get touch events all over.
334+ // However, we don't want to the user to be able to scroll back all the way. For
335+ // that, the beginning of the gesture starts with a negative value for contentX
336+ // so the flickable wants to pull it into the view already. "shift" tunes the
337+ // distance where to "lock" the content.
338+ property real shift: width / 2
339+ property real shiftedContentX: contentX + shift
340+
341+ property int tileDistance: width / 4
342+
343+ // Those markers mark the various positions in the spread (ratio to screen width from right to left):
344+ // 0 - 1: following finger, snap back to the beginning on release
345+ property real positionMarker1: 0.3
346+ // 1 - 2: curved snapping movement, snap to app 1 on release
347+ property real positionMarker2: 0.45
348+ // 2 - 3: movement follows finger, snaps back to app 1 on release
349+ property real positionMarker3: 0.6
350+ // passing 3, we detach movement from the finger and snap to 4
351+ property real positionMarker4: 0.9
352+
353+ // This is where the first app snaps to when bringing it in from the right edge.
354+ property real snapPosition: 0.75
355+
356+ // Phase of the animation:
357+ // 0: Starting from right edge, a new app (index 1) comes in from the right
358+ // 1: The app has reached the first snap position.
359+ // 2: The list is dragged further and snaps into the spread view when entering phase 2
360+ property int phase: 0
361+
362+ property int selectedIndex: -1
363+
364+ onShiftedContentXChanged: {
365+ switch (phase) {
366+ case 0:
367+ if (shiftedContentX > width * positionMarker2) {
368+ phase = 1;
369+ }
370+ break;
371+ case 1:
372+ if (shiftedContentX < width * positionMarker2) {
373+ phase = 0;
374+ } else if (shiftedContentX >= width * positionMarker4) {
375+ phase = 2;
376+ }
377+ break;
378+ }
379+ }
380+
381+ function snap() {
382+ if (shiftedContentX < positionMarker1 * width) {
383+ snapAnimation.targetContentX = -shift;
384+ snapAnimation.start();
385+ } else if (shiftedContentX < positionMarker2 * width) {
386+ snapTo(1)
387+ } else if (shiftedContentX < positionMarker3 * width) {
388+ snapTo(1)
389+ } else if (phase < 2){
390+ // Add 1 pixel to make sure we definitely hit positionMarker4 even with rounding errors of the animation.
391+ snapAnimation.targetContentX = width * positionMarker4 + 1 - shift;
392+ snapAnimation.start();
393+ }
394+ }
395+ function snapTo(index) {
396+ spreadView.selectedIndex = index;
397+ root.fullscreen = ApplicationManager.get(index).fullscreen;
398+ snapAnimation.targetContentX = -shift;
399+ snapAnimation.start();
400+ }
401+
402+ SequentialAnimation {
403+ id: snapAnimation
404+ property int targetContentX: -spreadView.shift
405+
406+ UbuntuNumberAnimation {
407+ target: spreadView
408+ property: "contentX"
409+ to: snapAnimation.targetContentX
410+ duration: UbuntuAnimation.FastDuration
411+ }
412+
413+ ScriptAction {
414+ script: {
415+ if (spreadView.selectedIndex >= 0) {
416+ ApplicationManager.focusApplication(ApplicationManager.get(spreadView.selectedIndex).appId);
417+ spreadView.selectedIndex = -1
418+ spreadView.phase = 0;
419+ spreadView.contentX = -spreadView.shift;
420+ }
421+ }
422+ }
423+ }
424+
425+ Item {
426+ id: spreadRow
427+ // This width controls how much the spread can be flicked left/right. It's composed of:
428+ // tileDistance * app count (with a minimum of 3 apps, in order to also allow moving 1 and 2 apps a bit)
429+ // + some constant value (still scales with the screen width) which looks good and somewhat fills the screen
430+ width: Math.max(3, ApplicationManager.count) * spreadView.tileDistance + (spreadView.width - spreadView.tileDistance) * 1.5
431+
432+ x: spreadView.contentX
433+
434+ Repeater {
435+ id: spreadRepeater
436+ model: ApplicationManager
437+ delegate: TransformedSpreadDelegate {
438+ id: appDelegate
439+ objectName: "appDelegate" + index
440+ startAngle: 45
441+ endAngle: 5
442+ startScale: 1.1
443+ endScale: 0.7
444+ startDistance: spreadView.tileDistance
445+ endDistance: units.gu(.5)
446+ width: spreadView.width
447+ height: spreadView.height
448+ selected: spreadView.selectedIndex == index
449+ otherSelected: spreadView.selectedIndex >= 0 && !selected
450+
451+ z: index
452+ x: index == 0 ? 0 : spreadView.width + (index - 1) * spreadView.tileDistance
453+
454+ // Each tile has a different progress value running from 0 to 1.
455+ // A progress value of 0 means the tile is at the right edge. 1 means the tile has reched the left edge.
456+ progress: {
457+ var tileProgress = (spreadView.shiftedContentX - index * spreadView.tileDistance) / spreadView.width;
458+ // Tile 1 needs to move directly from the beginning...
459+ if (index == 1 && spreadView.phase < 2) {
460+ tileProgress += spreadView.tileDistance / spreadView.width;
461+ }
462+ return tileProgress;
463+ }
464+
465+ // This mostly is the same as progress, just adds the snapping to phase 1 for tiles 0 and 1
466+ animatedProgress: {
467+ if (spreadView.phase == 0 && index < 2) {
468+ if (progress < spreadView.positionMarker1) {
469+ return progress;
470+ } else if (progress < spreadView.positionMarker1 + snappingCurve.period){
471+ return spreadView.positionMarker1 + snappingCurve.value * 3;
472+ } else {
473+ return spreadView.positionMarker2;
474+ }
475+ }
476+ return progress;
477+ }
478+
479+ EasingCurve {
480+ id: snappingCurve
481+ type: EasingCurve.OutQuad
482+ period: 0.05
483+ progress: appDelegate.progress - spreadView.positionMarker1
484+ }
485+
486+ onClicked: {
487+ if (spreadView.phase == 2) {
488+ if (ApplicationManager.focusedApplicationId == ApplicationManager.get(index).appId) {
489+ spreadView.snapTo(index);
490+ } else {
491+ ApplicationManager.requestFocusApplication(ApplicationManager.get(index).appId);
492+ }
493+ }
494+ }
495+ }
496+ }
497+ }
498+ }
499+}
qml/SideStage/SidestageHandle.qml → qml/Stages/SidestageHandle.qmlrenamed

The content is the same.

qml/Stages/SpreadDelegate.qmlnew file+55
@@ -0,0 +1,55 @@
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: Michael Zanetti <michael.zanetti@canonical.com>
17+*/
18+
19+import QtQuick 2.0
20+
21+Item {
22+ id: root
23+
24+ signal clicked()
25+
26+ property real topMarginProgress
27+
28+ QtObject {
29+ id: priv
30+ property real heightDifference: root.height - appImage.implicitHeight
31+ }
32+
33+ Image {
34+ id: dropShadow
35+ anchors.fill: appImage
36+ anchors.margins: -units.gu(2)
37+ source: "graphics/dropshadow.png"
38+ opacity: .4
39+ }
40+ Image {
41+ id: appImage
42+ anchors {
43+ left: parent.left;
44+ bottom: parent.bottom;
45+ top: parent.top;
46+ topMargin: priv.heightDifference * Math.max(0, 1 - root.topMarginProgress)
47+ }
48+ source: model.screenshot
49+ antialiasing: true
50+ }
51+ MouseArea {
52+ anchors.fill: appImage
53+ onClicked: root.clicked()
54+ }
55+}
qml/Stages/StageWithSideStage.qmlnew file+407
@@ -0,0 +1,407 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import Ubuntu.Components 0.1
19+import "../Components"
20+import Unity.Application 0.1
21+import Ubuntu.Gestures 0.1
22+
23+Item {
24+ id: root
25+ objectName: "stages"
26+ anchors.fill: parent
27+
28+ // Controls to be set from outside
29+ property bool shown: false
30+ property bool moving: false
31+ property int dragAreaWidth
32+
33+ // State information propagated to the outside
34+ readonly property bool painting: mainStageImage.visible || sideStageImage.visible || sideStageSnapAnimation.running
35+ property bool fullscreen: priv.focusedApplication ? priv.focusedApplication.fullscreen : false
36+ property bool overlayMode: (sideStageImage.shown && priv.mainStageAppId.length == 0) || priv.overlayOverride
37+ || (priv.mainStageAppId.length == 0 && sideStageSnapAnimation.running)
38+
39+ readonly property int overlayWidth: priv.overlayOverride ? 0 : priv.sideStageWidth
40+
41+ onShownChanged: {
42+ if (!shown) {
43+ priv.mainStageAppId = "";
44+ }
45+ }
46+
47+ onMovingChanged: {
48+ if (moving) {
49+ if (!priv.mainStageAppId && !priv.sideStageAppId) {
50+ // Pulling in from the right, make the last used (topmost) app visible
51+ var application = ApplicationManager.get(0);
52+ if (application.stage == ApplicationInfoInterface.SideStage) {
53+ sideStageImage.application = application;
54+ sideStageImage.x = root.width - sideStageImage.width
55+ sideStageImage.visible = true;
56+ } else {
57+ mainStageImage.application = application;
58+ mainStageImage.visible = true;
59+ }
60+ } else {
61+ priv.requestNewScreenshot(ApplicationInfoInterface.MainStage)
62+ if (priv.focusedApplicationId == priv.sideStageAppId) {
63+ priv.requestNewScreenshot(ApplicationInfoInterface.SideStage)
64+ }
65+ }
66+ } else {
67+ mainStageImage.visible = false;
68+ sideStageImage.visible = false;
69+ }
70+ }
71+
72+ QtObject {
73+ id: priv
74+
75+ property int sideStageWidth: units.gu(40)
76+
77+
78+ property string sideStageAppId
79+ property string mainStageAppId
80+
81+
82+ property var sideStageApp: ApplicationManager.findApplication(sideStageAppId)
83+ property var mainStageApp: ApplicationManager.findApplication(mainStageAppId)
84+
85+ property string sideStageScreenshot: sideStageApp ? sideStageApp.screenshot : ""
86+ property string mainStageScreenshot: mainStageApp ? mainStageApp.screenshot : ""
87+
88+ property string focusedApplicationId: ApplicationManager.focusedApplicationId
89+ property var focusedApplication: ApplicationManager.findApplication(focusedApplicationId)
90+ property url focusedScreenshot: focusedApplication ? focusedApplication.screenshot : ""
91+
92+ property bool waitingForMainScreenshot: false
93+ property bool waitingForSideScreenshot: false
94+ property bool waitingForScreenshots: waitingForMainScreenshot || waitingForSideScreenshot
95+
96+ property string startingAppId: ""
97+
98+ // Keep overlayMode even if there is no focused app (to allow pulling in the sidestage from the right)
99+ property bool overlayOverride: false
100+
101+ onFocusedApplicationChanged: {
102+ if (focusedApplication) {
103+ if (focusedApplication.stage == ApplicationInfoInterface.MainStage) {
104+ mainStageAppId = focusedApplicationId;
105+ priv.overlayOverride = false;
106+ if (priv.startingAppId == focusedApplicationId && sideStageImage.shown) {
107+ // There was already a sidestage app on top. bring it back!
108+ ApplicationManager.focusApplication(priv.sideStageAppId)
109+ priv.startingAppId = "";
110+ }
111+ } else if (focusedApplication.stage == ApplicationInfoInterface.SideStage) {
112+ sideStageAppId = focusedApplicationId;
113+ if (priv.startingAppId == focusedApplicationId && !sideStageImage.shown) {
114+ sideStageImage.snapToApp(focusedApplication);
115+ priv.startingAppId = "";
116+ }
117+ }
118+ } else if (root.overlayMode){
119+ sideStageImage.snapTo(root.width)
120+ }
121+ }
122+
123+ onMainStageScreenshotChanged: {
124+ waitingForMainScreenshot = false;
125+ }
126+
127+ onSideStageScreenshotChanged: {
128+ waitingForSideScreenshot = false;
129+ }
130+
131+ onFocusedScreenshotChanged: {
132+ waitingForSideScreenshot = false;
133+ }
134+
135+ onWaitingForScreenshotsChanged: {
136+ if (waitingForScreenshots) {
137+ return;
138+ }
139+
140+ if (root.moving) {
141+ if (mainStageAppId) {
142+ mainStageImage.application = mainStageApp
143+ mainStageImage.visible = true;
144+ }
145+ if (sideStageAppId && focusedApplicationId == sideStageAppId) {
146+ sideStageImage.application = sideStageApp;
147+ sideStageImage.x = root.width - sideStageImage.width
148+ sideStageImage.visible = true;
149+ }
150+ }
151+ if (sideStageHandleMouseArea.pressed) {
152+ if (sideStageAppId) {
153+ sideStageImage.application = sideStageApp;
154+ sideStageImage.x = root.width - sideStageImage.width
155+ sideStageImage.visible = true;
156+ }
157+ if (mainStageAppId) {
158+ mainStageImage.application = mainStageApp
159+ mainStageImage.visible = true;
160+ }
161+ }
162+ }
163+
164+ function requestNewScreenshot(stage) {
165+ if (stage == ApplicationInfoInterface.MainStage && mainStageAppId) {
166+ waitingForMainScreenshot = true;
167+ ApplicationManager.updateScreenshot(mainStageAppId);
168+ } else if (stage == ApplicationInfoInterface.SideStage && sideStageAppId) {
169+ waitingForSideScreenshot = true;
170+ ApplicationManager.updateScreenshot(sideStageAppId);
171+ }
172+ }
173+
174+ }
175+ // FIXME: the signal connection seems to get lost with the fake application manager.
176+ Connections {
177+ target: priv.sideStageApp
178+ onScreenshotChanged: priv.sideStageScreenshot = priv.sideStageApp.screenshot
179+ }
180+ Connections {
181+ target: priv.mainStageApp
182+ onScreenshotChanged: priv.mainStageScreenshot = priv.mainStageApp.screenshot
183+ }
184+
185+ Connections {
186+ target: ApplicationManager
187+
188+ onApplicationAdded: {
189+ priv.startingAppId = appId;
190+ splashScreenTimer.start();
191+ var application = ApplicationManager.findApplication(appId)
192+ if (application.stage == ApplicationInfoInterface.SideStage) {
193+ sideStageSplash.visible = true;
194+ } else if (application.stage == ApplicationInfoInterface.MainStage) {
195+ mainStageSplash.visible = true;
196+ }
197+ }
198+
199+ onFocusRequested: {
200+ var application = ApplicationManager.findApplication(appId)
201+ if (application.stage == ApplicationInfoInterface.SideStage) {
202+ if (!root.shown) {
203+ priv.mainStageAppId = "";
204+ mainStageImage.application = null
205+ }
206+ if (sideStageImage.shown) {
207+ sideStageImage.switchTo(application);
208+ if (priv.mainStageAppId) {
209+ mainStageImage.application = priv.mainStageApp;
210+ mainStageImage.visible = true;
211+ }
212+ } else {
213+ sideStageImage.application = application;
214+ sideStageImage.snapToApp(application);
215+ }
216+ } else if (application.stage == ApplicationInfoInterface.MainStage) {
217+ if (root.shown) {
218+ if (sideStageImage.shown) {
219+ sideStageImage.application = priv.sideStageApp;
220+ sideStageImage.visible = true;
221+ }
222+ priv.mainStageAppId = application.appId;
223+ mainStageImage.switchTo(application)
224+ ApplicationManager.focusApplication(appId)
225+ if (sideStageImage.shown) {
226+ // There was already a focused SS app. Bring it back
227+ ApplicationManager.focusApplication(priv.sideStageAppId)
228+ }
229+ } else {
230+ if (sideStageImage.shown) {
231+ sideStageImage.visible = false;
232+ sideStageImage.x = root.width;
233+ }
234+
235+ mainStageImage.application = application;
236+ ApplicationManager.focusApplication(appId)
237+ }
238+ }
239+ }
240+
241+ onApplicationRemoved: {
242+ if (priv.mainStageAppId == appId) {
243+ priv.mainStageAppId = "";
244+ }
245+ if (priv.sideStageAppId == appId) {
246+ priv.sideStageAppId = "";
247+ }
248+ if (priv.sideStageAppId.length == 0) {
249+ sideStageImage.shown = false;
250+ priv.overlayOverride = false;
251+ }
252+ }
253+
254+ }
255+
256+ Timer {
257+ id: splashScreenTimer
258+ // FIXME: apart from removing this completely in the future and make the app surface paint
259+ // meaningful stuff, also check for colin's stuff to land so we can shape 1.4 secs away from here
260+ // https://code.launchpad.net/~cjwatson/upstart-app-launch/libclick-manifest/+merge/210520
261+ // https://code.launchpad.net/~cjwatson/upstart-app-launch/libclick-pkgdir/+merge/209909
262+ interval: 1700
263+ repeat: false
264+ onTriggered: {
265+ mainStageSplash.visible = false;
266+ sideStageSplash.visible = false;
267+ }
268+ }
269+
270+ SwitchingApplicationImage {
271+ id: mainStageImage
272+ anchors.bottom: parent.bottom
273+ width: parent.width
274+ visible: false
275+
276+ onSwitched: {
277+ sideStageImage.visible = false;
278+ }
279+ }
280+
281+ Rectangle {
282+ id: mainStageSplash
283+ anchors.fill: root
284+ anchors.rightMargin: root.width - sideStageImage.x
285+ color: "white"
286+ }
287+
288+ SidestageHandle {
289+ id: sideStageHandle
290+ anchors { top: parent.top; right: sideStageImage.left; bottom: parent.bottom }
291+ width: root.dragAreaWidth
292+ visible: root.shown && priv.sideStageAppId && sideStageImage.x < root.width
293+
294+ }
295+ MouseArea {
296+ id: sideStageHandleMouseArea
297+ anchors { top: parent.top; right: parent.right; bottom: parent.bottom; rightMargin: sideStageImage.shown ? sideStageImage.width : 0}
298+ width: root.dragAreaWidth
299+ visible: priv.sideStageAppId
300+
301+ property var dragPoints: new Array()
302+
303+ onPressed: {
304+ priv.requestNewScreenshot(ApplicationInfoInterface.SideStage)
305+ if (priv.mainStageAppId) {
306+ priv.requestNewScreenshot(ApplicationInfoInterface.MainStage)
307+ }
308+ }
309+
310+ onMouseXChanged: {
311+ dragPoints.push(mouseX)
312+
313+ var dragPoint = root.width + mouseX;
314+ if (sideStageImage.shown) {
315+ dragPoint -= sideStageImage.width
316+ }
317+ sideStageImage.x = Math.max(root.width - sideStageImage.width, dragPoint)
318+ }
319+
320+ onReleased: {
321+ var distance = 0;
322+ var lastX = dragPoints[0];
323+ var oneWayFlick = true;
324+ for (var i = 0; i < dragPoints.length; ++i) {
325+ if (dragPoints[i] < lastX) {
326+ oneWayFlick = false;
327+ }
328+ distance += dragPoints[i] - lastX;
329+ lastX = dragPoints[i];
330+ }
331+ dragPoints = [];
332+
333+ if (oneWayFlick || distance > sideStageImage.width / 2) {
334+ sideStageImage.snapTo(root.width)
335+ } else {
336+ sideStageImage.snapToApp(priv.sideStageApp)
337+ }
338+ }
339+ }
340+
341+ SwitchingApplicationImage {
342+ id: sideStageImage
343+ width: priv.sideStageWidth
344+ height: root.height
345+ x: root.width
346+ anchors.bottom: parent.bottom
347+ visible: true
348+ property bool shown: false
349+
350+ onSwitched: {
351+ mainStageImage.visible = false;
352+ ApplicationManager.focusApplication(application.appId)
353+ }
354+
355+ function snapTo(targetX) {
356+ sideStageSnapAnimation.targetX = targetX
357+ sideStageImage.visible = true;
358+ if (priv.mainStageAppId) {
359+ mainStageImage.application = priv.mainStageApp
360+ mainStageImage.visible = true;
361+ }
362+ sideStageSnapAnimation.start();
363+ }
364+
365+ function snapToApp(application) {
366+ sideStageImage.application = application
367+ sideStageSnapAnimation.snapToId = application.appId;
368+ snapTo(root.width - sideStageImage.width);
369+ }
370+
371+ SequentialAnimation {
372+ id: sideStageSnapAnimation
373+ property int targetX: root.width
374+ property string snapToId
375+
376+ UbuntuNumberAnimation { target: sideStageImage; property: "x"; to: sideStageSnapAnimation.targetX; duration: UbuntuAnimation.SlowDuration }
377+ ScriptAction {
378+ script: {
379+ if (sideStageSnapAnimation.targetX == root.width) {
380+ if (priv.mainStageAppId) {
381+ ApplicationManager.focusApplication(priv.mainStageAppId)
382+ } else {
383+ priv.overlayOverride = true;
384+ ApplicationManager.unfocusCurrentApplication();
385+ }
386+ sideStageImage.shown = false;
387+ }
388+ if (sideStageSnapAnimation.snapToId) {
389+ ApplicationManager.focusApplication(sideStageSnapAnimation.snapToId)
390+ sideStageSnapAnimation.snapToId = "";
391+ sideStageImage.shown = true;
392+ priv.overlayOverride = false;
393+ }
394+ sideStageImage.visible = false;
395+ mainStageImage.visible = false;
396+ }
397+ }
398+ }
399+ }
400+
401+ Rectangle {
402+ id: sideStageSplash
403+ anchors.fill: parent
404+ anchors.leftMargin: sideStageImage.x
405+ color: "white"
406+ }
407+}
qml/Stages/SwitchingApplicationImage.qmlnew file+81
@@ -0,0 +1,81 @@
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: Michael Zanetti <michael.zanetti@canonical.com>
17+*/
18+
19+import QtQuick 2.0
20+import Ubuntu.Components 0.1
21+
22+Rectangle {
23+ id: root
24+ implicitHeight: image.implicitHeight
25+ implicitWidth: image.implicitWidth
26+ color: "black"
27+
28+ property var application
29+
30+ signal switched()
31+
32+ function switchTo(application) {
33+ if (root.application == application) {
34+ root.switched();
35+ return;
36+ }
37+
38+ priv.newApplication = application
39+ root.visible = true;
40+ switchToAnimation.start()
41+ }
42+
43+ QtObject {
44+ id: priv
45+ property var newApplication
46+ }
47+
48+ Image {
49+ id: newImage
50+ anchors.bottom: parent.bottom
51+ width: root.width
52+ source: priv.newApplication ? priv.newApplication.screenshot : ""
53+ }
54+
55+ Image {
56+ id: image
57+ visible: true
58+ source: root.application ? root.application.screenshot : ""
59+ width: root.width
60+ height: sourceSize.height
61+ anchors.bottom: parent.bottom
62+
63+ }
64+
65+ SequentialAnimation {
66+ id: switchToAnimation
67+ ParallelAnimation {
68+ UbuntuNumberAnimation { target: image; property: "x"; from: 0; to: root.width; duration: UbuntuAnimation.SlowDuration }
69+ UbuntuNumberAnimation { target: newImage; property: "scale"; from: 0.7; to: 1; duration: UbuntuAnimation.SlowDuration }
70+ }
71+ ScriptAction {
72+ script: {
73+ image.x = 0
74+ root.application = priv.newApplication
75+ root.visible = false;
76+ priv.newApplication = null
77+ root.switched();
78+ }
79+ }
80+ }
81+}
qml/Stages/TransformedSpreadDelegate.qmlnew file+327
@@ -0,0 +1,327 @@
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: Michael Zanetti <michael.zanetti@canonical.com>
17+*/
18+
19+import QtQuick 2.0
20+import Utils 0.1
21+import Ubuntu.Components 0.1
22+
23+SpreadDelegate {
24+ id: root
25+
26+ property bool selected: false
27+ property bool otherSelected: false
28+
29+ // The progress animates the tiles. A value > 0 makes it appear from the right edge. At 1 it reaches the end position.
30+ property real progress: 0
31+ // This is required to snap tile 1 during phase 1 and 2.
32+ property real animatedProgress: 0
33+
34+ property real startAngle: 0
35+ property real endAngle: 0
36+
37+ property real startScale: 1
38+ property real endScale: 1
39+
40+ // Specific to just one tile
41+ property real tile1StartScale: startScale + .4
42+ property real tile0SnapAngle: 10
43+
44+ property real startDistance: units.gu(5)
45+ property real endDistance: units.gu(.5)
46+
47+ onSelectedChanged: {
48+ if (selected) {
49+ priv.snapshot();
50+ }
51+ priv.isSelected = selected;
52+ }
53+
54+ onOtherSelectedChanged: {
55+ if (otherSelected) {
56+ priv.snapshot();
57+ }
58+ priv.otherSelected = otherSelected;
59+ }
60+
61+ Connections {
62+ target: spreadView
63+ onPhaseChanged: {
64+ if (spreadView.phase == 1) {
65+ if (index == 0) {
66+ priv.phase2startTranslate = priv.easingAnimation(0, spreadView.positionMarker4, 0, -spreadView.width, spreadView.positionMarker4) + spreadView.width;
67+ priv.phase2startAngle = priv.easingAnimation(0, spreadView.positionMarker4, root.startAngle, root.endAngle, spreadView.positionMarker4);
68+ priv.phase2startScale = priv.easingAnimation(0, spreadView.positionMarker4, root.startScale, root.endScale, spreadView.positionMarker4);
69+ priv.phase2startTopMarginProgress = priv.easingAnimation(0, 1, 0, 1, spreadView.positionMarker4);
70+ } else if (index == 1) {
71+ // find where the main easing for Tile 1 would be when reaching phase 2
72+ var phase2Progress = spreadView.positionMarker4 - spreadView.tileDistance / spreadView.width;
73+ priv.phase2startTranslate = priv.easingAnimation(0, phase2Progress, 0, -spreadView.width + root.endDistance, phase2Progress);
74+ priv.phase2startAngle = priv.easingAnimation(0, phase2Progress, root.startAngle, root.endAngle, phase2Progress);
75+ priv.phase2startScale = priv.easingAnimation(0, phase2Progress, root.startScale, root.endScale, phase2Progress);
76+ priv.phase2startTopMarginProgress = priv.easingAnimation(0, 1, 0, spreadView.positionMarker4, phase2Progress);
77+ }
78+ }
79+ }
80+ }
81+
82+ QtObject {
83+ id: priv
84+ property bool isSelected: false
85+ property bool otherSelected: false
86+ property real selectedProgress
87+ property real selectedXTranslate
88+ property real selectedAngle
89+ property real selectedScale
90+ property real selectedOpacity
91+ property real selectedTopMarginProgress
92+
93+ // Those values are needed as target values for the end of phase 1.
94+ // As they are static values, lets calculate them once when entering phase 1 instead of calculating them in each animation pass.
95+ property real phase2startTranslate
96+ property real phase2startAngle
97+ property real phase2startScale
98+ property real phase2startTopMarginProgress
99+
100+ function snapshot() {
101+ selectedProgress = root.progress;
102+ selectedXTranslate = xTranslate;
103+ selectedAngle = angle;
104+ selectedScale = scale;
105+ selectedOpacity = opacity;
106+ selectedTopMarginProgress = topMarginProgress;
107+ }
108+
109+ // This calculates how much negative progress there can be if unwinding the spread completely
110+ // the progress for each tile starts at 0 when it crosses the right edge, so the later a tile comes in,
111+ // the bigger its negativeProgress can be.
112+ property real negativeProgress: {
113+ if (index == 1 && spreadView.phase < 2) {
114+ return 0;
115+ }
116+ return -index * spreadView.tileDistance / spreadView.width;
117+ }
118+
119+ function linearAnimation(startProgress, endProgress, startValue, endValue, progress) {
120+ // progress : progressDiff = value : valueDiff => value = progress * valueDiff / progressDiff
121+ return (progress - startProgress) * (endValue - startValue) / (endProgress - startProgress) + startValue;
122+ }
123+
124+ function easingAnimation(startProgress, endProgress, startValue, endValue, progress) {
125+ helperEasingCurve.progress = progress - startProgress;
126+ helperEasingCurve.period = endProgress - startProgress;
127+ return helperEasingCurve.value * (endValue - startValue) + startValue;
128+ }
129+
130+ property real animatedEndDistance: linearAnimation(0, 2, root.endDistance, 0, root.progress)
131+
132+ // The following blocks handle the animation of the tile in the spread.
133+ // At the beginning, each tile is attached at the right edge, outside the screen.
134+ // The progress for each tile starts at 0 and it reaches its end position at a progress of 1.
135+ // The first phases are handled special for the first 2 tiles. as we do the alt-tab and snapping in there
136+ // Once we reached phase 3, the animation is the same for all tiles.
137+ // When a tile is selected, the animation state is snapshotted, and the spreadView is unwound (progress animates
138+ // back to negativeProgress). All tiles are kept in place and faded out to 0 opacity except
139+ // the selected tile, which is animated from the snapshotted position to be fullscreen.
140+
141+ property real xTranslate: {
142+ if (otherSelected) {
143+ if (spreadView.phase < 2 && index == 0) {
144+ return linearAnimation(selectedProgress, negativeProgress,
145+ selectedXTranslate, selectedXTranslate - spreadView.tileDistance, root.progress);
146+ }
147+
148+ return selectedXTranslate;
149+ }
150+
151+ switch (index) {
152+ case 0:
153+ if (spreadView.phase == 0) {
154+ return Math.min(0, linearAnimation(0, spreadView.positionMarker2,
155+ 0, -spreadView.width * .25, root.animatedProgress));
156+ } else if (spreadView.phase == 1){
157+ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
158+ -spreadView.width * .25, priv.phase2startTranslate, root.progress);
159+ } else if (!priv.isSelected){ // phase 2
160+ // Apply the same animation as with the rest but add spreadView.width to align it with the others.
161+ return -easingCurve.value * spreadView.width + spreadView.width;
162+ } else if (priv.isSelected) {
163+ return linearAnimation(selectedProgress, negativeProgress, selectedXTranslate, 0, root.progress);
164+ }
165+
166+ case 1:
167+ if (spreadView.phase == 0 && !priv.isSelected) {
168+ return linearAnimation(0, spreadView.positionMarker2,
169+ 0, -spreadView.width * spreadView.snapPosition, root.animatedProgress);
170+ } else if (spreadView.phase == 1 && !priv.isSelected) {
171+ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
172+ -spreadView.width * spreadView.snapPosition, priv.phase2startTranslate,
173+ root.progress);
174+ }
175+ }
176+
177+ if (priv.isSelected) {
178+ // Distance to left edge
179+ var targetTranslate = -spreadView.width - ((index - 1) * root.startDistance);
180+ return linearAnimation(selectedProgress, negativeProgress,
181+ selectedXTranslate, targetTranslate, root.progress);
182+ }
183+
184+ // Fix it at the right edge...
185+ var rightEdgeOffset = -((index - 1) * root.startDistance);
186+ // ...and use our easing to move them to the left. Stop a bit earlier for each tile
187+ return -easingCurve.value * spreadView.width + (index * animatedEndDistance) + rightEdgeOffset;
188+
189+ }
190+
191+ property real angle: {
192+ if (priv.otherSelected) {
193+ return priv.selectedAngle;
194+ }
195+ if (priv.isSelected) {
196+ return linearAnimation(selectedProgress, negativeProgress, selectedAngle, 0, root.progress);
197+ }
198+ switch (index) {
199+ case 0:
200+ if (spreadView.phase == 0) {
201+ return Math.max(0, linearAnimation(0, spreadView.positionMarker2,
202+ 0, root.tile0SnapAngle, root.animatedProgress));
203+ } else if (spreadView.phase == 1) {
204+ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
205+ root.tile0SnapAngle, phase2startAngle, root.progress);
206+ }
207+ case 1:
208+ if (spreadView.phase == 0) {
209+ return linearAnimation(0, spreadView.positionMarker2, root.startAngle,
210+ root.startAngle * (1-spreadView.snapPosition), root.animatedProgress);
211+ } else if (spreadView.phase == 1) {
212+ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
213+ root.startAngle * (1-spreadView.snapPosition), priv.phase2startAngle,
214+ root.progress);
215+ }
216+ }
217+ return root.startAngle - easingCurve.value * (root.startAngle - root.endAngle);
218+ }
219+
220+ property real scale: {
221+ if (priv.otherSelected) {
222+ return priv.selectedScale;
223+ }
224+ if (priv.isSelected) {
225+ return linearAnimation(selectedProgress, negativeProgress, selectedScale, 1, root.progress);
226+ }
227+
228+ switch (index) {
229+ case 0:
230+ if (spreadView.phase == 0) {
231+ return 1;
232+ } else if (spreadView.phase == 1) {
233+ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
234+ 1, phase2startScale, root.progress);
235+ }
236+ case 1:
237+ if (spreadView.phase == 0) {
238+ var targetScale = tile1StartScale - ((tile1StartScale - 1) * spreadView.snapPosition);
239+ return linearAnimation(0, spreadView.positionMarker2,
240+ root.tile1StartScale, targetScale, root.animatedProgress);
241+ } else if (spreadView.phase == 1) {
242+ var startScale = tile1StartScale - ((tile1StartScale - 1) * spreadView.snapPosition);
243+ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
244+ startScale, priv.phase2startScale, root.progress);
245+ }
246+ }
247+ return root.startScale - easingCurve.value * (root.startScale - root.endScale);
248+ }
249+
250+ property real opacity: {
251+ if (priv.otherSelected) {
252+ return linearAnimation (selectedProgress, Math.max(0, selectedProgress - .5),
253+ selectedOpacity, 0, root.progress);
254+ }
255+ if (index == 0) {
256+ switch (spreadView.phase) {
257+ case 0:
258+ return linearAnimation(0, spreadView.positionMarker2, 1, .3, root.animatedProgress);
259+ case 1:
260+ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
261+ .3, 1, root.animatedProgress);
262+ }
263+ }
264+
265+ return 1;
266+ }
267+
268+ property real topMarginProgress: {
269+ if (priv.isSelected) {
270+ return linearAnimation(selectedProgress, negativeProgress, selectedTopMarginProgress, 0, root.progress);
271+ }
272+
273+ switch (index) {
274+ case 0:
275+ if (spreadView.phase == 0) {
276+ return 0;
277+ } else if (spreadView.phase == 1) {
278+ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
279+ 0, priv.phase2startTopMarginProgress, root.progress);
280+ }
281+ break;
282+ case 1:
283+ if (spreadView.phase == 0) {
284+ return 0;
285+ } else if (spreadView.phase == 1) {
286+ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
287+ 0, priv.phase2startTopMarginProgress, root.progress);
288+ }
289+ }
290+
291+ return easingCurve.value;
292+ }
293+ }
294+
295+ transform: [
296+ Rotation {
297+ origin { x: 0; y: spreadView.height / 2 }
298+ axis { x: 0; y: 1; z: 0 }
299+ angle: priv.angle
300+ },
301+ Scale {
302+ origin { x: 0; y: spreadView.height / 2 }
303+ xScale: priv.scale
304+ yScale: xScale
305+ },
306+ Translate {
307+ x: priv.xTranslate
308+ }
309+ ]
310+ opacity: priv.opacity
311+ topMarginProgress: priv.topMarginProgress
312+
313+ EasingCurve {
314+ id: easingCurve
315+ type: EasingCurve.OutSine
316+ period: 1 - spreadView.positionMarker2
317+ progress: root.animatedProgress
318+ }
319+
320+ // This is used as a calculation helper to figure values for progress other than the current one
321+ // Do not bind anything to this...
322+ EasingCurve {
323+ id: helperEasingCurve
324+ type: easingCurve.type
325+ period: easingCurve.period
326+ }
327+}
qml/Stages/graphics/dropshadow.pngnew file

Binary file not shown.

qml/SideStage/graphics/sidestage_handle@20.png → qml/Stages/graphics/sidestage_handle@20.pngrenamed

Binary file not shown.

qml/SideStage/graphics/sidestage_handle@20.sci → qml/Stages/graphics/sidestage_handle@20.scirenamed

The content is the same.

src/main.cpp+2−6
@@ -120,18 +120,14 @@ int startShell(int argc, const char** argv, void* server)
120 prependImportPaths(view->engine(), ::overrideImportPaths());
120 prependImportPaths(view->engine(), ::overrideImportPaths());
121 appendImportPaths(view->engine(), ::fallbackImportPaths());
121 appendImportPaths(view->engine(), ::fallbackImportPaths());
122
122
123− if (isUbuntuMirServer) {
124− QStringList importPaths = view->engine()->importPathList();
125− importPaths.replaceInStrings(QRegExp("qt5/imports$"), "qt5/imports/Unity-Mir");
126− view->engine()->setImportPathList(importPaths);
127−
128#if UNITY8_GREETER
123#if UNITY8_GREETER
124+ if (isUbuntuMirServer) {
129 // Add alpha to surface, so that the greeter can bleed through
125 // Add alpha to surface, so that the greeter can bleed through
130 QSurfaceFormat format;
126 QSurfaceFormat format;
131 format.setAlphaBufferSize(8);
127 format.setAlphaBufferSize(8);
132 view->setFormat(format);
128 view->setFormat(format);
133−#endif
134 }
129 }
130+#endif
135
131
136 view->setSource(source);
132 view->setSource(source);
137 view->setColor(Qt::transparent);
133 view->setColor(Qt::transparent);
tests/autopilot/unity8/shell/emulators/dash.py+1−4
@@ -80,10 +80,7 @@ class Dash(emulators.UnityEmulatorBase):
80 'No scope found with id {0}'.format(scope_id))
80 'No scope found with id {0}'.format(scope_id))
81
81
82 def _get_scope_from_loader(self, loader):
82 def _get_scope_from_loader(self, loader):
83− if loader.scopeId == 'clickscope':
84− return loader.select_single(DashApps)
83+ return loader.get_children()[0]
85− else:
86− return loader.select_single(GenericScopeView)
87
84
88 def _open_scope_scrolling(self, scope_loader):
85 def _open_scope_scrolling(self, scope_loader):
89 scroll = self._get_scroll_direction(scope_loader)
86 scroll = self._get_scroll_direction(scope_loader)
tests/autopilot/unity8/shell/emulators/main_window.py+1−1
@@ -112,4 +112,4 @@ class QQuickView(emulators.UnityEmulatorBase):
112
112
113 def get_current_focused_app_id(self):
113 def get_current_focused_app_id(self):
114 """Return the id of the focused application."""
114 """Return the id of the focused application."""
115− return self.select_single('Shell').currentFocusedAppId
115+ return self.select_single('Shell').focusedApplicationId
tests/mocks/Ubuntu/CMakeLists.txt+1
@@ -1 +1,2 @@
1add_subdirectory(DownloadDaemonListener)
1add_subdirectory(DownloadDaemonListener)
2+add_subdirectory(Thumbnailer)
tests/mocks/Ubuntu/Thumbnailer/CMakeLists.txtnew file+7
@@ -0,0 +1,7 @@
1+# copy files into build directory for shadow builds
2+add_custom_target(UbuntuThumbnailerQmlDirFile ALL
3+ COMMAND cp "${CMAKE_CURRENT_SOURCE_DIR}/qmldir" ${CMAKE_CURRENT_BINARY_DIR}
4+ DEPENDS qmldir
5+)
6+
7+install(FILES qmldir DESTINATION ${SHELL_INSTALL_QML}/mocks/Ubuntu/Thumbnailer)
tests/mocks/Ubuntu/Thumbnailer/qmldirnew file+2
@@ -0,0 +1,2 @@
1+module Ubuntu.Thumbnailer
2+Dummy 0.1 Dummy.qml
tests/mocks/Unity/Application/ApplicationInfo.cpp+9
@@ -20,6 +20,7 @@
20#include <QQuickItem>
20#include <QQuickItem>
21#include <QQuickView>
21#include <QQuickView>
22#include <QQmlComponent>
22#include <QQmlComponent>
23+#include <QTimer>
23
24
24ApplicationInfo::ApplicationInfo(const QString &appId, QObject *parent)
25ApplicationInfo::ApplicationInfo(const QString &appId, QObject *parent)
25 : ApplicationInfoInterface(appId, parent)
26 : ApplicationInfoInterface(appId, parent)
@@ -32,6 +33,7 @@ ApplicationInfo::ApplicationInfo(const QString &appId, QObject *parent)
32 ,m_windowComponent(0)
33 ,m_windowComponent(0)
33 ,m_parentItem(0)
34 ,m_parentItem(0)
34{
35{
36+ QTimer::singleShot(300, this, SLOT(setRunning()));
35}
37}
36
38
37ApplicationInfo::ApplicationInfo(QObject *parent)
39ApplicationInfo::ApplicationInfo(QObject *parent)
@@ -44,6 +46,7 @@ ApplicationInfo::ApplicationInfo(QObject *parent)
44 ,m_windowComponent(0)
46 ,m_windowComponent(0)
45 ,m_parentItem(0)
47 ,m_parentItem(0)
46{
48{
49+ QTimer::singleShot(300, this, SLOT(setRunning()));
47}
50}
48
51
49void ApplicationInfo::onWindowComponentStatusChanged(QQmlComponent::Status status)
52void ApplicationInfo::onWindowComponentStatusChanged(QQmlComponent::Status status)
@@ -52,6 +55,12 @@ void ApplicationInfo::onWindowComponentStatusChanged(QQmlComponent::Status statu
52 doCreateWindowItem();
55 doCreateWindowItem();
53}
56}
54
57
58+void ApplicationInfo::setRunning()
59+{
60+ m_state = Running;
61+ Q_EMIT stateChanged();
62+}
63+
55void ApplicationInfo::createWindowComponent()
64void ApplicationInfo::createWindowComponent()
56{
65{
57 // The assumptions I make here really should hold.
66 // The assumptions I make here really should hold.
tests/mocks/Unity/Application/ApplicationInfo.h+2
@@ -74,6 +74,7 @@ class ApplicationInfo : public ApplicationInfoInterface {
74 IMPLEMENT_PROPERTY(fullscreen, Fullscreen, bool)
74 IMPLEMENT_PROPERTY(fullscreen, Fullscreen, bool)
75 IMPLEMENT_PROPERTY(imageQml, ImageQml, QString)
75 IMPLEMENT_PROPERTY(imageQml, ImageQml, QString)
76 IMPLEMENT_PROPERTY(windowQml, WindowQml, QString)
76 IMPLEMENT_PROPERTY(windowQml, WindowQml, QString)
77+ IMPLEMENT_PROPERTY(screenshot, Screenshot, QUrl)
77
78
78 #undef IMPLEMENT_PROPERTY
79 #undef IMPLEMENT_PROPERTY
79
80
@@ -83,6 +84,7 @@ class ApplicationInfo : public ApplicationInfoInterface {
83
84
84 private Q_SLOTS:
85 private Q_SLOTS:
85 void onWindowComponentStatusChanged(QQmlComponent::Status status);
86 void onWindowComponentStatusChanged(QQmlComponent::Status status);
87+ void setRunning();
86
88
87 private:
89 private:
88 void createWindowItem();
90 void createWindowItem();
tests/mocks/Unity/Application/ApplicationManager.cpp+109−3
@@ -24,13 +24,17 @@
24#include <QQuickItem>
24#include <QQuickItem>
25#include <QQuickView>
25#include <QQuickView>
26#include <QQmlComponent>
26#include <QQmlComponent>
27+#include <QTimer>
28+#include <QDateTime>
27
29
28ApplicationManager::ApplicationManager(QObject *parent)
30ApplicationManager::ApplicationManager(QObject *parent)
29 : ApplicationManagerInterface(parent)
31 : ApplicationManagerInterface(parent)
32+ , m_suspended(false)
30 , m_mainStageComponent(0)
33 , m_mainStageComponent(0)
31 , m_mainStage(0)
34 , m_mainStage(0)
32 , m_sideStageComponent(0)
35 , m_sideStageComponent(0)
33 , m_sideStage(0)
36 , m_sideStage(0)
37+ , m_rightMargin(0)
34{
38{
35 buildListOfAvailableApplications();
39 buildListOfAvailableApplications();
36}
40}
@@ -63,6 +67,8 @@ QVariant ApplicationManager::data(const QModelIndex& index, int role) const {
63 return app->state();
67 return app->state();
64 case RoleFocused:
68 case RoleFocused:
65 return app->focused();
69 return app->focused();
70+ case RoleScreenshot:
71+ return app->screenshot();
66 default:
72 default:
67 return QVariant();
73 return QVariant();
68 }
74 }
@@ -84,13 +90,16 @@ ApplicationInfo *ApplicationManager::findApplication(const QString &appId) const
84}
90}
85
91
86void ApplicationManager::add(ApplicationInfo *application) {
92void ApplicationManager::add(ApplicationInfo *application) {
87− if (!application)
93+ if (!application) {
88 return;
94 return;
95+ }
89
96
90 beginInsertRows(QModelIndex(), m_runningApplications.size(), m_runningApplications.size());
97 beginInsertRows(QModelIndex(), m_runningApplications.size(), m_runningApplications.size());
91 m_runningApplications.append(application);
98 m_runningApplications.append(application);
92 endInsertRows();
99 endInsertRows();
100+ Q_EMIT applicationAdded(application->appId());
93 Q_EMIT countChanged();
101 Q_EMIT countChanged();
102+ Q_EMIT focusRequested(application->appId());
94}
103}
95
104
96void ApplicationManager::remove(ApplicationInfo *application) {
105void ApplicationManager::remove(ApplicationInfo *application) {
@@ -99,6 +108,7 @@ void ApplicationManager::remove(ApplicationInfo *application) {
99 beginRemoveRows(QModelIndex(), i, i);
108 beginRemoveRows(QModelIndex(), i, i);
100 m_runningApplications.removeAt(i);
109 m_runningApplications.removeAt(i);
101 endRemoveRows();
110 endRemoveRows();
111+ Q_EMIT applicationRemoved(application->appId());
102 Q_EMIT countChanged();
112 Q_EMIT countChanged();
103 }
113 }
104}
114}
@@ -171,6 +181,8 @@ ApplicationInfo* ApplicationManager::startApplication(const QString &appId,
171 }
181 }
172 add(application);
182 add(application);
173
183
184+ QMetaObject::invokeMethod(this, "focusApplication", Qt::QueuedConnection, Q_ARG(QString, appId));
185+
174 return application;
186 return application;
175}
187}
176
188
@@ -180,8 +192,32 @@ bool ApplicationManager::stopApplication(const QString &appId)
180 if (application == nullptr)
192 if (application == nullptr)
181 return false;
193 return false;
182
194
195+ if (application->appId() == focusedApplicationId()) {
196+ unfocusCurrentApplication();
197+ }
183 remove(application);
198 remove(application);
184− Q_EMIT focusedApplicationIdChanged();
199+ return true;
200+}
201+
202+bool ApplicationManager::updateScreenshot(const QString &appId)
203+{
204+ int idx = -1;
205+ ApplicationInfo *application = nullptr;
206+ for (int i = 0; i < m_availableApplications.count(); ++i) {
207+ application = m_availableApplications.at(i);
208+ if (application->appId() == appId) {
209+ idx = i;
210+ break;
211+ }
212+ }
213+
214+ if (idx == -1) {
215+ return false;
216+ }
217+
218+ application->setScreenshot(QString("image://application/%1/%2").arg(appId).arg(QDateTime::currentMSecsSinceEpoch()));
219+ QModelIndex appIndex = index(idx);
220+ Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << RoleScreenshot);
185 return true;
221 return true;
186}
222}
187
223
@@ -194,6 +230,25 @@ QString ApplicationManager::focusedApplicationId() const {
194 return QString();
230 return QString();
195}
231}
196
232
233+bool ApplicationManager::suspended() const
234+{
235+ return m_suspended;
236+}
237+
238+void ApplicationManager::setSuspended(bool suspended)
239+{
240+ ApplicationInfo *focusedApp = findApplication(focusedApplicationId());
241+ if (focusedApp) {
242+ if (suspended) {
243+ focusedApp->setState(ApplicationInfo::Suspended);
244+ } else {
245+ focusedApp->setState(ApplicationInfo::Running);
246+ }
247+ }
248+ m_suspended = suspended;
249+ Q_EMIT suspendedChanged();
250+}
251+
197bool ApplicationManager::focusApplication(const QString &appId)
252bool ApplicationManager::focusApplication(const QString &appId)
198{
253{
199 ApplicationInfo *application = findApplication(appId);
254 ApplicationInfo *application = findApplication(appId);
@@ -242,6 +297,15 @@ bool ApplicationManager::focusApplication(const QString &appId)
242 return true;
297 return true;
243}
298}
244
299
300+bool ApplicationManager::requestFocusApplication(const QString &appId)
301+{
302+ if (appId != focusedApplicationId()) {
303+ QMetaObject::invokeMethod(this, "focusRequested", Qt::QueuedConnection, Q_ARG(QString, appId));
304+ return true;
305+ }
306+ return false;
307+}
308+
245void ApplicationManager::unfocusCurrentApplication()
309void ApplicationManager::unfocusCurrentApplication()
246{
310{
247 for (ApplicationInfo *app : m_runningApplications) {
311 for (ApplicationInfo *app : m_runningApplications) {
@@ -270,10 +334,12 @@ void ApplicationManager::generateQmlStrings(ApplicationInfo *application)
270 "Image {\n"
334 "Image {\n"
271 " anchors.fill: parent\n"
335 " anchors.fill: parent\n"
272 " anchors.topMargin: %1\n"
336 " anchors.topMargin: %1\n"
337+ " anchors.rightMargin: %2\n"
273− " source: \"file://%2/Dash/graphics/phone/screenshots/%3.png\"\n"
338+ " source: \"file://%3/Dash/graphics/phone/screenshots/%4.png\"\n"
274 " smooth: true\n"
339 " smooth: true\n"
275 " fillMode: Image.PreserveAspectCrop\n"
340 " fillMode: Image.PreserveAspectCrop\n"
276 "}").arg(topMargin)
341 "}").arg(topMargin)
342+ .arg(m_rightMargin)
277 .arg(qmlDirectory())
343 .arg(qmlDirectory())
278 .arg(application->icon().toString());
344 .arg(application->icon().toString());
279 application->setWindowQml(windowQml);
345 application->setWindowQml(windowQml);
@@ -299,6 +365,7 @@ void ApplicationManager::buildListOfAvailableApplications()
299 application->setName("Phone");
365 application->setName("Phone");
300 application->setIcon(QUrl("phone"));
366 application->setIcon(QUrl("phone"));
301 application->setStage(ApplicationInfo::SideStage);
367 application->setStage(ApplicationInfo::SideStage);
368+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
302 generateQmlStrings(application);
369 generateQmlStrings(application);
303 m_availableApplications.append(application);
370 m_availableApplications.append(application);
304
371
@@ -307,6 +374,7 @@ void ApplicationManager::buildListOfAvailableApplications()
307 application->setName("Camera");
374 application->setName("Camera");
308 application->setIcon(QUrl("camera"));
375 application->setIcon(QUrl("camera"));
309 application->setFullscreen(true);
376 application->setFullscreen(true);
377+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
310 generateQmlStrings(application);
378 generateQmlStrings(application);
311 m_availableApplications.append(application);
379 m_availableApplications.append(application);
312
380
@@ -314,6 +382,7 @@ void ApplicationManager::buildListOfAvailableApplications()
314 application->setAppId("gallery-app");
382 application->setAppId("gallery-app");
315 application->setName("Gallery");
383 application->setName("Gallery");
316 application->setIcon(QUrl("gallery"));
384 application->setIcon(QUrl("gallery"));
385+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
317 generateQmlStrings(application);
386 generateQmlStrings(application);
318 m_availableApplications.append(application);
387 m_availableApplications.append(application);
319
388
@@ -322,13 +391,16 @@ void ApplicationManager::buildListOfAvailableApplications()
322 application->setName("Facebook");
391 application->setName("Facebook");
323 application->setIcon(QUrl("facebook"));
392 application->setIcon(QUrl("facebook"));
324 application->setStage(ApplicationInfo::SideStage);
393 application->setStage(ApplicationInfo::SideStage);
394+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
325 generateQmlStrings(application);
395 generateQmlStrings(application);
326 m_availableApplications.append(application);
396 m_availableApplications.append(application);
327
397
328 application = new ApplicationInfo(this);
398 application = new ApplicationInfo(this);
329 application->setAppId("webbrowser-app");
399 application->setAppId("webbrowser-app");
400+ application->setFullscreen(true);
330 application->setName("Browser");
401 application->setName("Browser");
331 application->setIcon(QUrl("browser"));
402 application->setIcon(QUrl("browser"));
403+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
332 generateQmlStrings(application);
404 generateQmlStrings(application);
333 m_availableApplications.append(application);
405 m_availableApplications.append(application);
334
406
@@ -337,6 +409,7 @@ void ApplicationManager::buildListOfAvailableApplications()
337 application->setName("Twitter");
409 application->setName("Twitter");
338 application->setIcon(QUrl("twitter"));
410 application->setIcon(QUrl("twitter"));
339 application->setStage(ApplicationInfo::SideStage);
411 application->setStage(ApplicationInfo::SideStage);
412+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
340 generateQmlStrings(application);
413 generateQmlStrings(application);
341 m_availableApplications.append(application);
414 m_availableApplications.append(application);
342
415
@@ -344,6 +417,7 @@ void ApplicationManager::buildListOfAvailableApplications()
344 application->setAppId("gmail-webapp");
417 application->setAppId("gmail-webapp");
345 application->setName("GMail");
418 application->setName("GMail");
346 application->setIcon(QUrl("gmail"));
419 application->setIcon(QUrl("gmail"));
420+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
347 m_availableApplications.append(application);
421 m_availableApplications.append(application);
348
422
349 application = new ApplicationInfo(this);
423 application = new ApplicationInfo(this);
@@ -351,6 +425,7 @@ void ApplicationManager::buildListOfAvailableApplications()
351 application->setName("Weather");
425 application->setName("Weather");
352 application->setIcon(QUrl("weather"));
426 application->setIcon(QUrl("weather"));
353 application->setStage(ApplicationInfo::SideStage);
427 application->setStage(ApplicationInfo::SideStage);
428+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
354 generateQmlStrings(application);
429 generateQmlStrings(application);
355 m_availableApplications.append(application);
430 m_availableApplications.append(application);
356
431
@@ -359,6 +434,7 @@ void ApplicationManager::buildListOfAvailableApplications()
359 application->setName("Notepad");
434 application->setName("Notepad");
360 application->setIcon(QUrl("notepad"));
435 application->setIcon(QUrl("notepad"));
361 application->setStage(ApplicationInfo::SideStage);
436 application->setStage(ApplicationInfo::SideStage);
437+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
362 m_availableApplications.append(application);
438 m_availableApplications.append(application);
363
439
364 application = new ApplicationInfo(this);
440 application = new ApplicationInfo(this);
@@ -366,6 +442,7 @@ void ApplicationManager::buildListOfAvailableApplications()
366 application->setName("Calendar");
442 application->setName("Calendar");
367 application->setIcon(QUrl("calendar"));
443 application->setIcon(QUrl("calendar"));
368 application->setStage(ApplicationInfo::SideStage);
444 application->setStage(ApplicationInfo::SideStage);
445+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
369 m_availableApplications.append(application);
446 m_availableApplications.append(application);
370
447
371 application = new ApplicationInfo(this);
448 application = new ApplicationInfo(this);
@@ -373,18 +450,21 @@ void ApplicationManager::buildListOfAvailableApplications()
373 application->setName("Media Player");
450 application->setName("Media Player");
374 application->setIcon(QUrl("mediaplayer-app"));
451 application->setIcon(QUrl("mediaplayer-app"));
375 application->setFullscreen(true);
452 application->setFullscreen(true);
453+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
376 m_availableApplications.append(application);
454 m_availableApplications.append(application);
377
455
378 application = new ApplicationInfo(this);
456 application = new ApplicationInfo(this);
379 application->setAppId("evernote");
457 application->setAppId("evernote");
380 application->setName("Evernote");
458 application->setName("Evernote");
381 application->setIcon(QUrl("evernote"));
459 application->setIcon(QUrl("evernote"));
460+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
382 m_availableApplications.append(application);
461 m_availableApplications.append(application);
383
462
384 application = new ApplicationInfo(this);
463 application = new ApplicationInfo(this);
385 application->setAppId("map");
464 application->setAppId("map");
386 application->setName("Map");
465 application->setName("Map");
387 application->setIcon(QUrl("map"));
466 application->setIcon(QUrl("map"));
467+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
388 generateQmlStrings(application);
468 generateQmlStrings(application);
389 m_availableApplications.append(application);
469 m_availableApplications.append(application);
390
470
@@ -392,24 +472,28 @@ void ApplicationManager::buildListOfAvailableApplications()
392 application->setAppId("pinterest");
472 application->setAppId("pinterest");
393 application->setName("Pinterest");
473 application->setName("Pinterest");
394 application->setIcon(QUrl("pinterest"));
474 application->setIcon(QUrl("pinterest"));
475+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
395 m_availableApplications.append(application);
476 m_availableApplications.append(application);
396
477
397 application = new ApplicationInfo(this);
478 application = new ApplicationInfo(this);
398 application->setAppId("soundcloud");
479 application->setAppId("soundcloud");
399 application->setName("SoundCloud");
480 application->setName("SoundCloud");
400 application->setIcon(QUrl("soundcloud"));
481 application->setIcon(QUrl("soundcloud"));
482+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
401 m_availableApplications.append(application);
483 m_availableApplications.append(application);
402
484
403 application = new ApplicationInfo(this);
485 application = new ApplicationInfo(this);
404 application->setAppId("wikipedia");
486 application->setAppId("wikipedia");
405 application->setName("Wikipedia");
487 application->setName("Wikipedia");
406 application->setIcon(QUrl("wikipedia"));
488 application->setIcon(QUrl("wikipedia"));
489+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
407 m_availableApplications.append(application);
490 m_availableApplications.append(application);
408
491
409 application = new ApplicationInfo(this);
492 application = new ApplicationInfo(this);
410 application->setAppId("youtube");
493 application->setAppId("youtube");
411 application->setName("YouTube");
494 application->setName("YouTube");
412 application->setIcon(QUrl("youtube"));
495 application->setIcon(QUrl("youtube"));
496+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
413 m_availableApplications.append(application);
497 m_availableApplications.append(application);
414}
498}
415
499
@@ -484,3 +568,25 @@ void ApplicationManager::createSideStage()
484 m_sideStage->setParentItem(shell);
568 m_sideStage->setParentItem(shell);
485 m_sideStage->setFlag(QQuickItem::ItemHasContents, false);
569 m_sideStage->setFlag(QQuickItem::ItemHasContents, false);
486}
570}
571+
572+QStringList ApplicationManager::availableApplications()
573+{
574+ QStringList appIds;
575+ Q_FOREACH(ApplicationInfo *app, m_availableApplications) {
576+ appIds << app->appId();
577+ }
578+ return appIds;
579+}
580+
581+int ApplicationManager::rightMargin() const
582+{
583+ return m_rightMargin;
584+}
585+
586+void ApplicationManager::setRightMargin(int rightMargin)
587+{
588+ m_rightMargin = rightMargin;
589+ Q_FOREACH(ApplicationInfo *app, m_availableApplications) {
590+ generateQmlStrings(app);
591+ }
592+}
tests/mocks/Unity/Application/ApplicationManager.h+16
@@ -45,6 +45,10 @@ class ApplicationManager : public ApplicationManagerInterface {
45
45
46 Q_PROPERTY(bool fake READ fake CONSTANT)
46 Q_PROPERTY(bool fake READ fake CONSTANT)
47
47
48+ // Only for testing
49+ // This can be used to place some controls to right, like make tryPhoneStage for example
50+ Q_PROPERTY(int rightMargin READ rightMargin WRITE setRightMargin)
51+
48 public:
52 public:
49 ApplicationManager(QObject *parent = NULL);
53 ApplicationManager(QObject *parent = NULL);
50 virtual ~ApplicationManager();
54 virtual ~ApplicationManager();
@@ -87,13 +91,22 @@ class ApplicationManager : public ApplicationManagerInterface {
87 Q_INVOKABLE void move(int from, int to);
91 Q_INVOKABLE void move(int from, int to);
88
92
89 // Application control methods
93 // Application control methods
94+ Q_INVOKABLE bool requestFocusApplication(const QString &appId) override;
90 Q_INVOKABLE bool focusApplication(const QString &appId) override;
95 Q_INVOKABLE bool focusApplication(const QString &appId) override;
91 Q_INVOKABLE void unfocusCurrentApplication() override;
96 Q_INVOKABLE void unfocusCurrentApplication() override;
92 Q_INVOKABLE ApplicationInfo *startApplication(const QString &appId, const QStringList &arguments = QStringList()) override;
97 Q_INVOKABLE ApplicationInfo *startApplication(const QString &appId, const QStringList &arguments = QStringList()) override;
93 Q_INVOKABLE ApplicationInfo *startApplication(const QString &appId, ExecFlags flags, const QStringList &arguments = QStringList());
98 Q_INVOKABLE ApplicationInfo *startApplication(const QString &appId, ExecFlags flags, const QStringList &arguments = QStringList());
94 Q_INVOKABLE bool stopApplication(const QString &appId) override;
99 Q_INVOKABLE bool stopApplication(const QString &appId) override;
100+ Q_INVOKABLE bool updateScreenshot(const QString &appId) override;
95
101
96 QString focusedApplicationId() const override;
102 QString focusedApplicationId() const override;
103+ bool suspended() const;
104+ void setSuspended(bool suspended);
105+
106+ // Only for testing
107+ Q_INVOKABLE QStringList availableApplications();
108+ int rightMargin() const;
109+ void setRightMargin(int rightMargin);
97
110
98 Q_SIGNALS:
111 Q_SIGNALS:
99 void keyboardHeightChanged();
112 void keyboardHeightChanged();
@@ -113,12 +126,15 @@ class ApplicationManager : public ApplicationManagerInterface {
113 void createSideStage();
126 void createSideStage();
114 int m_keyboardHeight;
127 int m_keyboardHeight;
115 bool m_keyboardVisible;
128 bool m_keyboardVisible;
129+ bool m_suspended;
116 QList<ApplicationInfo*> m_runningApplications;
130 QList<ApplicationInfo*> m_runningApplications;
117 QList<ApplicationInfo*> m_availableApplications;
131 QList<ApplicationInfo*> m_availableApplications;
118 QQmlComponent *m_mainStageComponent;
132 QQmlComponent *m_mainStageComponent;
119 QQuickItem *m_mainStage;
133 QQuickItem *m_mainStage;
120 QQmlComponent *m_sideStageComponent;
134 QQmlComponent *m_sideStageComponent;
121 QQuickItem *m_sideStage;
135 QQuickItem *m_sideStage;
136+
137+ int m_rightMargin;
122};
138};
123
139
124Q_DECLARE_OPERATORS_FOR_FLAGS(ApplicationManager::ExecFlags)
140Q_DECLARE_OPERATORS_FOR_FLAGS(ApplicationManager::ExecFlags)
tests/mocks/Unity/Application/ApplicationScreenshotProvider.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 "ApplicationScreenshotProvider.h"
18+#include "ApplicationManager.h"
19+#include "ApplicationInfo.h"
20+
21+#include "paths.h"
22+
23+#include <QDebug>
24+#include <QGuiApplication>
25+#include <QWindow>
26+#include <QQuickWindow>
27+
28+ApplicationScreenshotProvider::ApplicationScreenshotProvider(ApplicationManager *appManager)
29+ : QQuickImageProvider(QQuickImageProvider::Image)
30+ , m_appManager(appManager)
31+{
32+}
33+
34+QImage ApplicationScreenshotProvider::requestImage(const QString &imageId, QSize * size,
35+ const QSize &requestedSize)
36+{
37+ // We ignore requestedSize here intentionally to avoid keeping scaled copies around
38+ Q_UNUSED(requestedSize)
39+
40+ QString appId = imageId.split('/').first();
41+
42+ ApplicationInfo* app = static_cast<ApplicationInfo*>(m_appManager->findApplication(appId));
43+ if (app == nullptr) {
44+ return QImage();
45+ }
46+
47+ QString filePath = QString("%1/Dash/graphics/phone/screenshots/%2@12.png").arg(qmlDirectory()).arg(app->icon().toString());
48+
49+ QImage image;
50+ if (!image.load(filePath)) {
51+ qWarning() << "failed loading app image" << filePath;
52+ }
53+
54+
55+ if (app->stage() == ApplicationInfo::SideStage) {
56+ QByteArray gus = qgetenv("GRID_UNIT_PX");
57+ image = image.scaledToWidth(gus.toInt() * 48);
58+ } else {
59+ // Lets scale main stage applications to be the size of the screen/window.
60+ QGuiApplication *unity = qobject_cast<QGuiApplication*>(qApp);
61+ Q_FOREACH (QWindow *win, unity->allWindows()) {
62+ QQuickWindow *quickWin = qobject_cast<QQuickWindow*>(win);
63+ if (quickWin) {
64+ image = image.scaledToWidth(quickWin->width() - m_appManager->rightMargin());
65+ break;
66+ }
67+ }
68+ }
69+
70+ size->setWidth(image.width());
71+ size->setHeight(image.height());
72+
73+ return image;
74+}
tests/mocks/Unity/Application/ApplicationScreenshotProvider.hnew file+34
@@ -0,0 +1,34 @@
1+/*
2+ * Copyright (C) 201 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 APPLICATIONSCREENSHOTPROVIDER_H
18+#define APPLICATIONSCREENSHOTPROVIDER_H
19+
20+#include <QQuickImageProvider>
21+
22+class ApplicationManager;
23+class ApplicationScreenshotProvider : public QQuickImageProvider
24+{
25+public:
26+ explicit ApplicationScreenshotProvider(ApplicationManager *appManager);
27+
28+ QImage requestImage(const QString &appId, QSize *size, const QSize &requestedSize) override;
29+
30+private:
31+ ApplicationManager* m_appManager;
32+};
33+
34+#endif // APPLICATIONSCREENSHOTPROVIDER_H
tests/mocks/Unity/Application/CMakeLists.txt+1
@@ -5,6 +5,7 @@ set(FakeUnityApplicationQml_SOURCES
5 ApplicationInfo.cpp
5 ApplicationInfo.cpp
6 ApplicationImage.cpp
6 ApplicationImage.cpp
7 ApplicationManager.cpp
7 ApplicationManager.cpp
8+ ApplicationScreenshotProvider.cpp
8 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
9 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
9 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
10 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
10)
11)
tests/mocks/Unity/Application/plugin.cpp+18−3
@@ -18,13 +18,20 @@
18#include "ApplicationInfo.h"
18#include "ApplicationInfo.h"
19#include "ApplicationImage.h"
19#include "ApplicationImage.h"
20#include "ApplicationManager.h"
20#include "ApplicationManager.h"
21+#include "ApplicationScreenshotProvider.h"
21
22
22#include <qqml.h>
23#include <qqml.h>
24+#include <QQmlEngine>
25+
26+ApplicationManager *s_appManager = 0;
23
27
24static QObject* applicationManagerSingleton(QQmlEngine* engine, QJSEngine* scriptEngine) {
28static QObject* applicationManagerSingleton(QQmlEngine* engine, QJSEngine* scriptEngine) {
25− Q_UNUSED(engine);
29+ Q_UNUSED(engine);
26− Q_UNUSED(scriptEngine);
30+ Q_UNUSED(scriptEngine);
31+ if (!s_appManager) {
27− return new ApplicationManager();
32+ s_appManager = new ApplicationManager();
33+ }
34+ return s_appManager;
28}
35}
29
36
30void FakeUnityApplicationQmlPlugin::registerTypes(const char *uri)
37void FakeUnityApplicationQmlPlugin::registerTypes(const char *uri)
@@ -37,3 +44,11 @@ void FakeUnityApplicationQmlPlugin::registerTypes(const char *uri)
37
44
38 qmlRegisterType<ApplicationImage>(uri, 0, 1, "ApplicationImage");
45 qmlRegisterType<ApplicationImage>(uri, 0, 1, "ApplicationImage");
39}
46}
47+
48+void FakeUnityApplicationQmlPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
49+{
50+ QQmlExtensionPlugin::initializeEngine(engine, uri);
51+
52+ ApplicationManager* appManager = static_cast<ApplicationManager*>(applicationManagerSingleton(engine, NULL));
53+ engine->addImageProvider(QLatin1String("application"), new ApplicationScreenshotProvider(appManager));
54+}
tests/mocks/Unity/Application/plugin.h+1
@@ -25,6 +25,7 @@ class FakeUnityApplicationQmlPlugin : public QQmlExtensionPlugin
25 Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
25 Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
26public:
26public:
27 void registerTypes(const char *uri);
27 void registerTypes(const char *uri);
28+ void initializeEngine(QQmlEngine *engine, const char *uri);
28};
29};
29
30
30#endif
31#endif
tests/plugins/DashViews/listviewwithpageheadertest.cpp−1
@@ -1855,7 +1855,6 @@ private Q_SLOTS:
1855 scrollToBottom();
1855 scrollToBottom();
1856 lvwph->showHeader();
1856 lvwph->showHeader();
1857 QTRY_VERIFY(!lvwph->m_contentYAnimation->isRunning());
1857 QTRY_VERIFY(!lvwph->m_contentYAnimation->isRunning());
1858− QTest::qWait(100); // Make sure stuff is stable
1859 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 100));
1858 QMetaObject::invokeMethod(model, "insertItem", Q_ARG(QVariant, 0), Q_ARG(QVariant, 100));
1860 model->setProperty(3, "size", 10);
1859 model->setProperty(3, "size", 10);
1861 model->setProperty(4, "size", 10);
1860 model->setProperty(4, "size", 10);
tests/plugins/DashViews/listviewwithpageheadertestsection.cpp+3−3
@@ -1680,12 +1680,11 @@ private Q_SLOTS:
1680 verifyItem(18, 700, 50., false, QString(), true);
1680 verifyItem(18, 700, 50., false, QString(), true);
1681 verifyItem(19, 750, 50., false, QString(), true);
1681 verifyItem(19, 750, 50., false, QString(), true);
1682 verifyItem(20, 800, 50., false, QString(), true);
1682 verifyItem(20, 800, 50., false, QString(), true);
1683+ QCOMPARE(lvwph->m_clipItem->y(), lvwph->contentY());
1683− QCOMPARE(lvwph->m_minYExtent, 5 * 1510./21. + 660 - 1970 + 50);
1684+ QCOMPARE(lvwph->m_minYExtent, 5 * 1510./21. + 660 - lvwph->contentY() + 50);
1684− QCOMPARE(lvwph->m_clipItem->y(), 1970.);
1685 QCOMPARE(lvwph->m_clipItem->clip(), false);
1685 QCOMPARE(lvwph->m_clipItem->clip(), false);
1686 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1686 QCOMPARE(lvwph->m_headerItem->y(), 0.);
1687 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1687 QCOMPARE(lvwph->m_headerItem->height(), 50.);
1688− QCOMPARE(lvwph->contentY(), 1970.);
1689 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1688 QCOMPARE(lvwph->m_headerItemShownHeight, 0.);
1690 }
1689 }
1691
1690
@@ -2108,6 +2107,7 @@ private Q_SLOTS:
2108 QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
2107 QVERIFY(QQuickItemPrivate::get(lvwph->m_topSectionItem)->culled);
2109
2108
2110 QTest::qWait(1);
2109 QTest::qWait(1);
2110+ QCoreApplication::instance()->processEvents();
2111
2111
2112 changeContentY(-15);
2112 changeContentY(-15);
2113
2113
tests/plugins/Unity/Launcher/launchermodeltest.cpp+5
@@ -39,6 +39,7 @@ public:
39 ApplicationInfoInterface::Stage stage() const { return ApplicationInfoInterface::MainStage; }
39 ApplicationInfoInterface::Stage stage() const { return ApplicationInfoInterface::MainStage; }
40 ApplicationInfoInterface::State state() const { return ApplicationInfoInterface::Running; }
40 ApplicationInfoInterface::State state() const { return ApplicationInfoInterface::Running; }
41 bool focused() const { return m_focused; }
41 bool focused() const { return m_focused; }
42+ QUrl screenshot() const { return QUrl(); }
42
43
43 // Methods used for mocking (not in the interface)
44 // Methods used for mocking (not in the interface)
44 void setFocused(bool focused) { m_focused = focused; Q_EMIT focusedChanged(focused); }
45 void setFocused(bool focused) { m_focused = focused; Q_EMIT focusedChanged(focused); }
@@ -92,6 +93,10 @@ public:
92 m_list.takeAt(index)->deleteLater();
93 m_list.takeAt(index)->deleteLater();
93 endRemoveRows();
94 endRemoveRows();
94 }
95 }
96+ bool updateScreenshot(const QString &appId) { Q_UNUSED(appId); return true; }
97+ bool requestFocusApplication(const QString &appId) { Q_UNUSED(appId); return true; }
98+ bool suspended() const { return false; }
99+ void setSuspended(bool) {}
95
100
96private:
101private:
97 QList<MockApp*> m_list;
102 QList<MockApp*> m_list;
tests/qmltests/CMakeLists.txt+2−2
@@ -13,7 +13,6 @@ set(qmltest_DEFAULT_TARGETS qmlunittests)
13set(qmltest_DEFAULT_NO_ADD_TEST FALSE)
13set(qmltest_DEFAULT_NO_ADD_TEST FALSE)
14set(qmltest_DEFAULT_PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=minimal")
14set(qmltest_DEFAULT_PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=minimal")
15add_qml_test(Components AnimationControllerWithSignals)
15add_qml_test(Components AnimationControllerWithSignals)
16−add_qml_test(Components Carousel)
17add_qml_test(Panel IndicatorItem)
16add_qml_test(Panel IndicatorItem)
18add_qml_test(utils/Unity/Test UnityTest)
17add_qml_test(utils/Unity/Test UnityTest)
19
18
@@ -24,6 +23,7 @@ set(qmltest_DEFAULT_PROPERTIES "")
24add_qml_test(. Shell)
23add_qml_test(. Shell)
25add_qml_test(. GreeterShell ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/liblightdm/single")
24add_qml_test(. GreeterShell ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/liblightdm/single")
26add_manual_qml_test(Components DragHandle IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
25add_manual_qml_test(Components DragHandle IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
26+add_qml_test(Components Carousel)
27add_qml_test(Components DraggingArea)
27add_qml_test(Components DraggingArea)
28add_qml_test(Components EdgeDemoOverlay)
28add_qml_test(Components EdgeDemoOverlay)
29add_qml_test(Components FilterGrid)
29add_qml_test(Components FilterGrid)
@@ -74,4 +74,4 @@ add_qml_test(Panel/Indicators DefaultIndicatorWidget ENVIRONMENT "LD_LIBRARY_PAT
74add_qml_test(Panel/Indicators DefaultIndicatorPage ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/QMenuModel")
74add_qml_test(Panel/Indicators DefaultIndicatorPage ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/QMenuModel")
75add_qml_test(Panel/Indicators MenuItemFactory)
75add_qml_test(Panel/Indicators MenuItemFactory)
76add_qml_test(Panel/Indicators MessageMenuItemFactory)
76add_qml_test(Panel/Indicators MessageMenuItemFactory)
77−add_qml_test(SideStage SideStage)
77+add_qml_test(Stages PhoneStage)
tests/qmltests/Components/tst_Carousel.qml+194−168
@@ -16,179 +16,205 @@
16
16
17import QtQuick 2.0
17import QtQuick 2.0
18import QtTest 1.0
18import QtTest 1.0
19+import "../../../qml/Components/"
19import "../../../qml/Components/carousel.js" as Carousel
20import "../../../qml/Components/carousel.js" as Carousel
20
21
21−TestCase {
22+Item {
22− id: root
23+ width: 700
23− name: "Carousel"
24+ height: 400
24−
25− property real carouselWidth
26− property int itemCount
27− property real tileWidth
28−
29− property real contentWidth: itemCount * tileWidth
30− // following variables are copied from Carousel.qml
31− // I'm not using the variables directly from there, as then tileWidth and others would be affected as well
32− property real gapToMiddlePhase: Math.min(carouselWidth / 2 - tileWidth / 2, (contentWidth - carouselWidth) / 2)
33− property real gapToEndPhase: contentWidth - carouselWidth - gapToMiddlePhase
34− property real kMiddleIndex: (carouselWidth / 2) / tileWidth - 0.5
35− property real kGapEnd: kMiddleIndex * (1 - gapToEndPhase / gapToMiddlePhase)
36− property real kXBeginningEnd: 1 / tileWidth + kMiddleIndex / gapToMiddlePhase
37−
38− // test for the getContinuousIndex() function
39− function test_getContinuousIndex_data() {
40− // testing for 10 items of size 100 pixel
41− return [ {tag:"at start",
42− x: 0, carouselWidth:400, tileWidth:100, itemCount:10, result: 0},
43− {tag:"in startup",
44− x: 100, carouselWidth:400, tileWidth:100, itemCount:10, result: 2},
45− {tag:"in the middle",
46− x: 350, carouselWidth:400, tileWidth:100, itemCount:10, result: 5},
47− {tag:"at end",
48− x: 600, carouselWidth:400, tileWidth:100, itemCount:10, result: 9},
49− ]
50− }
51
25
52− function test_getContinuousIndex(data) {
26+ Carousel {
53− root.carouselWidth = data.carouselWidth
27+ anchors.fill: parent
54− root.tileWidth = data.tileWidth
28+ id: carousel
55− root.itemCount = data.itemCount
29+ model: 40
56
30
57− var index = Carousel.getContinuousIndex(data.x,
31+ property int createdDelegates: 0
58− data.tileWidth,
32+ property int destroyedDelegates: 0
59− gapToMiddlePhase,
60− gapToEndPhase,
61− kGapEnd,
62− kMiddleIndex,
63− kXBeginningEnd)
64− compare(index, data.result)
65− }
66−
67− // test for the getXFromContinuousIndex() function
68− function test_getXFromContinuousIndex_data() {
69− return [ {tag:"at start",
70− index: 0, carouselWidth:400, tileWidth:100, itemCount:10, drawBuffer:0, result: 0},
71− {tag:"in startup",
72− index: 2, carouselWidth:400, tileWidth:100, itemCount:10, drawBuffer:0, result: 100},
73− {tag:"in startup with drawBuffer",
74− index: 2, carouselWidth:400, tileWidth:100, itemCount:10, drawBuffer:100, result: 0},
75− {tag:"in the middle",
76− index: 5, carouselWidth:400, tileWidth:100, itemCount:10, drawBuffer:0, result: 350},
77− {tag:"in the middle with drawBuffer",
78− index: 5, carouselWidth:400, tileWidth:100, itemCount:10, drawBuffer:100, result: 250},
79− {tag:"at end",
80− index: 9, carouselWidth:400, tileWidth:100, itemCount:10, drawBuffer:0, result: 600},
81− {tag:"at end with drawBuffer",
82− index: 9, carouselWidth:400, tileWidth:100, itemCount:10, drawBuffer:100, result: 500},
83− ]
84− }
85−
86− function test_getXFromContinuousIndex(data) {
87− root.carouselWidth = data.carouselWidth
88− root.tileWidth = data.tileWidth
89− root.itemCount = data.itemCount
90−
91− var x = Carousel.getXFromContinuousIndex(data.index,
92− data.carouselWidth,
93− contentWidth,
94− data.tileWidth,
95− gapToMiddlePhase,
96− gapToEndPhase,
97− data.drawBuffer)
98− compare(x, data.result)
99− }
100−
101− // test for the getViewTranslation() function
102− function test_getViewTranslation_data() {
103− return [ {tag:"at start - viewfactor 1",
104− x: 0, carouselWidth:400, tileWidth:100, itemCount:10, translationXViewFactor:1, result: 150},
105− {tag:"at start - viewfactor 2",
106− x: 0, carouselWidth:400, tileWidth:100, itemCount:10, translationXViewFactor:2, result: 300},
107− {tag:"in startup",
108− x: 100, carouselWidth:400, tileWidth:100, itemCount:10, translationXViewFactor:1, result: 50},
109− {tag:"in the middle",
110− x: 350, carouselWidth:400, tileWidth:100, itemCount:10, translationXViewFactor:1, result: 0},
111− {tag:"at end",
112− x: 600, carouselWidth:400, tileWidth:100, itemCount:10, translationXViewFactor:1, result: -150},
113− ]
114− }
115
33
116− function test_getViewTranslation(data) {
34+ itemComponent: Rectangle {
117− root.carouselWidth = data.carouselWidth
35+ color: "red"
118− root.tileWidth = data.tileWidth
36+ Component.onCompleted: carousel.createdDelegates++
119− root.itemCount = data.itemCount
37+ Component.onDestruction: carousel.destroyedDelegates++
120−
38+ }
121− var x = Carousel.getViewTranslation(data.x,
122− data.tileWidth,
123− root.gapToMiddlePhase,
124− root.gapToEndPhase,
125− data.translationXViewFactor)
126− compare(x, data.result)
127 }
39 }
128−
40+ TestCase {
41+ id: root
42+ name: "Carousel"
43+ when: windowShown
44+
45+ property real carouselWidth
46+ property int itemCount
47+ property real tileWidth
48+
49+ property real contentWidth: itemCount * tileWidth
50+ // following variables are copied from Carousel.qml
51+ // I'm not using the variables directly from there, as then tileWidth and others would be affected as well
52+ property real gapToMiddlePhase: Math.min(carouselWidth / 2 - tileWidth / 2, (contentWidth - carouselWidth) / 2)
53+ property real gapToEndPhase: contentWidth - carouselWidth - gapToMiddlePhase
54+ property real kMiddleIndex: (carouselWidth / 2) / tileWidth - 0.5
55+ property real kGapEnd: kMiddleIndex * (1 - gapToEndPhase / gapToMiddlePhase)
56+ property real kXBeginningEnd: 1 / tileWidth + kMiddleIndex / gapToMiddlePhase
57+
58+ function test_onlyNeededItemsCreated() {
59+ compare(carousel.createdDelegates, 10);
60+ compare(carousel.destroyedDelegates, 0);
61+ }
62+
63+ // test for the getContinuousIndex() function
64+ function test_getContinuousIndex_data() {
65+ // testing for 10 items of size 100 pixel
66+ return [ {tag:"at start",
67+ x: 0, carouselWidth:400, tileWidth:100, itemCount:10, result: 0},
68+ {tag:"in startup",
69+ x: 100, carouselWidth:400, tileWidth:100, itemCount:10, result: 2},
70+ {tag:"in the middle",
71+ x: 350, carouselWidth:400, tileWidth:100, itemCount:10, result: 5},
72+ {tag:"at end",
73+ x: 600, carouselWidth:400, tileWidth:100, itemCount:10, result: 9},
74+ ]
75+ }
76+
77+ function test_getContinuousIndex(data) {
78+ root.carouselWidth = data.carouselWidth
79+ root.tileWidth = data.tileWidth
80+ root.itemCount = data.itemCount
81+
82+ var index = Carousel.getContinuousIndex(data.x,
83+ data.tileWidth,
84+ gapToMiddlePhase,
85+ gapToEndPhase,
86+ kGapEnd,
87+ kMiddleIndex,
88+ kXBeginningEnd)
89+ compare(index, data.result)
90+ }
91+
92+ // test for the getXFromContinuousIndex() function
93+ function test_getXFromContinuousIndex_data() {
94+ return [ {tag:"at start",
95+ index: 0, carouselWidth:400, tileWidth:100, itemCount:10, drawBuffer:0, result: 0},
96+ {tag:"in startup",
97+ index: 2, carouselWidth:400, tileWidth:100, itemCount:10, drawBuffer:0, result: 100},
98+ {tag:"in startup with drawBuffer",
99+ index: 2, carouselWidth:400, tileWidth:100, itemCount:10, drawBuffer:100, result: 0},
100+ {tag:"in the middle",
101+ index: 5, carouselWidth:400, tileWidth:100, itemCount:10, drawBuffer:0, result: 350},
102+ {tag:"in the middle with drawBuffer",
103+ index: 5, carouselWidth:400, tileWidth:100, itemCount:10, drawBuffer:100, result: 250},
104+ {tag:"at end",
105+ index: 9, carouselWidth:400, tileWidth:100, itemCount:10, drawBuffer:0, result: 600},
106+ {tag:"at end with drawBuffer",
107+ index: 9, carouselWidth:400, tileWidth:100, itemCount:10, drawBuffer:100, result: 500},
108+ ]
109+ }
110+
111+ function test_getXFromContinuousIndex(data) {
112+ root.carouselWidth = data.carouselWidth
113+ root.tileWidth = data.tileWidth
114+ root.itemCount = data.itemCount
115+
116+ var x = Carousel.getXFromContinuousIndex(data.index,
117+ data.carouselWidth,
118+ contentWidth,
119+ data.tileWidth,
120+ gapToMiddlePhase,
121+ gapToEndPhase,
122+ data.drawBuffer)
123+ compare(x, data.result)
124+ }
125+
126+ // test for the getViewTranslation() function
127+ function test_getViewTranslation_data() {
128+ return [ {tag:"at start - viewfactor 1",
129+ x: 0, carouselWidth:400, tileWidth:100, itemCount:10, translationXViewFactor:1, result: 150},
130+ {tag:"at start - viewfactor 2",
131+ x: 0, carouselWidth:400, tileWidth:100, itemCount:10, translationXViewFactor:2, result: 300},
132+ {tag:"in startup",
133+ x: 100, carouselWidth:400, tileWidth:100, itemCount:10, translationXViewFactor:1, result: 50},
134+ {tag:"in the middle",
135+ x: 350, carouselWidth:400, tileWidth:100, itemCount:10, translationXViewFactor:1, result: 0},
136+ {tag:"at end",
137+ x: 600, carouselWidth:400, tileWidth:100, itemCount:10, translationXViewFactor:1, result: -150},
138+ ]
139+ }
140+
141+ function test_getViewTranslation(data) {
142+ root.carouselWidth = data.carouselWidth
143+ root.tileWidth = data.tileWidth
144+ root.itemCount = data.itemCount
145+
146+ var x = Carousel.getViewTranslation(data.x,
147+ data.tileWidth,
148+ root.gapToMiddlePhase,
149+ root.gapToEndPhase,
150+ data.translationXViewFactor)
151+ compare(x, data.result)
152+ }
153+
129− // test for the getItemScale() function
154+ // test for the getItemScale() function
130− function test_getItemScale_data() {
155+ function test_getItemScale_data() {
131− return [ // tests for distance
156+ return [ // tests for distance
132− {distance: 0, continuousIndex: 50, numberOfItems: 100, scaleFactor: 1, result: 1},
157+ {distance: 0, continuousIndex: 50, numberOfItems: 100, scaleFactor: 1, result: 1},
133− {distance: 9, continuousIndex: 50, numberOfItems: 100, scaleFactor: 1, result: 0.01},
158+ {distance: 9, continuousIndex: 50, numberOfItems: 100, scaleFactor: 1, result: 0.01},
134− {distance: 999, continuousIndex: 50, numberOfItems: 100, scaleFactor: 1, result: 0.01},
159+ {distance: 999, continuousIndex: 50, numberOfItems: 100, scaleFactor: 1, result: 0.01},
135− {distance: 1, continuousIndex: 50, numberOfItems: 100, scaleFactor: 1, result: 0.98}, // = 1 - (1 / 50)
160+ {distance: 1, continuousIndex: 50, numberOfItems: 100, scaleFactor: 1, result: 0.98}, // = 1 - (1 / 50)
136− {distance: 3, continuousIndex: 50, numberOfItems: 100, scaleFactor: 1, result: 0.688230855}, // = 1 - (3^2.5 / 50)
161+ {distance: 3, continuousIndex: 50, numberOfItems: 100, scaleFactor: 1, result: 0.688230855}, // = 1 - (3^2.5 / 50)
137− // tests for continuousIndex
162+ // tests for continuousIndex
138− {distance: 1, continuousIndex: 50, numberOfItems: 100, scaleFactor: 1, result: 0.98}, // = 1 - (1 / 50)
163+ {distance: 1, continuousIndex: 50, numberOfItems: 100, scaleFactor: 1, result: 0.98}, // = 1 - (1 / 50)
139− {distance: 1, continuousIndex: 97, numberOfItems: 100, scaleFactor: 1, result: 0.99}, // = 1 - (1 / 100) - distanceToBounds is used
164+ {distance: 1, continuousIndex: 97, numberOfItems: 100, scaleFactor: 1, result: 0.99}, // = 1 - (1 / 100) - distanceToBounds is used
140− // tests for numberOfItems
165+ // tests for numberOfItems
141− {distance: 1, continuousIndex: 50, numberOfItems: 100, scaleFactor: 1, result: 0.98}, // = 1 - (1 / 50)
166+ {distance: 1, continuousIndex: 50, numberOfItems: 100, scaleFactor: 1, result: 0.98}, // = 1 - (1 / 50)
142− {distance: 1, continuousIndex: 50, numberOfItems: 53, scaleFactor: 1, result: 0.99}, // = 1 - (1 / 100) - distanceToBounds is used
167+ {distance: 1, continuousIndex: 50, numberOfItems: 53, scaleFactor: 1, result: 0.99}, // = 1 - (1 / 100) - distanceToBounds is used
143− // tests for scaleFactor
168+ // tests for scaleFactor
144− {distance: 1, continuousIndex: 50, numberOfItems: 100, scaleFactor: 1, result: 0.98}, // = 1 - (1 / 50)
169+ {distance: 1, continuousIndex: 50, numberOfItems: 100, scaleFactor: 1, result: 0.98}, // = 1 - (1 / 50)
145− {distance: 1, continuousIndex: 50, numberOfItems: 100, scaleFactor: 2, result: 0.99}, // = 1 - (1 / 100)
170+ {distance: 1, continuousIndex: 50, numberOfItems: 100, scaleFactor: 2, result: 0.99}, // = 1 - (1 / 100)
146− {distance: 1, continuousIndex: 50, numberOfItems: 100, scaleFactor: 0.5, result: 0.96}, // = 1 - (1 / 25)
171+ {distance: 1, continuousIndex: 50, numberOfItems: 100, scaleFactor: 0.5, result: 0.96}, // = 1 - (1 / 25)
147− {distance: 1, continuousIndex: 50, numberOfItems: 53, scaleFactor: 1, result: 0.99}, // = 1 - (1 / 100) - distanceToBounds is used
172+ {distance: 1, continuousIndex: 50, numberOfItems: 53, scaleFactor: 1, result: 0.99}, // = 1 - (1 / 100) - distanceToBounds is used
148− {distance: 1, continuousIndex: 50, numberOfItems: 53, scaleFactor: 2, result: 0.996666666}, // = 1 - (1 / 300) - distanceToBounds is used
173+ {distance: 1, continuousIndex: 50, numberOfItems: 53, scaleFactor: 2, result: 0.996666666}, // = 1 - (1 / 300) - distanceToBounds is used
149− ]
174+ ]
150− }
175+ }
151−
176+
152− function test_getItemScale(data) {
177+ function test_getItemScale(data) {
153− var scale = Carousel.getItemScale(data.distance,
178+ var scale = Carousel.getItemScale(data.distance,
154− data.continuousIndex,
179+ data.continuousIndex,
155− data.numberOfItems,
180+ data.numberOfItems,
156− data.scaleFactor)
181+ data.scaleFactor)
157− compare(scale, data.result)
182+ compare(scale, data.result)
158− }
183+ }
159−
184+
160− // test for the getItemTranslation() function
185+ // test for the getItemTranslation() function
161− function test_getItemTranslation_data() {
186+ function test_getItemTranslation_data() {
162− return [ // tests for index and selectedIndex
187+ return [ // tests for index and selectedIndex
163− {index: 1, selectedIndex: 1, distance: 1, scale: 1, maxScale: 1, maxTranslation: 10, result: 0},
188+ {index: 1, selectedIndex: 1, distance: 1, scale: 1, maxScale: 1, maxTranslation: 10, result: 0},
164− // tests if distance only affects the sign
189+ // tests if distance only affects the sign
165− {index: 1, selectedIndex: 2, distance: 1, scale: 0, maxScale: 1, maxTranslation: 10, result: 10},
190+ {index: 1, selectedIndex: 2, distance: 1, scale: 0, maxScale: 1, maxTranslation: 10, result: 10},
166− {index: 1, selectedIndex: 2, distance: 99, scale: 0, maxScale: 1, maxTranslation: 10, result: 10},
191+ {index: 1, selectedIndex: 2, distance: 99, scale: 0, maxScale: 1, maxTranslation: 10, result: 10},
167− {index: 1, selectedIndex: 2, distance: -1, scale: 0, maxScale: 1, maxTranslation: 10, result: -10},
192+ {index: 1, selectedIndex: 2, distance: -1, scale: 0, maxScale: 1, maxTranslation: 10, result: -10},
168− {index: 1, selectedIndex: 2, distance: -99, scale: 0, maxScale: 1, maxTranslation: 10, result: -10},
193+ {index: 1, selectedIndex: 2, distance: -99, scale: 0, maxScale: 1, maxTranslation: 10, result: -10},
169− // tests for the scale
194+ // tests for the scale
170− {index: 1, selectedIndex: 2, distance: 1, scale: 1, maxScale: 1, maxTranslation: 10, result: 0},
195+ {index: 1, selectedIndex: 2, distance: 1, scale: 1, maxScale: 1, maxTranslation: 10, result: 0},
171− {index: 1, selectedIndex: 2, distance: 1, scale: 0, maxScale: 1, maxTranslation: 10, result: 10},
196+ {index: 1, selectedIndex: 2, distance: 1, scale: 0, maxScale: 1, maxTranslation: 10, result: 10},
172− {index: 1, selectedIndex: 2, distance: 1, scale: 0.5, maxScale: 1, maxTranslation: 10, result: 5},
197+ {index: 1, selectedIndex: 2, distance: 1, scale: 0.5, maxScale: 1, maxTranslation: 10, result: 5},
173− // tests for maxScale
198+ // tests for maxScale
174− {index: 1, selectedIndex: 2, distance: 1, scale: 0, maxScale: 0.98, maxTranslation: 10, result: 9.8},
199+ {index: 1, selectedIndex: 2, distance: 1, scale: 0, maxScale: 0.98, maxTranslation: 10, result: 9.8},
175− {index: 1, selectedIndex: 2, distance: 1, scale: 0.5, maxScale: 0.95, maxTranslation: 10, result: 4.5},
200+ {index: 1, selectedIndex: 2, distance: 1, scale: 0.5, maxScale: 0.95, maxTranslation: 10, result: 4.5},
176− {index: 1, selectedIndex: 2, distance: 1, scale: 0.8, maxScale: 0.93, maxTranslation: 10, result: 1.3},
201+ {index: 1, selectedIndex: 2, distance: 1, scale: 0.8, maxScale: 0.93, maxTranslation: 10, result: 1.3},
177− // test for maxTranslation
202+ // test for maxTranslation
178− {index: 1, selectedIndex: 2, distance: 1, scale: 1, maxScale: 1, maxTranslation: 1, result: 0},
203+ {index: 1, selectedIndex: 2, distance: 1, scale: 1, maxScale: 1, maxTranslation: 1, result: 0},
179− {index: 1, selectedIndex: 2, distance: 1, scale: 0, maxScale: 1, maxTranslation: 1, result: 1},
204+ {index: 1, selectedIndex: 2, distance: 1, scale: 0, maxScale: 1, maxTranslation: 1, result: 1},
180− {index: 1, selectedIndex: 2, distance: 1, scale: 0, maxScale: 1, maxTranslation: 10, result: 10},
205+ {index: 1, selectedIndex: 2, distance: 1, scale: 0, maxScale: 1, maxTranslation: 10, result: 10},
181− {index: 1, selectedIndex: 2, distance: 1, scale: 0, maxScale: 1, maxTranslation: 0, result: 0},
206+ {index: 1, selectedIndex: 2, distance: 1, scale: 0, maxScale: 1, maxTranslation: 0, result: 0},
182− ]
207+ ]
183− }
208+ }
184−
209+
185− function test_getItemTranslation(data) {
210+ function test_getItemTranslation(data) {
186− var scale = Carousel.getItemTranslation(data.index,
211+ var scale = Carousel.getItemTranslation(data.index,
187− data.selectedIndex,
212+ data.selectedIndex,
188− data.distance,
213+ data.distance,
189− data.scale,
214+ data.scale,
190− data.maxScale,
215+ data.maxScale,
191− data.maxTranslation)
216+ data.maxTranslation)
192− compare(scale, data.result)
217+ compare(scale, data.result)
218+ }
193 }
219 }
194}
220}
tests/qmltests/Components/tst_ResponsiveFlowView.qml+1−2
@@ -82,8 +82,7 @@ Item {
82 ResponsiveFlowView {
82 ResponsiveFlowView {
83 id: flow
83 id: flow
84 anchors.fill: parent
84 anchors.fill: parent
85− firstModel: fakeModel
86− secondModel: fakeModel
85+ model: fakeModel
87 minimumHorizontalSpacing:
86 minimumHorizontalSpacing:
88 minHSpacingSelector.values[minHSpacingSelector.selectedIndex]
87 minHSpacingSelector.values[minHSpacingSelector.selectedIndex]
89 verticalSpacing: units.gu(2)
88 verticalSpacing: units.gu(2)
tests/qmltests/Dash/Apps/tst_RunningApplicationsGrid.qml+50−117
@@ -18,87 +18,20 @@ import QtQuick 2.0
18import QtTest 1.0
18import QtTest 1.0
19import "../../../../qml/Dash/Apps"
19import "../../../../qml/Dash/Apps"
20import Unity.Test 0.1 as UT
20import Unity.Test 0.1 as UT
21+import Unity.Application 0.1
21
22
22−Item {
23+// Using Rectangle to have an opaque surface because AppManager paints app surfaces behind it.
24+Rectangle {
23 width: units.gu(50)
25 width: units.gu(50)
24 height: units.gu(40)
26 height: units.gu(40)
25
27
26− QtObject {
28+ function resetRunningApplications() {
27− id: fakeApplicationManager
29+ while (ApplicationManager.count > 0) {
28−
30+ ApplicationManager.stopApplication(ApplicationManager.get(0).appId)
29− property bool sideStageEnabled: false
30−
31− function stopApplication(appId) {
32− for (var i=0, len=fakeRunningAppsModel.count; i<len; i++) {
33− if (appId == fakeRunningAppsModel.get(i).appId) {
34− fakeRunningAppsModel.remove(i)
35− }
36− }
37− }
38− }
39−
40− QtObject {
41− id: shell
42− property bool dashShown: true
43− property bool stageScreenshotsReady: false
44− property var applicationManager: fakeApplicationManager
45−
46− function activateApplication(appId) {
47− }
48− }
49−
50− ListModel {
51− id: fakeRunningAppsModel
52−
53− function contains(appId) {
54− for (var i=0, len=fakeRunningAppsModel.count; i<len; i++) {
55− if (appId == fakeRunningAppsModel.get(i).appId) {
56− return true;
57− }
58− }
59− return false;
60− }
61−
62− function rePopulate() {
63− for (var i=0, len=availableAppsModel.count; i<len; i++) {
64− fakeRunningAppsModel.append(availableAppsModel.get(i));
65− }
66− }
67− }
68−
69− ListModel {
70− id: availableAppsModel
71− ListElement {
72− name: "Phone"
73− icon: "phone-app"
74− exec: "/usr/bin/phone-app"
75− appId: "phone"
76− imageQml: "import QtQuick 2.0\n \
77− Rectangle { \n \
78− anchors.fill:parent \n \
79− color:'darkgreen' \n \
80− Text { anchors.centerIn: parent; text: 'PHONE' } \n \
81− }"
82 }
31 }
83
32
84− ListElement {
33+ ApplicationManager.startApplication("phone-app");
85− name: "Calendar"
34+ ApplicationManager.startApplication("webbrowser-app");
86− icon: "calendar-app"
87− exec: "/usr/bin/calendar-app"
88− appId: "calendar"
89− imageQml: "import QtQuick 2.0\n \
90− Rectangle { \n \
91− anchors.fill:parent \n \
92− color:'darkblue' \n \
93− Text { anchors.centerIn: parent; text: 'CALENDAR'\n \
94− color:'white'} \n \
95− }"
96− }
97− }
98−
99− function resetRunningApplications() {
100− fakeRunningAppsModel.clear()
101− fakeRunningAppsModel.rePopulate()
102 }
35 }
103
36
104 Component.onCompleted: {
37 Component.onCompleted: {
@@ -109,7 +42,7 @@ Item {
109 RunningApplicationsGrid {
42 RunningApplicationsGrid {
110 id: runningApplicationsGrid
43 id: runningApplicationsGrid
111 anchors.fill: parent
44 anchors.fill: parent
112− firstModel: fakeRunningAppsModel
45+ model: ApplicationManager
113 }
46 }
114
47
115 UT.UnityTestCase {
48 UT.UnityTestCase {
@@ -121,11 +54,11 @@ Item {
121 resetRunningApplications()
54 resetRunningApplications()
122 }
55 }
123
56
124− property var calendarTile
57+ property var browserTile
125 property var phoneTile
58 property var phoneTile
126
59
127− property var isCalendarLongPressed: false
60+ property var isBrowserLongPressed: false
128− function onCalendarLongPressed() {isCalendarLongPressed = true}
61+ function onBrowserLongPressed() {isBrowserLongPressed = true}
129
62
130 property var isPhoneLongPressed: false
63 property var isPhoneLongPressed: false
131 function onPhoneLongPressed() {isPhoneLongPressed = true}
64 function onPhoneLongPressed() {isPhoneLongPressed = true}
@@ -133,25 +66,25 @@ Item {
133 // Tiles should go to termination mode when any one of them is long-pressed.
66 // Tiles should go to termination mode when any one of them is long-pressed.
134 // Long-pressing when they're in termination mode brings them back to activation mode
67 // Long-pressing when they're in termination mode brings them back to activation mode
135 function test_enterTerminationMode() {
68 function test_enterTerminationMode() {
136− calendarTile = findChild(runningApplicationsGrid, "runningAppTile Calendar")
69+ browserTile = findChild(runningApplicationsGrid, "runningAppTile Browser")
137− verify(calendarTile != undefined)
70+ verify(browserTile != undefined)
138− calendarTile.onPressAndHold.connect(onCalendarLongPressed)
71+ browserTile.onPressAndHold.connect(onBrowserLongPressed)
139
72
140 phoneTile = findChild(runningApplicationsGrid, "runningAppTile Phone")
73 phoneTile = findChild(runningApplicationsGrid, "runningAppTile Phone")
141 verify(phoneTile != undefined)
74 verify(phoneTile != undefined)
142 phoneTile.onPressAndHold.connect(onPhoneLongPressed)
75 phoneTile.onPressAndHold.connect(onPhoneLongPressed)
143
76
144− compare(calendarTile.terminationModeEnabled, false)
77+ compare(browserTile.terminationModeEnabled, false)
145 compare(phoneTile.terminationModeEnabled, false)
78 compare(phoneTile.terminationModeEnabled, false)
146 compare(runningApplicationsGrid.terminationModeEnabled, false)
79 compare(runningApplicationsGrid.terminationModeEnabled, false)
147
80
148− isCalendarLongPressed = false
81+ isBrowserLongPressed = false
149− mousePress(calendarTile, calendarTile.width/2, calendarTile.height/2)
82+ mousePress(browserTile, browserTile.width/2, browserTile.height/2)
150 tryCompareFunction(checkSwitchToTerminationModeAfterLongPress, true)
83 tryCompareFunction(checkSwitchToTerminationModeAfterLongPress, true)
151
84
152− mouseRelease(calendarTile, calendarTile.width/2, calendarTile.height/2)
85+ mouseRelease(browserTile, browserTile.width/2, browserTile.height/2)
153
86
154− compare(calendarTile.terminationModeEnabled, true)
87+ compare(browserTile.terminationModeEnabled, true)
155 compare(phoneTile.terminationModeEnabled, true)
88 compare(phoneTile.terminationModeEnabled, true)
156 compare(runningApplicationsGrid.terminationModeEnabled, true)
89 compare(runningApplicationsGrid.terminationModeEnabled, true)
157
90
@@ -161,23 +94,23 @@ Item {
161
94
162 mouseRelease(phoneTile, phoneTile.width/2, phoneTile.height/2)
95 mouseRelease(phoneTile, phoneTile.width/2, phoneTile.height/2)
163
96
164− compare(calendarTile.terminationModeEnabled, false)
97+ compare(browserTile.terminationModeEnabled, false)
165 compare(phoneTile.terminationModeEnabled, false)
98 compare(phoneTile.terminationModeEnabled, false)
166 compare(runningApplicationsGrid.terminationModeEnabled, false)
99 compare(runningApplicationsGrid.terminationModeEnabled, false)
167
100
168− calendarTile.onPressAndHold.disconnect(onCalendarLongPressed)
101+ browserTile.onPressAndHold.disconnect(onBrowserLongPressed)
169 phoneTile.onPressAndHold.disconnect(onPhoneLongPressed)
102 phoneTile.onPressAndHold.disconnect(onPhoneLongPressed)
170 }
103 }
171
104
172 // Checks that components swicth to termination mode after (and only after) a long
105 // Checks that components swicth to termination mode after (and only after) a long
173− // press happens on Calendar tile.
106+ // press happens on Browser tile.
174 function checkSwitchToTerminationModeAfterLongPress() {
107 function checkSwitchToTerminationModeAfterLongPress() {
175− compare(calendarTile.terminationModeEnabled, isCalendarLongPressed)
108+ compare(browserTile.terminationModeEnabled, isBrowserLongPressed)
176− compare(phoneTile.terminationModeEnabled, isCalendarLongPressed)
109+ compare(phoneTile.terminationModeEnabled, isBrowserLongPressed)
177− compare(runningApplicationsGrid.terminationModeEnabled, isCalendarLongPressed)
110+ compare(runningApplicationsGrid.terminationModeEnabled, isBrowserLongPressed)
178
111
179− return isCalendarLongPressed &&
112+ return isBrowserLongPressed &&
180− calendarTile.terminationModeEnabled &&
113+ browserTile.terminationModeEnabled &&
181 phoneTile.terminationModeEnabled &&
114 phoneTile.terminationModeEnabled &&
182 runningApplicationsGrid.terminationModeEnabled
115 runningApplicationsGrid.terminationModeEnabled
183 }
116 }
@@ -185,12 +118,12 @@ Item {
185 // Checks that components swicth to activation mode after (and only after) a long
118 // Checks that components swicth to activation mode after (and only after) a long
186 // press happens on Phone tile.
119 // press happens on Phone tile.
187 function checkSwitchToActivationModeAfterLongPress() {
120 function checkSwitchToActivationModeAfterLongPress() {
188− compare(calendarTile.terminationModeEnabled, !isPhoneLongPressed)
121+ compare(browserTile.terminationModeEnabled, !isPhoneLongPressed)
189 compare(phoneTile.terminationModeEnabled, !isPhoneLongPressed)
122 compare(phoneTile.terminationModeEnabled, !isPhoneLongPressed)
190 compare(runningApplicationsGrid.terminationModeEnabled, !isPhoneLongPressed)
123 compare(runningApplicationsGrid.terminationModeEnabled, !isPhoneLongPressed)
191
124
192 return isPhoneLongPressed &&
125 return isPhoneLongPressed &&
193− !calendarTile.terminationModeEnabled &&
126+ !browserTile.terminationModeEnabled &&
194 !phoneTile.terminationModeEnabled &&
127 !phoneTile.terminationModeEnabled &&
195 !runningApplicationsGrid.terminationModeEnabled
128 !runningApplicationsGrid.terminationModeEnabled
196 }
129 }
@@ -200,17 +133,17 @@ Item {
200 function test_clickTileNotClose() {
133 function test_clickTileNotClose() {
201 runningApplicationsGrid.terminationModeEnabled = true
134 runningApplicationsGrid.terminationModeEnabled = true
202
135
203− var calendarTile = findChild(runningApplicationsGrid, "runningAppTile Calendar")
136+ var browserTile = findChild(runningApplicationsGrid, "runningAppTile Browser")
204− verify(calendarTile != undefined)
137+ verify(browserTile != undefined)
205
138
206− verify(fakeRunningAppsModel.contains("calendar"))
139+ verify(ApplicationManager.findApplication("webbrowser-app") !== null)
207
140
208− mouseClick(calendarTile, calendarTile.width/2, calendarTile.height/2)
141+ mouseClick(browserTile, browserTile.width/2, browserTile.height/2)
209
142
210− verify(fakeRunningAppsModel.contains("calendar"))
143+ verify(ApplicationManager.findApplication("webbrowser-app") !== null)
211
144
212− // The tile for the Calendar app should stay there
145+ // The tile for the Browser app should stay there
213− tryCompareFunction(checkCalendarTileExists, true)
146+ tryCompareFunction(checkBrowserTileExists, true)
214 }
147 }
215
148
216 // While in termination mode, clicking on a running application tile's close icon
149 // While in termination mode, clicking on a running application tile's close icon
@@ -218,25 +151,25 @@ Item {
218 function test_clickCloseIconToTerminateApp() {
151 function test_clickCloseIconToTerminateApp() {
219 runningApplicationsGrid.terminationModeEnabled = true
152 runningApplicationsGrid.terminationModeEnabled = true
220
153
221− var calendarTile = findChild(runningApplicationsGrid, "runningAppTile Calendar")
154+ var browserTile = findChild(runningApplicationsGrid, "runningAppTile Browser")
222− var calendarTileCloseButton = findChild(runningApplicationsGrid, "closeIcon Calendar")
155+ var browserTileCloseButton = findChild(runningApplicationsGrid, "closeIcon Browser")
223
156
224− verify(calendarTile != undefined)
157+ verify(browserTile != undefined)
225− verify(calendarTileCloseButton != undefined)
158+ verify(browserTileCloseButton != undefined)
226− verify(fakeRunningAppsModel.contains("calendar"))
159+ verify(ApplicationManager.findApplication("webbrowser-app") !== 0)
227
160
228− mouseClick(calendarTileCloseButton, calendarTileCloseButton.width/2, calendarTileCloseButton.height/2)
161+ mouseClick(browserTileCloseButton, browserTileCloseButton.width/2, browserTileCloseButton.height/2)
229 wait(0) // spin event loop to start any pending animation
162 wait(0) // spin event loop to start any pending animation
230
163
231− verify(!fakeRunningAppsModel.contains("calendar"))
164+ verify(ApplicationManager.findApplication("webbrowser-app") === null)
232
165
233− // The tile for the Calendar app should eventually vanish since the
166+ // The tile for the Browser app should eventually vanish since the
234 // application has been terminated.
167 // application has been terminated.
235− tryCompareFunction(checkCalendarTileExists, false)
168+ tryCompareFunction(checkBrowserTileExists, false)
236 }
169 }
237
170
238− function checkCalendarTileExists() {
171+ function checkBrowserTileExists() {
239− return findChild(runningApplicationsGrid, "runningAppTile Calendar")
172+ return findChild(runningApplicationsGrid, "runningAppTile Browser")
240 != undefined
173 != undefined
241 }
174 }
242
175
@@ -245,8 +178,8 @@ Item {
245 function test_clickOutsideTilesDisablesTerminationMode() {
178 function test_clickOutsideTilesDisablesTerminationMode() {
246 runningApplicationsGrid.terminationModeEnabled = true
179 runningApplicationsGrid.terminationModeEnabled = true
247
180
248− var calendarTile = findChild(runningApplicationsGrid, "runningAppTile Calendar")
181+ var browserTile = findChild(runningApplicationsGrid, "runningAppTile Browser")
249− verify(calendarTile != undefined)
182+ verify(browserTile != undefined)
250
183
251 verify(runningApplicationsGrid.terminationModeEnabled);
184 verify(runningApplicationsGrid.terminationModeEnabled);
252
185
tests/qmltests/Dash/tst_GenericScopeView.qml+12−1
@@ -105,11 +105,22 @@ Item {
105 tryCompare(previewListView, "open", false);
105 tryCompare(previewListView, "open", false);
106 }
106 }
107
107
108+ function test_searchQuery() {
109+ genericScopeView.scope = scopes.get(0);
110+ genericScopeView.scope.searchQuery = "test";
111+ genericScopeView.scope = scopes.get(1);
112+ genericScopeView.scope.searchQuery = "test2";
113+ genericScopeView.scope = scopes.get(0);
114+ tryCompare(genericScopeView.scope, "searchQuery", "test");
115+ genericScopeView.scope = scopes.get(1);
116+ tryCompare(genericScopeView.scope, "searchQuery", "test2");
117+ }
118+
108 function test_changeScope() {
119 function test_changeScope() {
109 genericScopeView.scope.searchQuery = "test"
120 genericScopeView.scope.searchQuery = "test"
110 genericScopeView.scope = scopes.get(1)
121 genericScopeView.scope = scopes.get(1)
111 genericScopeView.scope = scopes.get(2)
122 genericScopeView.scope = scopes.get(2)
112− tryCompare(genericScopeView.scope, "searchQuery", "")
123+ tryCompare(genericScopeView.scope, "searchQuery", "test")
113 }
124 }
114
125
115 function test_filter_expand_collapse() {
126 function test_filter_expand_collapse() {
tests/qmltests/Notifications/tst_VisualSnapDecisionsQueue.qml+1
@@ -193,6 +193,7 @@ Row {
193 addSomeSnapDecisionNotifications();
193 addSomeSnapDecisionNotifications();
194
194
195 // make sure the view is properly updated before going on
195 // make sure the view is properly updated before going on
196+ notifications.forceLayout();
196 waitForRendering(notifications);
197 waitForRendering(notifications);
197
198
198 var snap_decision = [findChild(notifications, "notification1"),
199 var snap_decision = [findChild(notifications, "notification1"),
tests/qmltests/Stages/tst_PhoneStage.qmlnew file+288
@@ -0,0 +1,288 @@
1+/*
2+ * Copyright 2014 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import QtTest 1.0
19+import Unity.Test 0.1 as UT
20+import ".."
21+import "../../../qml/Stages"
22+import Ubuntu.Components 0.1
23+import Unity.Application 0.1
24+
25+Item {
26+ width: units.gu(70)
27+ height: units.gu(70)
28+
29+ Rectangle {
30+
31+ }
32+
33+ PhoneStage {
34+ id: phoneStage
35+ anchors { fill: parent; rightMargin: units.gu(30) }
36+ shown: true
37+ dragAreaWidth: units.gu(2)
38+ }
39+
40+ Binding {
41+ target: ApplicationManager
42+ property: "rightMargin"
43+ value: phoneStage.anchors.rightMargin
44+ }
45+
46+ Rectangle {
47+ anchors { fill: parent; leftMargin: phoneStage.width }
48+// color: "blue"
49+
50+ Column {
51+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
52+ spacing: units.gu(1)
53+ Button {
54+ anchors { left: parent.left; right: parent.right }
55+ text: "Add App"
56+ onClicked: {
57+ testCase.addApps();
58+ }
59+ }
60+ Button {
61+ anchors { left: parent.left; right: parent.right }
62+ text: "Add App"
63+ }
64+ }
65+ }
66+
67+ UT.UnityTestCase {
68+ id: testCase
69+ name: "PhoneStage"
70+ when: windowShown
71+
72+ function addApps(count) {
73+ if (count == undefined) count = 1;
74+ for (var i = 0; i < count; i++) {
75+ var app = ApplicationManager.startApplication(ApplicationManager.availableApplications()[ApplicationManager.count])
76+ tryCompare(app, "state", ApplicationInfoInterface.Running)
77+ // Fixme: Right now there is a timeout in the PhoneStage that displays a white splash
78+ // screen rectangle when an app starts. This is because we don't yet have a way of
79+ // knowing when an app has finished launching. That workaround and this wait() should
80+ // go away at some point and the app's state only changing to Running when ready for real.
81+// wait(1000)
82+ waitForRendering(phoneStage)
83+ }
84+ }
85+
86+ function goToSpread() {
87+ var spreadView = findChild(phoneStage, "spreadView");
88+
89+ var startX = phoneStage.width;
90+ var startY = phoneStage.height / 2;
91+ var endY = startY;
92+ var endX = units.gu(2);
93+
94+ touchFlick(phoneStage, startX, startY, endX, endY,
95+ true /* beginTouch */, true /* endTouch */, units.gu(10), 50);
96+ }
97+
98+ function test_shortFlick() {
99+ addApps(2)
100+ var startX = phoneStage.width - units.gu(1);
101+ var startY = phoneStage.height / 2;
102+ var endX = phoneStage.width / 2;
103+ var endY = startY;
104+
105+ var activeApp = ApplicationManager.get(0);
106+ var inactiveApp = ApplicationManager.get(1);
107+
108+ touchFlick(phoneStage, startX, startY, endX, endY,
109+ true /* beginTouch */, true /* endTouch */, units.gu(10), 50);
110+
111+ tryCompare(ApplicationManager, "focusedApplicationId", inactiveApp.appId)
112+
113+ touchFlick(phoneStage, startX, startY, endX, endY,
114+ true /* beginTouch */, true /* endTouch */, units.gu(10), 50);
115+
116+ tryCompare(ApplicationManager, "focusedApplicationId", activeApp.appId)
117+
118+ tryCompare(phoneStage, "painting", false);
119+ }
120+
121+ function test_enterSpread_data() {
122+ return [
123+ {tag: "<position1 (linear movement)", positionMarker: "positionMarker1", linear: true, offset: -1, endPhase: 0, targetPhase: 0, newFocusedIndex: 1 },
124+ {tag: "<position1 (non-linear movement)", positionMarker: "positionMarker1", linear: false, offset: -1, endPhase: 0, targetPhase: 0, newFocusedIndex: 0 },
125+ {tag: ">position1", positionMarker: "positionMarker1", linear: true, offset: +1, endPhase: 0, targetPhase: 0, newFocusedIndex: 1 },
126+ {tag: "<position2 (linear)", positionMarker: "positionMarker2", linear: true, offset: -1, endPhase: 0, targetPhase: 0, newFocusedIndex: 1 },
127+ {tag: "<position2 (non-linear)", positionMarker: "positionMarker2", linear: false, offset: -1, endPhase: 0, targetPhase: 0, newFocusedIndex: 1 },
128+ {tag: ">position2", positionMarker: "positionMarker2", linear: true, offset: +1, endPhase: 1, targetPhase: 0, newFocusedIndex: 1 },
129+ {tag: "<position3", positionMarker: "positionMarker3", linear: true, offset: -1, endPhase: 1, targetPhase: 0, newFocusedIndex: 1 },
130+ {tag: ">position3", positionMarker: "positionMarker3", linear: true, offset: +1, endPhase: 2, targetPhase: 2, newFocusedIndex: 2 },
131+ ];
132+ }
133+
134+ function test_enterSpread(data) {
135+ addApps(5)
136+
137+ var spreadView = findChild(phoneStage, "spreadView");
138+
139+ var startX = phoneStage.width;
140+ var startY = phoneStage.height / 2;
141+ var endY = startY;
142+ var endX = spreadView.width - (spreadView.width * spreadView[data.positionMarker]) - data.offset - phoneStage.dragAreaWidth;
143+
144+ var oldFocusedApp = ApplicationManager.get(0);
145+ var newFocusedApp = ApplicationManager.get(data.newFocusedIndex);
146+
147+ touchFlick(phoneStage, startX, startY, endX, endY,
148+ true /* beginTouch */, false /* endTouch */, units.gu(10), 50);
149+
150+ tryCompare(spreadView, "phase", data.endPhase)
151+
152+ if (!data.linear) {
153+ touchFlick(phoneStage, endX, endY, endX + units.gu(.5), endY,
154+ false /* beginTouch */, false /* endTouch */, units.gu(10), 50);
155+ touchFlick(phoneStage, endY + units.gu(.5), endY, endX, endY,
156+ false /* beginTouch */, false /* endTouch */, units.gu(10), 50);
157+ }
158+
159+ touchRelease(phoneStage, endX, endY);
160+
161+ tryCompare(spreadView, "phase", data.targetPhase)
162+
163+ if (data.targetPhase == 2) {
164+ var app2 = findChild(spreadView, "appDelegate2");
165+ mouseClick(app2, units.gu(1), units.gu(1));
166+ }
167+
168+ tryCompare(phoneStage, "painting", false);
169+ tryCompare(ApplicationManager, "focusedApplicationId", newFocusedApp.appId);
170+ }
171+
172+ function test_selectAppFromSpread_data() {
173+ var appsToTest = 6;
174+ var apps = new Array();
175+ for (var i = 0; i < appsToTest; i++) {
176+ var item = new Object();
177+ item.tag = "App " + i;
178+ item.index = i;
179+ item.total = appsToTest;
180+ apps.push(item)
181+ }
182+ return apps;
183+ }
184+
185+ function test_selectAppFromSpread(data) {
186+ addApps(data.total)
187+
188+ var spreadView = findChild(phoneStage, "spreadView");
189+
190+ goToSpread();
191+
192+ tryCompare(spreadView, "phase", 2);
193+
194+ var tile = findChild(spreadView, "appDelegate" + data.index);
195+ var appId = ApplicationManager.get(data.index).appId;
196+
197+ if (tile.mapToItem(spreadView).x > spreadView.width) {
198+ // Item is not visible... Need to flick the spread
199+ var startX = phoneStage.width - units.gu(1);
200+ var startY = phoneStage.height / 2;
201+ var endY = startY;
202+ var endX = units.gu(2);
203+ touchFlick(phoneStage, startX, startY, endX, endY, true, true, units.gu(10), 50)
204+ tryCompare(spreadView, "flicking", false);
205+ tryCompare(spreadView, "moving", false);
206+// waitForRendering(phoneStage);
207+ }
208+
209+ console.log("clicking app", data.index, "(", appId, ")")
210+ mouseClick(spreadView, tile.mapToItem(spreadView).x + units.gu(1), spreadView.height / 2)
211+ tryCompare(ApplicationManager, "focusedApplicationId", appId);
212+ tryCompare(spreadView, "phase", 0);
213+ }
214+
215+ function test_animateAppStartup() {
216+ compare(phoneStage.painting, false);
217+ addApps(2);
218+ tryCompare(phoneStage, "painting", true);
219+ tryCompare(phoneStage, "painting", false);
220+ addApps(1);
221+ tryCompare(phoneStage, "painting", true);
222+ tryCompare(phoneStage, "painting", false);
223+ }
224+
225+ function test_select_data() {
226+ return [
227+ { tag: "0", index: 0 },
228+ { tag: "2", index: 2 },
229+ { tag: "4", index: 4 },
230+ ]
231+ }
232+
233+ function test_select(data) {
234+ addApps(5);
235+
236+ var spreadView = findChild(phoneStage, "spreadView");
237+ var selectedApp = ApplicationManager.get(data.index);
238+
239+ goToSpread();
240+
241+ phoneStage.select(selectedApp.appId);
242+
243+ tryCompare(phoneStage, "painting", false);
244+ compare(ApplicationManager.focusedApplicationId, selectedApp.appId);
245+ }
246+
247+ function test_fullscreenMode() {
248+ var fullscreenApp = null;
249+ var normalApp = null;
250+
251+ for (var i = 0; i < 5; i++) {
252+ addApps(1);
253+ var newApp = ApplicationManager.get(0);
254+ tryCompare(phoneStage, "fullscreen", newApp.fullscreen);
255+ if (newApp.fullscreen && fullscreenApp == null) {
256+ fullscreenApp = newApp;
257+ } else if (!newApp.fullscreen && normalApp == null){
258+ normalApp = newApp;
259+ }
260+ }
261+ verify(fullscreenApp != null); // Can't continue the test without having a fullscreen app
262+ verify(normalApp != null); // Can't continue the test without having a non-fullscreen app
263+
264+ // Select a normal app
265+ goToSpread();
266+ phoneStage.select(normalApp.appId);
267+ tryCompare(phoneStage, "fullscreen", false);
268+
269+ // Select a fullscreen app
270+ goToSpread();
271+ phoneStage.select(fullscreenApp.appId);
272+ tryCompare(phoneStage, "fullscreen", true);
273+
274+ // Select a normal app
275+ goToSpread();
276+ phoneStage.select(normalApp.appId);
277+ tryCompare(phoneStage, "fullscreen", false);
278+ }
279+
280+ function cleanup() {
281+ while (ApplicationManager.count > 0) {
282+ var oldCount = ApplicationManager.count;
283+ ApplicationManager.stopApplication(ApplicationManager.get(0).appId)
284+ tryCompare(ApplicationManager, "count", oldCount - 1)
285+ }
286+ }
287+ }
288+}
tests/qmltests/tst_Shell.qml+18−9
@@ -108,6 +108,7 @@ Item {
108 while (apps.count > 0) {
108 while (apps.count > 0) {
109 ApplicationManager.stopApplication(apps.get(0).appId);
109 ApplicationManager.stopApplication(apps.get(0).appId);
110 }
110 }
111+ compare(ApplicationManager.count, 0)
111 }
112 }
112
113
113 /*
114 /*
@@ -176,24 +177,33 @@ Item {
176 tapOnAppIconInLauncher();
177 tapOnAppIconInLauncher();
177 waitUntilApplicationWindowIsFullyVisible();
178 waitUntilApplicationWindowIsFullyVisible();
178
179
179− var mainApp = ApplicationManager.focusedApplicationId;
180+ var mainAppId = ApplicationManager.focusedApplicationId;
180− verify(mainApp != "");
181+ verify(mainAppId != "");
182+ var mainApp = ApplicationManager.findApplication(mainAppId);
183+ verify(mainApp);
184+ tryCompare(mainApp, "state", ApplicationInfo.Running);
181
185
182 // Try to suspend while proximity is engaged...
186 // Try to suspend while proximity is engaged...
183 Powerd.displayPowerStateChange(Powerd.Off, Powerd.UseProximity);
187 Powerd.displayPowerStateChange(Powerd.Off, Powerd.UseProximity);
184− tryCompare(ApplicationManager, "focusedApplicationId", mainApp);
188+ tryCompare(SessionManager, "active", true);
185
189
186 // Now really suspend
190 // Now really suspend
191+ print("suspending")
187 Powerd.displayPowerStateChange(Powerd.Off, 0);
192 Powerd.displayPowerStateChange(Powerd.Off, 0);
193+ print("done suspending")
194+ tryCompare(SessionManager, "active", false);
195+
188− tryCompare(ApplicationManager, "focusedApplicationId", "");
196+ tryCompare(ApplicationManager, "suspended", true);
197+ compare(mainApp.state, ApplicationInfo.Suspended);
189
198
190 // And wake up
199 // And wake up
191 Powerd.displayPowerStateChange(Powerd.On, 0);
200 Powerd.displayPowerStateChange(Powerd.On, 0);
192− tryCompare(ApplicationManager, "focusedApplicationId", "");
193
201
194 // Activate session to focus app
202 // Activate session to focus app
195 SessionManager.active = true;
203 SessionManager.active = true;
196− tryCompare(ApplicationManager, "focusedApplicationId", mainApp);
204+ tryCompare(ApplicationManager, "suspended", false);
205+ compare(mainApp.state, ApplicationInfo.Running);
206+ tryCompare(ApplicationManager, "focusedApplicationId", mainAppId);
197 }
207 }
198
208
199 /*
209 /*
@@ -214,7 +224,7 @@ Item {
214 tryCompare(dash, "opacity", 1.0);
224 tryCompare(dash, "opacity", 1.0);
215
225
216 touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY,
226 touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY,
217− true /* beginTouch */, false /* endTouch */);
227+ true /* beginTouch */, false /* endTouch */, units.gu(10), 50);
218
228
219 // check that Dash has been scaled down and had its opacity reduced
229 // check that Dash has been scaled down and had its opacity reduced
220 tryCompareFunction(function() { return dash.contentScale <= 0.9; }, true);
230 tryCompareFunction(function() { return dash.contentScale <= 0.9; }, true);
@@ -245,7 +255,7 @@ Item {
245 tryCompare(dash, "opacity", 1.0);
255 tryCompare(dash, "opacity", 1.0);
246
256
247 touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY,
257 touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY,
248− true /* beginTouch */, false /* endTouch */);
258+ true /* beginTouch */, false /* endTouch */, units.gu(10), 50);
249
259
250 // check that Dash has been scaled down and had its opacity reduced
260 // check that Dash has been scaled down and had its opacity reduced
251 tryCompareFunction(function() { return dash.contentScale <= 0.9; }, true);
261 tryCompareFunction(function() { return dash.contentScale <= 0.9; }, true);
@@ -460,7 +470,6 @@ Item {
460 }
470 }
461
471
462 function test_DashShown(data) {
472 function test_DashShown(data) {
463−
464 if (data.app) {
473 if (data.app) {
465 dragLauncherIntoView();
474 dragLauncherIntoView();
466 tapOnAppIconInLauncher();
475 tapOnAppIconInLauncher();
tests/uqmlscene/main.cpp+13
@@ -341,6 +341,16 @@ static void loadDummyDataFiles(QQmlEngine &engine, const QString& directory)
341 }
341 }
342}
342}
343
343
344+static QObject *s_testRootObject = nullptr;
345+static QObject *testRootObject(QQmlEngine *engine, QJSEngine *jsEngine)
346+{
347+ Q_UNUSED(jsEngine);
348+ if (!s_testRootObject) {
349+ s_testRootObject = new QObject(engine);
350+ }
351+ return s_testRootObject;
352+}
353+
344static void usage()
354static void usage()
345{
355{
346 qWarning("Usage: uqmlscene [options] <filename>");
356 qWarning("Usage: uqmlscene [options] <filename>");
@@ -470,6 +480,9 @@ int main(int argc, char ** argv)
470 loadDummyDataFiles(engine, fi.path());
480 loadDummyDataFiles(engine, fi.path());
471 }
481 }
472 QObject::connect(&engine, SIGNAL(quit()), QCoreApplication::instance(), SLOT(quit()));
482 QObject::connect(&engine, SIGNAL(quit()), QCoreApplication::instance(), SLOT(quit()));
483+
484+ qmlRegisterSingletonType<QObject>("Qt.test.qtestroot", 1, 0, "QTestRootObject", testRootObject);
485+
473 component->loadUrl(options.file);
486 component->loadUrl(options.file);
474 if ( !component->isReady() ) {
487 if ( !component->isReady() ) {
475 qWarning("%s", qPrintable(component->errorString()));
488 qWarning("%s", qPrintable(component->errorString()));