on xenial help sign in
Merge with trunk
commit5970c90fcbe2d07531349fcf51243638d55db24f authorMirco Müller <mirco.mueller@ubuntu.com> dateMon 4 Aug 2014, 22:33 (12 years ago) parent4920ee17577e0d
115 files changed+4633−2569
cmake/modules/QmlTest.cmake+1−1 debian/changelog+62 debian/control+3−3 plugins/AccountsService/AccountsService.cpp+21−1 plugins/AccountsService/AccountsService.h+13 plugins/AccountsService/plugin.cpp+1 plugins/Dash/CardAttributes.qml+65 plugins/Dash/CardCreator.js+76−7 plugins/Dash/qmldir+1 plugins/LightDM/plugin.cpp+2 plugins/Utils/CMakeLists.txt+1 plugins/Utils/easingcurve.cpp+5−1 plugins/Utils/plugin.cpp+2 plugins/Utils/windowkeysfilter.cpp+57 plugins/Utils/windowkeysfilter.h+51 po/da.po+7−7 po/fa.po+51−49 po/hu.po+8−8 po/is.po+6−6 po/km.po+7−7 po/pl.po+7−7 po/ru.po+18−18 po/sv.po+6−6 po/ug.po+1−1 po/uk.po+4−4 po/zh_CN.po+1−1 qml/Components/Dialogs.qml+217 qml/Components/DraggingArea.qml+4 qml/Components/InputMethod.qml+79 qml/Components/PageHeader.qml+27−8 qml/Components/PassphraseLockscreen.qml+3 qml/Dash/Apps/CloseIcon.qml−62 qml/Dash/Apps/RunningApplicationTile.qml−128 qml/Dash/Apps/RunningApplicationsGrid.qml−105 qml/Dash/Apps/graphics/close_btn@9.png qml/Dash/CardTool.qml+27−2 qml/Dash/DashApps.qml−40 qml/Dash/DashContent.qml+5−3 qml/Dash/GenericScopeView.qml+1−10 qml/Dash/graphics/phone/screenshots/vkb_portrait.png qml/Launcher/Launcher.qml+1−1 qml/Launcher/LauncherDelegate.qml+9−6 qml/Launcher/graphics/notification@30.png qml/Launcher/graphics/notification@30.sci−5 qml/Panel/Indicators.qml+3 qml/Panel/Indicators/DefaultIndicatorPage.qml+1−1 qml/Shell.qml+183−399 qml/Stages/Animations/BaseSurfaceAnimation.qml+93 qml/Stages/Animations/DarkenAndFadeInAnimation.qml+48 qml/Stages/Animations/SwipeFromBottomAnimation.qml+48 qml/Stages/Animations/SwipeUpAnimation.qml+49 qml/Stages/PhoneStage.qml+171−263 qml/Stages/Splash.qml+68 qml/Stages/SpreadDelegate.qml+187−23 qml/Stages/StageWithSideStage.qml−413 qml/Stages/SurfaceContainer.qml+123 qml/Stages/SwitchingApplicationImage.qml−81 qml/Stages/TabletStage.qml+568 qml/Stages/TransformedSpreadDelegate.qml+36−30 qml/Stages/TransformedTabletSpreadDelegate.qml+369 run.sh+15−6 src/CMakeLists.txt−3 src/main.cpp+21−89 tests/autopilot/unity8/shell/emulators/dash.py−4 tests/autopilot/unity8/shell/tests/test_emulators.py+1−1 tests/autopilot/unity8/shell/tests/test_upstart.py+18−3 tests/mocks/AccountsService/AccountsService.cpp+12−2 tests/mocks/AccountsService/AccountsService.h+12 tests/mocks/AccountsService/CMakeLists.txt+4−1 tests/mocks/AccountsService/plugin.cpp+1 tests/mocks/LightDM/GreeterPrivate.h+2 tests/mocks/LightDM/demo/CMakeLists.txt+4−2 tests/mocks/LightDM/demo/GreeterPrivate.cpp+202−30 tests/mocks/LightDM/demo/UsersModelPrivate.cpp+1−1 tests/mocks/LightDM/full/GreeterPrivate.cpp+1−1 tests/mocks/LightDM/single-pin/GreeterPrivate.cpp+1−1 tests/mocks/LightDM/single-pin/UsersModelPrivate.cpp+1−1 tests/mocks/Unity/Application/Application.qmltypes−2 tests/mocks/Unity/Application/ApplicationDBusAdaptor.cpp+75 tests/mocks/Unity/Application/ApplicationDBusAdaptor.h+43 tests/mocks/Unity/Application/ApplicationInfo.cpp+69−21 tests/mocks/Unity/Application/ApplicationInfo.h+21−8 tests/mocks/Unity/Application/ApplicationManager.cpp+60−31 tests/mocks/Unity/Application/ApplicationManager.h+15−9 tests/mocks/Unity/Application/CMakeLists.txt+6−2 tests/mocks/Unity/Application/MirSurfaceItem.cpp+172 tests/mocks/Unity/Application/MirSurfaceItem.h+121 tests/mocks/Unity/Application/SurfaceManager.cpp+73 tests/mocks/Unity/Application/SurfaceManager.h+54 tests/mocks/Unity/Application/VirtualKeyboard.cpp+55 qml/Stages/SidestageHandle.qml → tests/mocks/Unity/Application/VirtualKeyboard.h+19−6 tests/mocks/Unity/Application/plugin.cpp+16−9 tests/mocks/Unity/CMakeLists.txt+1−1 tests/mocks/Unity/fake_scopes.cpp+5 tests/mocks/Unity/fake_scopes.h+1 tests/plugins/Dash/CMakeLists.txt+1 tests/plugins/Dash/cardcreator/1.res+63−61 tests/plugins/Dash/cardcreator/2.res+94−91 tests/plugins/Dash/cardcreator/3.res+77−74 tests/plugins/Dash/cardcreator/5.res+119−117 tests/plugins/Dash/cardcreator/7.res+128 tests/plugins/Dash/cardcreator/7.tst+3 tests/plugins/Dash/cardcreatortest.cpp+9−1 tests/plugins/Dash/listviewwithpageheadersectiontest.cpp−1 tests/plugins/Dash/tst_CardAttributes.qml+54 tests/qmltests/CMakeLists.txt−1 tests/qmltests/Components/tst_PageHeader.qml+36−6 tests/qmltests/Dash/Apps/tst_RunningApplicationsGrid.qml−196 tests/qmltests/Dash/CardHelpers.js+5−3 tests/qmltests/Dash/tst_Card.qml+4−2 tests/qmltests/Greeter/tst_Lockscreen.qml+11−5 tests/qmltests/Launcher/tst_Launcher.qml+11−2 tests/qmltests/Panel/tst_ActiveCallHint.qml+1 tests/qmltests/Stages/tst_PhoneStage.qml+9−56 tests/qmltests/tst_Shell.qml+109−12
cmake/modules/QmlTest.cmake+1−1
@@ -135,7 +135,7 @@ macro(add_binary_qml_test CLASS_NAME LD_PATH DEPS)
135135 set(testCommand
136136 LD_LIBRARY_PATH=${LD_PATH}
137137 ${CMAKE_CURRENT_BINARY_DIR}/${CLASS_NAME}TestExec
138− -o ${CMAKE_BINARY_DIR}/${CLASSNAME}Test.xml,xunitxml
138+ -o ${CMAKE_BINARY_DIR}/${CLASS_NAME}Test.xml,xunitxml
139139 -o -,txt)
140140
141141 add_qmltest_target(test${CLASS_NAME} "${testCommand}" FALSE TRUE)
debian/changelog+62
@@ -1,3 +1,65 @@
1+unity8 (8.00+14.10.20140731.1-0ubuntu1) utopic; urgency=low
2+
3+ [ Gerry Boland ]
4+ * Fix the run.sh script - pretend to be running with qtmir and emit
5+ SIGSTOP at the right time
6+
7+ [ Ying-Chun Liu ]
8+ * Implement Attribute UI. (LP: #1282460)
9+
10+ [ Albert Astals ]
11+ * Hide search history popup as soon as you start typing As discussed
12+ with Mike and Saviq
13+ * Compile with for scopes-v3 unity-api
14+ * PageHeader: Unfocus search field when search entry is selected
15+ * Show search field if the search query changes
16+ * Test: Add a condition for art.height being > 0 means stuff has
17+ already been layouted a bit without it it can happen that we get 0
18+ for everything at startup and tests still pass
19+ * Remove leftover in test of an old headerless implementation
20+
21+ [ Michael Zanetti ]
22+ * Drop Recent apps category from Dash (LP: #1281092)
23+ * update launcher count emblems to match new spec (LP: #1338984)
24+
25+ [ Bill Filler ]
26+ * disable predictive text for dash search field (LP: #1340409)
27+
28+ [ CI bot ]
29+ * Resync trunk
30+
31+ [ Antti Kaijanmäki ]
32+ * DefaultIndicatorPage: use Loader status to determine the visible
33+ property. (LP: #1350555)
34+
35+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Thu, 31 Jul 2014 16:51:01 +0000
36+
37+unity8 (8.00+14.10.20140729.1-0ubuntu1) utopic; urgency=low
38+
39+ [ Michael Terry ]
40+ * Check user's pin/password using PAM, instead of a plaintext keyfile.
41+ New build dependency: libpam0g-dev for phone unlock with PAM (LP:
42+ #1234983)
43+
44+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Tue, 29 Jul 2014 23:36:30 +0000
45+
46+unity8 (8.00+14.10.20140729-0ubuntu1) utopic; urgency=medium
47+
48+ [ Gerry Boland ]
49+ [ Daniel d'Andrada ]
50+ * Re-architecture unity8 to use the QtMirCompositor library so that
51+ the Qt scenegraph renderer is used as the Mir compositor, and
52+ application surfaces are added to the QML scene as items.
53+
54+ [ Michael Zanetti ]
55+ * Port phone right-edge spread code to use QtCompositor
56+ * Add right-edge spread animation for tablet
57+
58+ [ Ubuntu daily release ]
59+ * New rebuild forced
60+
61+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Tue, 29 Jul 2014 15:07:32 +0000
62+
163unity8 (7.90+14.10.20140725-0ubuntu1) utopic; urgency=low
264
365 [ CI bot ]
debian/control+3−3
@@ -19,12 +19,12 @@ Build-Depends: cmake,
1919 libgsettings-qt-dev,
2020 libhardware-dev,
2121 libhud-client2-dev,
22+ libpam0g-dev,
2223 libpay2-dev,
2324 libpulse-dev,
2425 libqmenumodel-dev (>= 0.2.8),
2526 libqt5xmlpatterns5-dev,
26− libunity-api-dev (>= 7.85),
27− libunity-mir-dev,
27+ libunity-api-dev (>= 7.87),
2828 libusermetricsoutput1-dev,
2929 libxcb1-dev,
3030 pkg-config,
@@ -80,10 +80,10 @@ Provides: indicator-renderer,
8080Depends: gsettings-desktop-schemas,
8181 libcap2-bin,
8282 libglib2.0-bin,
83− libunity-mir1 (>= 0.4),
8483 qmenumodel-qml (>= 0.2.8),
8584 qml-module-qtquick-xmllistmodel,
8685 qtdeclarative5-gsettings1.0,
86+ qtdeclarative5-qtmir-plugin (>= 0.4),
8787 qtdeclarative5-ubuntu-settings-components (>= 0.3),
8888 qtdeclarative5-ubuntu-telephony0.1,
8989 unity-launcher-impl-3,
plugins/AccountsService/AccountsService.cpp+21−1
@@ -26,7 +26,8 @@ AccountsService::AccountsService(QObject* parent)
2626 m_service(new AccountsServiceDBusAdaptor(this)),
2727 m_user(qgetenv("USER")),
2828 m_demoEdges(false),
29− m_statsWelcomeScreen(false)
29+ m_statsWelcomeScreen(false),
30+ m_passwordDisplayHint(Keyboard)
3031{
3132 connect(m_service, SIGNAL(propertiesChanged(const QString &, const QString &, const QStringList &)),
3233 this, SLOT(propertiesChanged(const QString &, const QString &, const QStringList &)));
@@ -47,6 +48,7 @@ void AccountsService::setUser(const QString &user)
4748 updateDemoEdges();
4849 updateBackgroundFile();
4950 updateStatsWelcomeScreen();
51+ updatePasswordDisplayHint();
5052}
5153
5254bool AccountsService::demoEdges() const
@@ -70,6 +72,11 @@ bool AccountsService::statsWelcomeScreen() const
7072 return m_statsWelcomeScreen;
7173}
7274
75+AccountsService::PasswordDisplayHint AccountsService::passwordDisplayHint() const
76+{
77+ return m_passwordDisplayHint;
78+}
79+
7380void AccountsService::updateDemoEdges()
7481{
7582 auto demoEdges = m_service->getUserProperty(m_user, "com.canonical.unity.AccountsService", "demo-edges").toBool();
@@ -97,6 +104,15 @@ void AccountsService::updateStatsWelcomeScreen()
97104 }
98105}
99106
107+void AccountsService::updatePasswordDisplayHint()
108+{
109+ PasswordDisplayHint passwordDisplayHint = (PasswordDisplayHint)m_service->getUserProperty(m_user, "com.ubuntu.AccountsService.SecurityPrivacy", "PasswordDisplayHint").toInt();
110+ if (m_passwordDisplayHint != passwordDisplayHint) {
111+ m_passwordDisplayHint = passwordDisplayHint;
112+ Q_EMIT passwordDisplayHintChanged();
113+ }
114+}
115+
100116void AccountsService::propertiesChanged(const QString &user, const QString &interface, const QStringList &changed)
101117{
102118 if (m_user != user) {
@@ -111,6 +127,10 @@ void AccountsService::propertiesChanged(const QString &user, const QString &inte
111127 if (changed.contains("StatsWelcomeScreen")) {
112128 updateStatsWelcomeScreen();
113129 }
130+ } else if (interface == "com.ubuntu.AccountsService.SecurityPrivacy") {
131+ if (changed.contains("PasswordDisplayHint")) {
132+ updatePasswordDisplayHint();
133+ }
114134 }
115135}
116136
plugins/AccountsService/AccountsService.h+13
@@ -27,6 +27,7 @@ class AccountsServiceDBusAdaptor;
2727class AccountsService: public QObject
2828{
2929 Q_OBJECT
30+ Q_ENUMS(PasswordDisplayHint)
3031 Q_PROPERTY (QString user
3132 READ user
3233 WRITE setUser
@@ -41,8 +42,16 @@ class AccountsService: public QObject
4142 Q_PROPERTY (bool statsWelcomeScreen
4243 READ statsWelcomeScreen
4344 NOTIFY statsWelcomeScreenChanged)
45+ Q_PROPERTY (PasswordDisplayHint passwordDisplayHint
46+ READ passwordDisplayHint
47+ NOTIFY passwordDisplayHintChanged)
4448
4549public:
50+ enum PasswordDisplayHint {
51+ Keyboard,
52+ Numeric,
53+ };
54+
4655 explicit AccountsService(QObject *parent = 0);
4756
4857 QString user() const;
@@ -51,12 +60,14 @@ public:
5160 void setDemoEdges(bool demoEdges);
5261 QString backgroundFile() const;
5362 bool statsWelcomeScreen() const;
63+ PasswordDisplayHint passwordDisplayHint() const;
5464
5565Q_SIGNALS:
5666 void userChanged();
5767 void demoEdgesChanged();
5868 void backgroundFileChanged();
5969 void statsWelcomeScreenChanged();
70+ void passwordDisplayHintChanged();
6071
6172private Q_SLOTS:
6273 void propertiesChanged(const QString &user, const QString &interface, const QStringList &changed);
@@ -66,12 +77,14 @@ private:
6677 void updateDemoEdges();
6778 void updateBackgroundFile();
6879 void updateStatsWelcomeScreen();
80+ void updatePasswordDisplayHint();
6981
7082 AccountsServiceDBusAdaptor *m_service;
7183 QString m_user;
7284 bool m_demoEdges;
7385 QString m_backgroundFile;
7486 bool m_statsWelcomeScreen;
87+ PasswordDisplayHint m_passwordDisplayHint;
7588};
7689
7790#endif
plugins/AccountsService/plugin.cpp+1
@@ -33,5 +33,6 @@ void AccountsServicePlugin::registerTypes(const char *uri)
3333{
3434 Q_ASSERT(uri == QLatin1String("AccountsService"));
3535 qDBusRegisterMetaType<QList<QVariantMap>>();
36+ qRegisterMetaType<AccountsService::PasswordDisplayHint>("AccountsService::PasswordDisplayHint");
3637 qmlRegisterSingletonType<AccountsService>(uri, 0, 1, "AccountsService", service_provider);
3738}
plugins/Dash/CardAttributes.qmlnew file+65
@@ -0,0 +1,65 @@
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.2
18+import QtQuick.Layouts 1.1
19+import Ubuntu.Components 1.1
20+import Ubuntu.Settings.Components 0.1
21+
22+GridLayout {
23+ id: grid
24+ anchors {
25+ left: parent.left;
26+ right: parent.right;
27+ }
28+ columns: 2 + repeater.count % 2
29+ property alias model: repeater.model
30+ property var color
31+
32+ Repeater {
33+ id: repeater
34+ delegate: Row {
35+ spacing: units.gu(0.5)
36+ readonly property int column: index % grid.columns;
37+ Layout.alignment: {
38+ if (column == 0) return Qt.AlignLeft;
39+ if (column == grid.columns - 1 || index == repeater.count - 1) return Qt.AlignRight;
40+ if (column == 1) return Qt.AlignHCenter;
41+ }
42+ Layout.columnSpan: index == repeater.count - 1 && grid.columns == 3 && column == 1 ? 2 : 1
43+ Layout.maximumWidth: Math.max(icon.width, label.x + label.implicitWidth)
44+ Layout.fillWidth: true
45+ StatusIcon {
46+ id: icon
47+ height: units.gu(2)
48+ sets: ["actions", "status", "apps"]
49+ source: "icon" in modelData ? modelData["icon"] : ""
50+ }
51+ Label {
52+ id: label
53+ width: parent.width - x
54+ anchors.verticalCenter: parent.verticalCenter
55+ text: "value" in modelData ? modelData["value"] : "";
56+ elide: Text.ElideRight
57+ maximumLineCount: 1
58+ font.weight: "style" in modelData && modelData["style"] == "highlighted" ? Font.DemiBold : Font.Light
59+ fontSize: "small"
60+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale)
61+ color: grid.color
62+ }
63+ }
64+ }
65+}
plugins/Dash/CardCreator.js+76−7
@@ -193,6 +193,21 @@ var kHeaderColumnCode = 'Column { \n\
193193 ] \n\
194194 }\n';
195195
196+// multiple column version of kHeaderColumnCode.
197+function kHeaderColumnCodeGenerator() {
198+ var headerColumnCodeTemplate = 'Column { \n\
199+ anchors.verticalCenter: parent.verticalCenter; \n\
200+ spacing: units.dp(2); \n\
201+ width: parent.width - x;\n\
202+ data: [ \n\
203+ %1 \n\
204+ ]\n\
205+ }\n';
206+ var args = Array.prototype.slice.call(arguments);
207+ var code = headerColumnCodeTemplate.arg(args.join(',\n'));
208+ return code;
209+}
210+
196211// %1 is used as anchors of mascotShapeLoader
197212var kMascotShapeLoaderCode = 'Loader { \n\
198213 id: mascotShapeLoader; \n\
@@ -224,7 +239,7 @@ var kMascotImageCode = 'Image { \n\
224239 }\n';
225240
226241// %1 is used as anchors of titleLabel
227−// %1 is used as color of titleLabel
242+// %2 is used as color of titleLabel
228243// %3 is used as extra condition for visible of titleLabel
229244var kTitleLabelCode = 'Label { \n\
230245 id: titleLabel; \n\
@@ -258,6 +273,15 @@ var kSubtitleLabelCode = 'Label { \n\
258273 horizontalAlignment: root.headerAlignment; \n\
259274 }\n';
260275
276+// %1 is used as anchors of attributesRow
277+var kAttributesRowCode = 'CardAttributes { \n\
278+ id: attributesRow; \n\
279+ objectName: "attributesRow"; \n\
280+ anchors { %1 } \n\
281+ color: %2; \n\
282+ model: cardData && cardData["attributes"] || undefined; \n\
283+ }\n';
284+
261285// %1 is used as top anchor of summary
262286// %2 is used as topMargin anchor of summary
263287// %3 is used as color of summary
@@ -308,6 +332,7 @@ function cardString(template, components) {
308332 var headerAsOverlay = hasArt && template && template["overlay"] === true && (hasTitle || hasMascot);
309333 var hasSubtitle = hasTitle && components["subtitle"] || false;
310334 var hasHeaderRow = hasMascot && hasTitle;
335+ var hasAttributes = hasTitle && components["attributes"] || false;
311336
312337 if (hasBackground) {
313338 code += kBackgroundLoaderCode;
@@ -377,6 +402,14 @@ function cardString(template, components) {
377402 code += 'readonly property int headerHeight: row.height;\n'
378403 } else if (hasMascot) {
379404 code += 'readonly property int headerHeight: mascotImage.height;\n'
405+ } else if (hasAttributes) {
406+ if (hasTitle && hasSubtitle) {
407+ code += 'readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin + attributesRow.height + attributesRow.anchors.topMargin;\n'
408+ } else if (hasTitle) {
409+ code += 'readonly property int headerHeight: titleLabel.height + attributesRow.height + attributesRow.anchors.topMargin;\n'
410+ } else {
411+ code += 'readonly property int headerHeight: attributesRow.height;\n'
412+ }
380413 } else if (hasSubtitle) {
381414 code += 'readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;\n'
382415 } else if (hasTitle) {
@@ -425,10 +458,12 @@ function cardString(template, components) {
425458
426459 var titleAnchors;
427460 var subtitleAnchors;
428− if (hasMascot && hasSubtitle) {
461+ var attributesAnchors;
462+ if (hasMascot && (hasSubtitle || hasAttributes)) {
429463 // Using row + column
430464 titleAnchors = 'left: parent.left; right: parent.right';
431465 subtitleAnchors = titleAnchors;
466+ attributesAnchors = subtitleAnchors;
432467 } else if (hasMascot) {
433468 // Using row + label
434469 titleAnchors = 'verticalCenter: parent.verticalCenter;\n'
@@ -438,11 +473,13 @@ function cardString(template, components) {
438473 titleAnchors = 'left: parent.left; \n\
439474 leftMargin: units.gu(1); \n\
440475 right: parent.right; \n\
476+ rightMargin: units.gu(1); \n\
441477 top: overlayLoader.top; \n\
442478 topMargin: units.gu(1);\n';
443479 } else {
444480 // Using anchors to the mascot/parent
445− titleAnchors = "right: parent.right;";
481+ titleAnchors = "right: parent.right;\n";
482+ titleAnchors += "rightMargin: units.gu(1);\n";
446483 titleAnchors += headerLeftAnchor;
447484 titleAnchors += headerVerticalAnchors;
448485 if (!headerLeftAnchorHasMargin) {
@@ -452,8 +489,19 @@ function cardString(template, components) {
452489 subtitleAnchors = 'left: titleLabel.left; \n\
453490 leftMargin: titleLabel.leftMargin; \n\
454491 right: titleLabel.right; \n\
492+ rightMargin: titleLabel.rightMargin; \n\
455493 top: titleLabel.bottom; \n\
456494 topMargin: units.dp(2);\n';
495+ if (hasSubtitle) {
496+ attributesAnchors = 'left: subtitleLabel.left; \n\
497+ leftMargin: subtitleLabel.leftMargin; \n\
498+ right: subtitleLabel.right; \n\
499+ rightMargin: subtitleLabel.rightMargin; \n\
500+ top: subtitleLabel.bottom; \n\
501+ topMargin: units.dp(2);\n';
502+ } else {
503+ attributesAnchors = subtitleAnchors;
504+ }
457505 }
458506
459507 var titleLabelVisibleExtra = (headerAsOverlay ? '&& overlayLoader.active': '');
@@ -463,11 +511,27 @@ function cardString(template, components) {
463511 subtitleCode += kSubtitleLabelCode.arg(subtitleAnchors).arg(color);
464512 }
465513
466− if (hasMascot && hasSubtitle) {
514+ if (hasMascot && (hasSubtitle || hasAttributes)) {
467515 // If using row + column wrap the code in the column
468516 titleSubtitleCode = kHeaderColumnCode.arg(titleCode).arg(subtitleCode);
517+ if (hasSubtitle && hasAttributes) {
518+ var attributesCode = kAttributesRowCode.arg(attributesAnchors).arg(color);
519+ titleSubtitleCode = kHeaderColumnCodeGenerator(titleCode, subtitleCode, attributesCode);
520+ } else if (hasSubtitle) {
521+ titleSubtitleCode = kHeaderColumnCode.arg(titleCode).arg(subtitleCode);
522+ } else if (hasAttributes) {
523+ var attributesCode = kAttributesRowCode.arg(attributesAnchors).arg(color);
524+ titleSubtitleCode = kHeaderColumnCode.arg(titleCode).arg(attributesCode);
525+ }
469526 } else {
470− titleSubtitleCode = titleCode + subtitleCode;
527+ titleSubtitleCode = titleCode;
528+ if (hasSubtitle) {
529+ titleSubtitleCode = titleSubtitleCode + subtitleCode;
530+ }
531+ if (hasAttributes) {
532+ var attributesCode = kAttributesRowCode.arg(attributesAnchors).arg(color);
533+ titleSubtitleCode = titleSubtitleCode + attributesCode;
534+ }
471535 }
472536 }
473537
@@ -487,6 +551,7 @@ function cardString(template, components) {
487551 else if (headerAsOverlay && hasArt) summaryTopAnchor = "artShapeHolder.bottom";
488552 else if (hasHeaderRow) summaryTopAnchor = "row.bottom";
489553 else if (hasMascot) summaryTopAnchor = "mascotImage.bottom";
554+ else if (hasAttributes) summaryTopAnchor = "attributesRow.bottom";
490555 else if (hasSubtitle) summaryTopAnchor = "subtitleLabel.bottom";
491556 else if (hasTitle) summaryTopAnchor = "titleLabel.bottom";
492557 else if (hasArt) summaryTopAnchor = "artShapeHolder.bottom";
@@ -499,7 +564,7 @@ function cardString(template, components) {
499564 color = 'root.scopeStyle ? root.scopeStyle.foreground : "grey"';
500565 }
501566
502− var summaryTopMargin = (hasMascot || hasSubtitle ? 'anchors.margins' : '0');
567+ var summaryTopMargin = (hasMascot || hasSubtitle || hasAttributes ? 'anchors.margins' : '0');
503568
504569 code += kSummaryLabelCode.arg(summaryTopAnchor).arg(summaryTopMargin).arg(color);
505570 }
@@ -510,6 +575,8 @@ function cardString(template, components) {
510575 code += 'implicitHeight: row.y + row.height + units.gu(1);\n';
511576 } else if (hasMascot) {
512577 code += 'implicitHeight: mascotImage.y + mascotImage.height;\n';
578+ } else if (hasAttributes) {
579+ code += 'implicitHeight: attributesRow.y + attributesRow.height + units.gu(1);\n';
513580 } else if (hasSubtitle) {
514581 code += 'implicitHeight: subtitleLabel.y + subtitleLabel.height + units.gu(1);\n';
515582 } else if (hasTitle) {
@@ -526,8 +593,10 @@ function cardString(template, components) {
526593function createCardComponent(parent, template, components) {
527594 var imports = 'import QtQuick 2.2; \n\
528595 import Ubuntu.Components 0.1; \n\
529− import Ubuntu.Thumbnailer 0.1;\n';
596+ import Ubuntu.Thumbnailer 0.1;\n\
597+ import Dash 0.1;\n';
530598 var card = cardString(template, components);
531599 var code = imports + 'Component {\n' + card + '}\n';
600+
532601 return Qt.createQmlObject(code, parent, "createCardComponent");
533602}
plugins/Dash/qmldir+1
@@ -3,3 +3,4 @@ plugin Dash-qml
33typeinfo Dash.qmltypes
44singleton CardCreatorCache 0.1 CardCreatorCache.qml
55ScopeStyle 0.1 ScopeStyle.qml
6+CardAttributes 0.1 CardAttributes.qml
plugins/LightDM/plugin.cpp+2
@@ -69,6 +69,8 @@ void LightDMPlugin::registerTypes(const char *uri)
6969 qmlRegisterType<UserMetricsOutput::ColorTheme>();
7070
7171 Q_ASSERT(uri == QLatin1String("LightDM"));
72+ qRegisterMetaType<QLightDM::Greeter::MessageType>("QLightDM::Greeter::MessageType");
73+ qRegisterMetaType<QLightDM::Greeter::PromptType>("QLightDM::Greeter::PromptType");
7274 qmlRegisterSingletonType<Greeter>(uri, 0, 1, "Greeter", greeter_provider);
7375 qmlRegisterSingletonType<UsersModel>(uri, 0, 1, "Users", users_provider);
7476 qmlRegisterUncreatableType<QLightDM::UsersModel>(uri, 0, 1, "UserRoles", "Type is not instantiable");
plugins/Utils/CMakeLists.txt+1
@@ -15,6 +15,7 @@ set(QMLPLUGIN_SRC
1515 qsortfilterproxymodelqml.cpp
1616 timeformatter.cpp
1717 unitymenumodelpaths.cpp
18+ windowkeysfilter.cpp
1819 easingcurve.cpp
1920 plugin.cpp
2021 )
plugins/Utils/easingcurve.cpp+5−1
@@ -34,7 +34,11 @@ QEasingCurve::Type EasingCurve::type() const
3434
3535void EasingCurve::setType(const QEasingCurve::Type &type)
3636{
37− m_easingCurve.setType(type);
37+ // FIXME: Working around bug https://bugreports.qt-project.org/browse/QTBUG-38686 here
38+ QEasingCurve newCurve;
39+ newCurve.setType(type);
40+ newCurve.setPeriod(m_easingCurve.period());
41+ m_easingCurve = newCurve;
3842 Q_EMIT typeChanged();
3943}
4044
plugins/Utils/plugin.cpp+2
@@ -30,6 +30,7 @@
3030#include "qsortfilterproxymodelqml.h"
3131#include "timeformatter.h"
3232#include "unitymenumodelpaths.h"
33+#include "windowkeysfilter.h"
3334#include "easingcurve.h"
3435
3536void UtilsPlugin::registerTypes(const char *uri)
@@ -40,6 +41,7 @@ void UtilsPlugin::registerTypes(const char *uri)
4041 qmlRegisterType<QSortFilterProxyModelQML>(uri, 0, 1, "SortFilterProxyModel");
4142 qmlRegisterType<UnityMenuModelPaths>(uri, 0, 1, "UnityMenuModelPaths");
4243 qmlRegisterType<TimeFormatter>(uri, 0, 1, "TimeFormatter");
44+ qmlRegisterType<WindowKeysFilter>(uri, 0, 1, "WindowKeysFilter");
4345 qmlRegisterType<GDateTimeFormatter>(uri, 0, 1, "GDateTimeFormatter");
4446 qmlRegisterType<EasingCurve>(uri, 0, 1, "EasingCurve");
4547}
plugins/Utils/windowkeysfilter.cppnew file+57
@@ -0,0 +1,57 @@
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+ * Author: Daniel d'Andrada <daniel.dandrada@canonical.com>
17+ */
18+
19+#include "windowkeysfilter.h"
20+
21+#include <QQuickWindow>
22+
23+WindowKeysFilter::WindowKeysFilter(QQuickItem *parent)
24+ : QQuickItem(parent)
25+{
26+ connect(this, &QQuickItem::windowChanged,
27+ this, &WindowKeysFilter::setupFilterOnWindow);
28+}
29+
30+bool WindowKeysFilter::eventFilter(QObject *watched, QEvent *event)
31+{
32+ Q_ASSERT(!m_filteredWindow.isNull());
33+ Q_ASSERT(watched == static_cast<QObject*>(m_filteredWindow.data()));
34+
35+ if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) {
36+ // Let QML see this event and decide if it does not want it
37+ event->accept();
38+ QCoreApplication::sendEvent(this, event);
39+ return event->isAccepted();
40+ } else {
41+ // Not interested
42+ return false;
43+ }
44+}
45+
46+void WindowKeysFilter::setupFilterOnWindow(QQuickWindow *window)
47+{
48+ if (!m_filteredWindow.isNull()) {
49+ m_filteredWindow->removeEventFilter(this);
50+ m_filteredWindow.clear();
51+ }
52+
53+ if (window) {
54+ window->installEventFilter(this);
55+ m_filteredWindow = window;
56+ }
57+}
plugins/Utils/windowkeysfilter.hnew file+51
@@ -0,0 +1,51 @@
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+ * Author: Daniel d'Andrada <daniel.dandrada@canonical.com>
17+ */
18+
19+#ifndef UNITY_WINDOWKEYSFILTER_H
20+#define UNITY_WINDOWKEYSFILTER_H
21+
22+#include <QQuickItem>
23+#include <QPointer>
24+
25+/*
26+ Receives all key events that arrive in the QQuickWindow where this item is placed.
27+
28+ Rejected key events will be allowed to be processed normally by the QQuickWindow whereas
29+ accepted ones will be filtered out. Events are accepted by default, so make sure you reject
30+ the keys you're not interested in.
31+
32+ If more than one WindowKeysFilter exist in the same QML scene (and thus in the same QQuickWindow)
33+ they will be called in the order of creation, which can be tricky to assess. So the best practice
34+ is to have at most one WindowKeysFilter per QML scene.
35+ */
36+class WindowKeysFilter : public QQuickItem
37+{
38+ Q_OBJECT
39+public:
40+ WindowKeysFilter(QQuickItem *parent = 0);
41+
42+ bool eventFilter(QObject *watched, QEvent *event);
43+
44+private Q_SLOTS:
45+ void setupFilterOnWindow(QQuickWindow *window);
46+
47+private:
48+ QPointer<QQuickWindow> m_filteredWindow;
49+};
50+
51+#endif // UNITY_WINDOWKEYSFILTER_H
po/da.po+7−7
@@ -9,13 +9,13 @@ msgstr ""
99"Project-Id-Version: unity8\n"
1010"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1111"POT-Creation-Date: 2014-07-11 14:59+0200\n"
12−"PO-Revision-Date: 2014-07-22 15:46+0000\n"
13−"Last-Translator: Kenneth Ramsussen <kentora@kentora.dk>\n"
12+"PO-Revision-Date: 2014-07-30 12:37+0000\n"
13+"Last-Translator: Ask Hjorth Larsen <asklarsen@gmail.com>\n"
1414"Language-Team: Danish <dansk@dansk-gruppen.dk>\n"
1515"MIME-Version: 1.0\n"
1616"Content-Type: text/plain; charset=UTF-8\n"
1717"Content-Transfer-Encoding: 8bit\n"
18−"X-Launchpad-Export-Date: 2014-07-26 07:16+0000\n"
18+"X-Launchpad-Export-Date: 2014-07-31 07:15+0000\n"
1919"X-Generator: Launchpad (build 17131)\n"
2020
2121#: plugins/Unity/Launcher/launcheritem.cpp:43
@@ -72,7 +72,7 @@ msgid ""
7272"You have now mastered the edge gestures and can start using the "
7373"phone<br><br>Tap on the screen to start"
7474msgstr ""
75−"Du mestrer nu kantgestusserne og kan begynde at bruge telefonen<br><br>Tryk "
75+"Du mestrer nu kantbevægelserne og kan begynde at bruge telefonen<br><br>Tryk "
7676"på skærmen for at starte"
7777
7878#: qml/Components/Lockscreen.qml:214
@@ -114,7 +114,7 @@ msgstr "Seneste"
114114
115115#: qml/Dash/GenericScopeView.qml:330
116116msgid "See all"
117−msgstr ""
117+msgstr "Se alle"
118118
119119#: qml/Dash/Previews/PreviewActionCombo.qml:34
120120msgid "More..."
@@ -170,11 +170,11 @@ msgstr "Indtast venligst PIN for SIM"
170170
171171#: qml/Panel/ActiveCallHint.qml:77
172172msgid "Tap to return to call..."
173−msgstr ""
173+msgstr "Tryk for at besvare opkaldet..."
174174
175175#: qml/Panel/ActiveCallHint.qml:90
176176msgid "Conference"
177−msgstr ""
177+msgstr "Konference"
178178
179179#: qml/Panel/Indicators/MenuItemFactory.qml:583
180180msgid "In queue…"
po/fa.po+51−49
@@ -8,200 +8,202 @@ msgstr ""
88"Project-Id-Version: unity8\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2014-07-11 14:59+0200\n"
11−"PO-Revision-Date: 2013-10-31 19:02+0000\n"
12−"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
11+"PO-Revision-Date: 2014-08-03 13:00+0000\n"
12+"Last-Translator: Arash Badie Modiri <arashbm@gmail.com>\n"
1313"Language-Team: Persian <fa@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-07-26 07:17+0000\n"
18−"X-Generator: Launchpad (build 17131)\n"
17+"X-Launchpad-Export-Date: 2014-08-04 06:32+0000\n"
18+"X-Generator: Launchpad (build 17147)\n"
1919
2020#: plugins/Unity/Launcher/launcheritem.cpp:43
2121#: plugins/Unity/Launcher/launcheritem.cpp:73
2222msgid "Pin shortcut"
23−msgstr ""
23+msgstr "سنجاق کردن میانبر"
2424
2525#: plugins/Unity/Launcher/launcheritem.cpp:73
2626msgid "Unpin shortcut"
27−msgstr ""
27+msgstr "برداشتن سنجاق میانبر"
2828
2929#: qml/Components/EdgeDemoOverlay.qml:151
3030msgid "Skip intro"
31−msgstr ""
31+msgstr "پرش از مقدّمه"
3232
3333#: qml/Components/EdgeDemo.qml:113
3434msgid "Right edge"
35−msgstr ""
35+msgstr "لبه‌ی راست"
3636
3737#: qml/Components/EdgeDemo.qml:114
3838msgid "Try swiping from the right edge to unlock the phone"
39−msgstr ""
39+msgstr "برای باز کردن قفل تلفن، از لبه‌ی راست بکشید"
4040
4141#: qml/Components/EdgeDemo.qml:145
4242msgid "Top edge"
43−msgstr ""
43+msgstr "لبه‌ی بالا"
4444
4545#: qml/Components/EdgeDemo.qml:146
4646msgid "Try swiping from the top edge to access the indicators"
47−msgstr ""
47+msgstr "برای دست‌رسی به نشانگرها، از لبه‌ی بالا بکشید"
4848
4949#: qml/Components/EdgeDemo.qml:171
5050msgid "Close"
51−msgstr ""
51+msgstr "بستن"
5252
5353#: qml/Components/EdgeDemo.qml:172
5454msgid "Swipe up again to close the settings screen"
55−msgstr ""
55+msgstr "برای بستن صفحه‌ی تنظیمات، دوباره به بالا بکشپد"
5656
5757#: qml/Components/EdgeDemo.qml:199
5858msgid "Left edge"
59−msgstr ""
59+msgstr "لبه‌ی چپ"
6060
6161#: qml/Components/EdgeDemo.qml:200
6262msgid "Swipe from the left to reveal the launcher for quick access to apps"
63−msgstr ""
63+msgstr "برای آشکار شدن اجراگر به منظور دست‌رسی سریع به اپ‌ها از سمت چپ بکشید"
6464
6565#: qml/Components/EdgeDemo.qml:227
6666msgid "Well done"
67−msgstr ""
67+msgstr "عالی بود"
6868
6969#: qml/Components/EdgeDemo.qml:228
7070msgid ""
7171"You have now mastered the edge gestures and can start using the "
7272"phone<br><br>Tap on the screen to start"
7373msgstr ""
74+"هم اکنون در استفاده از ژست های حرکتی مهارت لازم را دارید و می‌توانید استفاده "
75+"از تلفن را آغاز کنید<br><br>برای شروع صفحه را لمس کنید"
7476
7577#: qml/Components/Lockscreen.qml:214
7678msgid "Emergency Call"
77−msgstr ""
79+msgstr "تماس اضطراری"
7880
7981#: qml/Components/Lockscreen.qml:231
8082msgid "OK"
81−msgstr ""
83+msgstr "قبول"
8284
8385#: qml/Components/PassphraseLockscreen.qml:60
8486#, qt-format
8587msgid "Hello %1"
86−msgstr ""
88+msgstr "سلام %1"
8789
8890#: qml/Components/PassphraseLockscreen.qml:60
8991msgid "Hello"
90−msgstr ""
92+msgstr "سلام"
9193
9294#: qml/Components/PinLockscreen.qml:198
9395msgid "CANCEL"
94−msgstr ""
96+msgstr "لغو"
9597
9698#: qml/Components/PinLockscreen.qml:216
9799msgid "DONE"
98−msgstr ""
100+msgstr "انجام شد"
99101
100102#: qml/Components/SeeMore.qml:35
101103msgid "See more"
102−msgstr ""
104+msgstr "بیش‌تر ببینید"
103105
104106#: qml/Components/SeeMore.qml:58 qml/Dash/GenericScopeView.qml:330
105107msgid "See less"
106−msgstr ""
108+msgstr "کم‌تر ببینید"
107109
108110#: qml/Dash/DashApps.qml:37
109111msgid "Recent"
110−msgstr ""
112+msgstr "موارد اخیر"
111113
112114#: qml/Dash/GenericScopeView.qml:330
113115msgid "See all"
114−msgstr ""
116+msgstr "دیدن همه"
115117
116118#: qml/Dash/Previews/PreviewActionCombo.qml:34
117119msgid "More..."
118−msgstr ""
120+msgstr "بیش‌تر…"
119121
120122#: qml/Dash/Previews/PreviewActionCombo.qml:34
121123msgid "Less..."
122−msgstr ""
124+msgstr "کم‌تر…"
123125
124126#: qml/Dash/Previews/PreviewRatingInput.qml:81
125127msgid "Rate this"
126−msgstr ""
128+msgstr "امتیاز دادن"
127129
128130#: qml/Dash/Previews/PreviewRatingInput.qml:126
129131msgid "Add a review"
130−msgstr ""
132+msgstr "افزودن یک بازبینی"
131133
132134#: qml/Dash/Previews/PreviewRatingInput.qml:166
133135msgid "Send"
134−msgstr ""
136+msgstr "ارسال"
135137
136138#: qml/Greeter/Greeter.qml:157
137139msgid "Swipe to unlock"
138−msgstr ""
140+msgstr "برای باز کردن قفل بکشد"
139141
140142#: qml/Hud/HudParametrizedActionsPage.qml:132
141143msgid "Confirm"
142−msgstr ""
144+msgstr "تأیید"
143145
144146#: qml/Hud/Hud.qml:61 qml/Hud/Hud.qml:99 qml/Hud/Hud.qml:372
145147msgid "Type or say a command"
146−msgstr ""
148+msgstr "دستوری را بنویسید یا بگویید"
147149
148150#: qml/Hud/Hud.qml:84
149151msgid "Loading. Please Wait..."
150−msgstr ""
152+msgstr "در حال بار کردن. لطفاً صبر کنید…"
151153
152154#: qml/Hud/Hud.qml:89
153155msgid "Speak Now..."
154−msgstr ""
156+msgstr "اکنون صحبت کنید…"
155157
156158#: qml/Hud/Hud.qml:94
157159msgid "Speaking..."
158−msgstr ""
160+msgstr "در حال صحبت کردن…"
159161
160162#: qml/Notifications/NotificationMenuItemFactory.qml:91
161163msgid "Show password"
162−msgstr ""
164+msgstr "نمایش گذرواژه"
163165
164166#: qml/Notifications/NotificationMenuItemFactory.qml:103
165167msgid "Please enter SIM PIN"
166−msgstr ""
168+msgstr "لطفاً PIN سیم‌کارت را وارد کنید"
167169
168170#: qml/Panel/ActiveCallHint.qml:77
169171msgid "Tap to return to call..."
170−msgstr ""
172+msgstr "برای بازگشت به تماس ضربه بزنید…"
171173
172174#: qml/Panel/ActiveCallHint.qml:90
173175msgid "Conference"
174−msgstr ""
176+msgstr "کنفرانس"
175177
176178#: qml/Panel/Indicators/MenuItemFactory.qml:583
177179msgid "In queue…"
178−msgstr ""
180+msgstr "در صف…"
179181
180182#: qml/Panel/Indicators/MenuItemFactory.qml:587
181183msgid "Downloading"
182−msgstr ""
184+msgstr "در حال بارگیری"
183185
184186#: qml/Panel/Indicators/MenuItemFactory.qml:589
185187msgid "Paused, tap to resume"
186−msgstr ""
188+msgstr "مکث شد، برای از سر گیری ضربه بزنید"
187189
188190#: qml/Panel/Indicators/MenuItemFactory.qml:591
189191msgid "Canceled"
190−msgstr ""
192+msgstr "لغو شد"
191193
192194#: qml/Panel/Indicators/MenuItemFactory.qml:593
193195msgid "Finished"
194−msgstr ""
196+msgstr "پایان یافت"
195197
196198#: qml/Panel/Indicators/MenuItemFactory.qml:595
197199msgid "Failed, tap to retry"
198−msgstr ""
200+msgstr "شکست خورد، برای تلاش دوباره ضربه بزنید"
199201
200202#: qml/Panel/SearchIndicator.qml:27
201203msgid "Search"
202−msgstr ""
204+msgstr "جست‌وجو"
203205
204206#: qml/Shell.qml:359
205207#, qt-format
206208msgid "Please enter %1"
207−msgstr ""
209+msgstr "لطفا %1 را وارد کنید"
po/hu.po+8−8
@@ -8,14 +8,14 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2014-07-11 14:59+0200\n"
11−"PO-Revision-Date: 2014-07-27 15:04+0000\n"
11+"PO-Revision-Date: 2014-08-03 17:15+0000\n"
1212"Last-Translator: Richard Somlói <ricsipontaz@gmail.com>\n"
1313"Language-Team: Hungarian <hu@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-07-28 06:44+0000\n"
18−"X-Generator: Launchpad (build 17131)\n"
17+"X-Launchpad-Export-Date: 2014-08-04 06:32+0000\n"
18+"X-Generator: Launchpad (build 17147)\n"
1919
2020#: plugins/Unity/Launcher/launcheritem.cpp:43
2121#: plugins/Unity/Launcher/launcheritem.cpp:73
@@ -44,7 +44,7 @@ msgstr "Felső szél"
4444
4545#: qml/Components/EdgeDemo.qml:146
4646msgid "Try swiping from the top edge to access the indicators"
47−msgstr "A jelzők eléréséhez csúsztassa ujját a felső szélről"
47+msgstr "Az állapotmenük eléréséhez csúsztassa ujját a felső szélről"
4848
4949#: qml/Components/EdgeDemo.qml:171
5050msgid "Close"
@@ -82,16 +82,16 @@ msgstr "Vészhívás"
8282
8383#: qml/Components/Lockscreen.qml:231
8484msgid "OK"
85−msgstr "OK"
85+msgstr "Oké"
8686
8787#: qml/Components/PassphraseLockscreen.qml:60
8888#, qt-format
8989msgid "Hello %1"
90−msgstr "Szia %1"
90+msgstr "Üdv %1"
9191
9292#: qml/Components/PassphraseLockscreen.qml:60
9393msgid "Hello"
94−msgstr "Szia"
94+msgstr "Üdv"
9595
9696#: qml/Components/PinLockscreen.qml:198
9797msgid "CANCEL"
@@ -127,7 +127,7 @@ msgstr "Kevesebb…"
127127
128128#: qml/Dash/Previews/PreviewRatingInput.qml:81
129129msgid "Rate this"
130−msgstr "Osztályzás"
130+msgstr "Értékelje"
131131
132132#: qml/Dash/Previews/PreviewRatingInput.qml:126
133133msgid "Add a review"
po/is.po+6−6
@@ -10,14 +10,14 @@ msgstr ""
1010"Project-Id-Version: po_unity8-is\n"
1111"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1212"POT-Creation-Date: 2014-07-11 14:59+0200\n"
13−"PO-Revision-Date: 2014-07-23 05:57+0000\n"
13+"PO-Revision-Date: 2014-07-31 16:10+0000\n"
1414"Last-Translator: Sveinn í Felli <sv1@fellsnet.is>\n"
1515"Language-Team: Icelandic <translation-team-is@lists.sourceforge.net>\n"
1616"MIME-Version: 1.0\n"
1717"Content-Type: text/plain; charset=UTF-8\n"
1818"Content-Transfer-Encoding: 8bit\n"
19−"X-Launchpad-Export-Date: 2014-07-26 07:16+0000\n"
20−"X-Generator: Launchpad (build 17131)\n"
19+"X-Launchpad-Export-Date: 2014-08-01 07:14+0000\n"
20+"X-Generator: Launchpad (build 17141)\n"
2121"Language: is\n"
2222
2323#: plugins/Unity/Launcher/launcheritem.cpp:43
@@ -117,7 +117,7 @@ msgstr "Nýlegt"
117117
118118#: qml/Dash/GenericScopeView.qml:330
119119msgid "See all"
120−msgstr ""
120+msgstr "Sjá allt"
121121
122122#: qml/Dash/Previews/PreviewActionCombo.qml:34
123123msgid "More..."
@@ -173,11 +173,11 @@ msgstr "Settu inn PIN fyrir SIM"
173173
174174#: qml/Panel/ActiveCallHint.qml:77
175175msgid "Tap to return to call..."
176−msgstr ""
176+msgstr "Slá til að svara símtali..."
177177
178178#: qml/Panel/ActiveCallHint.qml:90
179179msgid "Conference"
180−msgstr ""
180+msgstr "Ráðstefna"
181181
182182#: qml/Panel/Indicators/MenuItemFactory.qml:583
183183msgid "In queue…"
po/km.po+7−7
@@ -8,14 +8,14 @@ msgstr ""
88"Project-Id-Version: unity8\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2014-07-11 14:59+0200\n"
11−"PO-Revision-Date: 2014-07-16 01:37+0000\n"
12−"Last-Translator: Khoem Sokhem <khoemsokhem@khmeros.info>\n"
11+"PO-Revision-Date: 2014-08-01 04:02+0000\n"
12+"Last-Translator: Sok Sophea <sksophea@gmail.com>\n"
1313"Language-Team: Khmer <km@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-07-26 07:16+0000\n"
18−"X-Generator: Launchpad (build 17131)\n"
17+"X-Launchpad-Export-Date: 2014-08-02 06:39+0000\n"
18+"X-Generator: Launchpad (build 17147)\n"
1919
2020#: plugins/Unity/Launcher/launcheritem.cpp:43
2121#: plugins/Unity/Launcher/launcheritem.cpp:73
@@ -115,7 +115,7 @@ msgstr "បច្ចុប្បន្ន"
115115
116116#: qml/Dash/GenericScopeView.qml:330
117117msgid "See all"
118−msgstr ""
118+msgstr "មើល​ទាំងអស់"
119119
120120#: qml/Dash/Previews/PreviewActionCombo.qml:34
121121msgid "More..."
@@ -171,11 +171,11 @@ msgstr "បញ្ចូល​កូដ PIN របស់​ស៊ីមកាត"
171171
172172#: qml/Panel/ActiveCallHint.qml:77
173173msgid "Tap to return to call..."
174−msgstr ""
174+msgstr "ប៉ះ​ដើម្បី​ត្រឡប់​ទៅ​ការ​ហៅ..."
175175
176176#: qml/Panel/ActiveCallHint.qml:90
177177msgid "Conference"
178−msgstr ""
178+msgstr "សន្និសីទ"
179179
180180#: qml/Panel/Indicators/MenuItemFactory.qml:583
181181msgid "In queue…"
po/pl.po+7−7
@@ -8,14 +8,14 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2014-07-11 14:59+0200\n"
11−"PO-Revision-Date: 2014-07-19 14:10+0000\n"
12−"Last-Translator: GTriderXC <gtriderxc@yahoo.com>\n"
11+"PO-Revision-Date: 2014-07-31 13:51+0000\n"
12+"Last-Translator: Michał Radwański <radwanski.michal@outlook.com>\n"
1313"Language-Team: Polish <pl@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-07-26 07:17+0000\n"
18−"X-Generator: Launchpad (build 17131)\n"
17+"X-Launchpad-Export-Date: 2014-08-01 07:14+0000\n"
18+"X-Generator: Launchpad (build 17141)\n"
1919
2020#: plugins/Unity/Launcher/launcheritem.cpp:43
2121#: plugins/Unity/Launcher/launcheritem.cpp:73
@@ -115,7 +115,7 @@ msgstr "Ostatnio używane"
115115
116116#: qml/Dash/GenericScopeView.qml:330
117117msgid "See all"
118−msgstr ""
118+msgstr "Zobacz wszystko"
119119
120120#: qml/Dash/Previews/PreviewActionCombo.qml:34
121121msgid "More..."
@@ -171,11 +171,11 @@ msgstr "Wprowadź kod PIN karty SIM"
171171
172172#: qml/Panel/ActiveCallHint.qml:77
173173msgid "Tap to return to call..."
174−msgstr ""
174+msgstr "Dotknij aby powrócić do połączenia..."
175175
176176#: qml/Panel/ActiveCallHint.qml:90
177177msgid "Conference"
178−msgstr ""
178+msgstr "Konferencja"
179179
180180#: qml/Panel/Indicators/MenuItemFactory.qml:583
181181msgid "In queue…"
po/ru.po+18−18
@@ -8,23 +8,23 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2014-07-11 14:59+0200\n"
11−"PO-Revision-Date: 2014-05-15 12:57+0000\n"
12−"Last-Translator: Zonov Roman <Unknown>\n"
11+"PO-Revision-Date: 2014-07-31 09:39+0000\n"
12+"Last-Translator: Jay ZDLin <Unknown>\n"
1313"Language-Team: Russian <ru@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-07-26 07:17+0000\n"
18−"X-Generator: Launchpad (build 17131)\n"
17+"X-Launchpad-Export-Date: 2014-08-01 07:14+0000\n"
18+"X-Generator: Launchpad (build 17141)\n"
1919
2020#: plugins/Unity/Launcher/launcheritem.cpp:43
2121#: plugins/Unity/Launcher/launcheritem.cpp:73
2222msgid "Pin shortcut"
23−msgstr ""
23+msgstr "Закрепить ярлык"
2424
2525#: plugins/Unity/Launcher/launcheritem.cpp:73
2626msgid "Unpin shortcut"
27−msgstr ""
27+msgstr "Изъять ярлык"
2828
2929#: qml/Components/EdgeDemoOverlay.qml:151
3030msgid "Skip intro"
@@ -85,7 +85,7 @@ msgstr "Экстренный вызов"
8585
8686#: qml/Components/Lockscreen.qml:231
8787msgid "OK"
88−msgstr ""
88+msgstr "ОК"
8989
9090#: qml/Components/PassphraseLockscreen.qml:60
9191#, qt-format
@@ -98,11 +98,11 @@ msgstr "Привет"
9898
9999#: qml/Components/PinLockscreen.qml:198
100100msgid "CANCEL"
101−msgstr ""
101+msgstr "ОТМЕНИТЬ"
102102
103103#: qml/Components/PinLockscreen.qml:216
104104msgid "DONE"
105−msgstr ""
105+msgstr "ГОТОВО"
106106
107107#: qml/Components/SeeMore.qml:35
108108msgid "See more"
@@ -118,7 +118,7 @@ msgstr "Недавние"
118118
119119#: qml/Dash/GenericScopeView.qml:330
120120msgid "See all"
121−msgstr ""
121+msgstr "Показать всё"
122122
123123#: qml/Dash/Previews/PreviewActionCombo.qml:34
124124msgid "More..."
@@ -174,35 +174,35 @@ msgstr "Введите PIN код"
174174
175175#: qml/Panel/ActiveCallHint.qml:77
176176msgid "Tap to return to call..."
177−msgstr ""
177+msgstr "Коснитесь для возврата к вызову..."
178178
179179#: qml/Panel/ActiveCallHint.qml:90
180180msgid "Conference"
181−msgstr ""
181+msgstr "Конференция"
182182
183183#: qml/Panel/Indicators/MenuItemFactory.qml:583
184184msgid "In queue…"
185−msgstr ""
185+msgstr "В очереди..."
186186
187187#: qml/Panel/Indicators/MenuItemFactory.qml:587
188188msgid "Downloading"
189−msgstr ""
189+msgstr "Загрузка"
190190
191191#: qml/Panel/Indicators/MenuItemFactory.qml:589
192192msgid "Paused, tap to resume"
193−msgstr ""
193+msgstr "Приостановлено, коснитесь для возобновления"
194194
195195#: qml/Panel/Indicators/MenuItemFactory.qml:591
196196msgid "Canceled"
197−msgstr ""
197+msgstr "Отменено"
198198
199199#: qml/Panel/Indicators/MenuItemFactory.qml:593
200200msgid "Finished"
201−msgstr ""
201+msgstr "Завершено"
202202
203203#: qml/Panel/Indicators/MenuItemFactory.qml:595
204204msgid "Failed, tap to retry"
205−msgstr ""
205+msgstr "Сбой, коснитесь для повтора"
206206
207207#: qml/Panel/SearchIndicator.qml:27
208208msgid "Search"
po/sv.po+6−6
@@ -8,14 +8,14 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2014-07-11 14:59+0200\n"
11−"PO-Revision-Date: 2014-07-25 14:27+0000\n"
12−"Last-Translator: Josef Andersson <Unknown>\n"
11+"PO-Revision-Date: 2014-07-31 08:10+0000\n"
12+"Last-Translator: Peter Hultqvist <phq@silentorbit.com>\n"
1313"Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-07-26 07:17+0000\n"
18−"X-Generator: Launchpad (build 17131)\n"
17+"X-Launchpad-Export-Date: 2014-08-01 07:14+0000\n"
18+"X-Generator: Launchpad (build 17141)\n"
1919"Language: sv\n"
2020
2121#: plugins/Unity/Launcher/launcheritem.cpp:43
@@ -102,11 +102,11 @@ msgstr "KLAR"
102102
103103#: qml/Components/SeeMore.qml:35
104104msgid "See more"
105−msgstr "Se mer"
105+msgstr "Visa mer"
106106
107107#: qml/Components/SeeMore.qml:58 qml/Dash/GenericScopeView.qml:330
108108msgid "See less"
109−msgstr "Se mindre"
109+msgstr "Visa mindre"
110110
111111#: qml/Dash/DashApps.qml:37
112112msgid "Recent"
po/ug.po+1−1
@@ -14,7 +14,7 @@ msgstr ""
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-07-29 06:54+0000\n"
17+"X-Launchpad-Export-Date: 2014-07-31 07:15+0000\n"
1818"X-Generator: Launchpad (build 17131)\n"
1919
2020#: plugins/Unity/Launcher/launcheritem.cpp:43
po/uk.po+4−4
@@ -8,13 +8,13 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2014-07-11 14:59+0200\n"
11−"PO-Revision-Date: 2014-07-28 21:35+0000\n"
12−"Last-Translator: Ярослав Смук <Smuk.Slavko@yandex.ua>\n"
11+"PO-Revision-Date: 2014-07-29 07:53+0000\n"
12+"Last-Translator: Yuri Chornoivan <yurchor@gmail.com>\n"
1313"Language-Team: Ukrainian <uk@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-07-29 06:54+0000\n"
17+"X-Launchpad-Export-Date: 2014-07-31 07:15+0000\n"
1818"X-Generator: Launchpad (build 17131)\n"
1919
2020#: plugins/Unity/Launcher/launcheritem.cpp:43
@@ -172,7 +172,7 @@ msgstr "Введіть PIN-код SIM-картки"
172172
173173#: qml/Panel/ActiveCallHint.qml:77
174174msgid "Tap to return to call..."
175−msgstr ""
175+msgstr "Торкніться, щоб повернутися до дзвінка…"
176176
177177#: qml/Panel/ActiveCallHint.qml:90
178178msgid "Conference"
po/zh_CN.po+1−1
@@ -14,7 +14,7 @@ msgstr ""
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-07-29 06:54+0000\n"
17+"X-Launchpad-Export-Date: 2014-07-31 07:15+0000\n"
1818"X-Generator: Launchpad (build 17131)\n"
1919
2020#: plugins/Unity/Launcher/launcheritem.cpp:43
qml/Components/Dialogs.qmlnew file+217
@@ -0,0 +1,217 @@
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+
19+import Unity.Application 0.1
20+import Unity.Session 0.1
21+import Ubuntu.Components 0.1
22+import Ubuntu.Components.Popups 0.1
23+
24+Item {
25+ id: root
26+
27+ function onPowerKeyPressed() {
28+ if (!powerKeyTimer.running) {
29+ powerKeyTimer.start();
30+ }
31+ }
32+
33+ function onPowerKeyReleased() {
34+ powerKeyTimer.stop();
35+ }
36+
37+ signal powerOffClicked();
38+
39+ QtObject {
40+ id: d // private stuff
41+
42+ property bool dialogShown: false
43+
44+ function showPowerDialog() {
45+ if (!d.dialogShown) {
46+ d.dialogShown = true;
47+ PopupUtils.open(powerDialog, root, {"z": root.z});
48+ }
49+ }
50+ }
51+
52+ Timer {
53+ id: powerKeyTimer
54+ interval: 2000
55+ repeat: false
56+ triggeredOnStart: false
57+
58+ onTriggered: {
59+ d.showPowerDialog();
60+ }
61+ }
62+
63+ Component {
64+ id: logoutDialog
65+ Dialog {
66+ id: dialogueLogout
67+ title: "Logout"
68+ text: "Are you sure that you want to logout?"
69+ Button {
70+ text: "Cancel"
71+ onClicked: {
72+ PopupUtils.close(dialogueLogout);
73+ d.dialogShown = false;
74+ }
75+ }
76+ Button {
77+ text: "Yes"
78+ onClicked: {
79+ DBusUnitySessionService.Logout();
80+ PopupUtils.close(dialogueLogout);
81+ d.dialogShown = false;
82+ }
83+ }
84+ }
85+ }
86+
87+ Component {
88+ id: shutdownDialog
89+ Dialog {
90+ id: dialogueShutdown
91+ title: "Shutdown"
92+ text: "Are you sure that you want to shutdown?"
93+ Button {
94+ text: "Cancel"
95+ onClicked: {
96+ PopupUtils.close(dialogueShutdown);
97+ d.dialogShown = false;
98+ }
99+ }
100+ Button {
101+ text: "Yes"
102+ onClicked: {
103+ dBusUnitySessionServiceConnection.closeAllApps();
104+ DBusUnitySessionService.Shutdown();
105+ PopupUtils.close(dialogueShutdown);
106+ d.dialogShown = false;
107+ }
108+ }
109+ }
110+ }
111+
112+ Component {
113+ id: rebootDialog
114+ Dialog {
115+ id: dialogueReboot
116+ title: "Reboot"
117+ text: "Are you sure that you want to reboot?"
118+ Button {
119+ text: "Cancel"
120+ onClicked: {
121+ PopupUtils.close(dialogueReboot)
122+ d.dialogShown = false;
123+ }
124+ }
125+ Button {
126+ text: "Yes"
127+ onClicked: {
128+ dBusUnitySessionServiceConnection.closeAllApps();
129+ DBusUnitySessionService.Reboot();
130+ PopupUtils.close(dialogueReboot);
131+ d.dialogShown = false;
132+ }
133+ }
134+ }
135+ }
136+
137+ Component {
138+ id: powerDialog
139+ Dialog {
140+ id: dialoguePower
141+ title: "Power"
142+ text: i18n.tr("Are you sure you would like to turn power off?")
143+ Button {
144+ text: i18n.tr("Power off")
145+ onClicked: {
146+ dBusUnitySessionServiceConnection.closeAllApps();
147+ PopupUtils.close(dialoguePower);
148+ d.dialogShown = false;
149+ root.powerOffClicked();
150+ }
151+ }
152+ Button {
153+ text: i18n.tr("Restart")
154+ onClicked: {
155+ dBusUnitySessionServiceConnection.closeAllApps();
156+ DBusUnitySessionService.Reboot();
157+ PopupUtils.close(dialoguePower);
158+ d.dialogShown = false;
159+ }
160+ }
161+ Button {
162+ text: i18n.tr("Cancel")
163+ onClicked: {
164+ PopupUtils.close(dialoguePower);
165+ d.dialogShown = false;
166+ }
167+ }
168+ }
169+ }
170+
171+
172+ Connections {
173+ id: dBusUnitySessionServiceConnection
174+ objectName: "dBusUnitySessionServiceConnection"
175+ target: DBusUnitySessionService
176+
177+ function closeAllApps() {
178+ while (true) {
179+ var app = ApplicationManager.get(0);
180+ if (app === null) {
181+ break;
182+ }
183+ ApplicationManager.stopApplication(app.appId);
184+ }
185+ }
186+
187+ onLogoutRequested: {
188+ // Display a dialog to ask the user to confirm.
189+ if (!d.dialogShown) {
190+ d.dialogShown = true;
191+ PopupUtils.open(logoutDialog, root, {"z": root.z});
192+ }
193+ }
194+
195+ onShutdownRequested: {
196+ // Display a dialog to ask the user to confirm.
197+ if (!d.dialogShown) {
198+ d.dialogShown = true;
199+ PopupUtils.open(shutdownDialog, root, {"z": root.z});
200+ }
201+ }
202+
203+ onRebootRequested: {
204+ // Display a dialog to ask the user to confirm.
205+ if (!d.dialogShown) {
206+ d.dialogShown = true;
207+ PopupUtils.open(rebootDialog, root, {"z": root.z});
208+ }
209+ }
210+
211+ onLogoutReady: {
212+ closeAllApps();
213+ Qt.quit();
214+ }
215+ }
216+
217+}
qml/Components/DraggingArea.qml+4
@@ -117,4 +117,8 @@ MouseArea {
117117 dragging = false
118118 __pressedPosition = Qt.point(mouse.x, mouse.y)
119119 }
120+
121+ onCanceled: {
122+ dragging = false
123+ }
120124}
qml/Components/InputMethod.qmlnew file+79
@@ -0,0 +1,79 @@
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 Unity.Application 0.1
19+import Ubuntu.Components 0.1
20+
21+Item {
22+ id: root
23+
24+ property var surface: null
25+
26+ property int transitionDuration: UbuntuAnimation.FastDuration
27+
28+ state: {
29+ if (surface && surface.state != MirSurfaceItem.Minimized) {
30+ return "shown";
31+ } else {
32+ return "hidden";
33+ }
34+ }
35+
36+ states: [
37+ State {
38+ name: "shown"
39+ PropertyChanges {
40+ target: root
41+ visible: true
42+ y: 0
43+ }
44+ },
45+ State {
46+ name: "hidden"
47+ PropertyChanges {
48+ target: root
49+ visible: false
50+ // half-way down because the vkb occupies only the lower half of the surface
51+ // TODO: consider keyboard rotation
52+ y: root.parent.height / 2.0
53+ }
54+ }
55+ ]
56+
57+ transitions: [
58+ Transition {
59+ from: "*"; to: "*"
60+ PropertyAction { property: "visible"; value: true }
61+ UbuntuNumberAnimation { property: "y"; duration: transitionDuration }
62+ }
63+ ]
64+
65+ onSurfaceChanged: {
66+ if (surface) {
67+ surface.parent = root;
68+ surface.anchors.fill = root;
69+ }
70+ }
71+
72+ Component.onDestruction: {
73+ if (surface) {
74+ surface.parent = null;
75+ surface.release();
76+ surface = null;
77+ }
78+ }
79+}
qml/Components/PageHeader.qml+27−8
@@ -44,6 +44,12 @@ Item {
4444 signal backClicked()
4545
4646 onScopeStyleChanged: refreshLogo()
47+ onSearchQueryChanged: {
48+ // Make sure we are at the search page if the search query changes behind our feet
49+ if (searchQuery) {
50+ headerContainer.showSearch = true;
51+ }
52+ }
4753
4854 function triggerSearch() {
4955 if (searchEntryEnabled) {
@@ -52,6 +58,12 @@ Item {
5258 }
5359 }
5460
61+ function closePopup() {
62+ if (headerContainer.popover != null) {
63+ PopupUtils.close(headerContainer.popover);
64+ }
65+ }
66+
5567 function resetSearch(keepFocus) {
5668 if (searchHistory) {
5769 searchHistory.addQuery(searchTextField.text);
@@ -60,9 +72,7 @@ Item {
6072 unfocus();
6173 }
6274 searchTextField.text = "";
63− if (headerContainer.popover != null) {
64− PopupUtils.close(headerContainer.popover);
65− }
75+ closePopup();
6676 }
6777
6878 function unfocus() {
@@ -103,9 +113,7 @@ Item {
103113 anchors { fill: parent; margins: units.gu(1); bottomMargin: units.gu(3) + bottomContainer.height }
104114 visible: headerContainer.showSearch
105115 onPressed: {
106− if (headerContainer.popover) {
107− PopupUtils.close(headerContainer.popover);
108− }
116+ closePopup();
109117 if (!searchTextField.text) {
110118 headerContainer.showSearch = false;
111119 }
@@ -117,7 +125,7 @@ Item {
117125 Flickable {
118126 id: headerContainer
119127 objectName: "headerContainer"
120− clip: true
128+ clip: openSearchAnimation.running
121129 anchors { left: parent.left; top: parent.top; right: parent.right }
122130 height: units.gu(6.5)
123131 contentHeight: headersColumn.height
@@ -155,6 +163,7 @@ Item {
155163 anchors { left: parent.left; right: parent.right }
156164 height: headerContainer.height
157165 contentHeight: height
166+ opacity: headerContainer.clip || headerContainer.showSearch ? 1 : 0 // setting visible false cause column to relayout
158167 separatorSource: ""
159168 // Required to keep PageHeadStyle noise down as it expects the Page's properties around.
160169 property var styledItem: searchHeader
@@ -171,6 +180,7 @@ Item {
171180 property var contents: TextField {
172181 id: searchTextField
173182 objectName: "searchTextField"
183+ inputMethodHints: Qt.ImhNoPredictiveText
174184 hasClearButton: false
175185 anchors {
176186 fill: parent
@@ -220,6 +230,12 @@ Item {
220230 root.openSearchHistory();
221231 }
222232 }
233+
234+ onTextChanged: {
235+ if (text != "") {
236+ closePopup();
237+ }
238+ }
223239 }
224240 }
225241
@@ -229,6 +245,7 @@ Item {
229245 anchors { left: parent.left; right: parent.right }
230246 height: headerContainer.height
231247 contentHeight: height
248+ opacity: headerContainer.clip || !headerContainer.showSearch ? 1 : 0 // setting visible false cause column to relayout
232249 separatorSource: ""
233250 textColor: root.scopeStyle ? root.scopeStyle.headerForeground : "grey"
234251 property var styledItem: header
@@ -296,6 +313,7 @@ Item {
296313
297314 Repeater {
298315 id: recentSearches
316+ objectName: "recentSearches"
299317 model: searchHistory
300318
301319 delegate: Standard {
@@ -304,7 +322,8 @@ Item {
304322 onClicked: {
305323 searchHistory.addQuery(text);
306324 searchTextField.text = text;
307− PopupUtils.close(popover);
325+ closePopup();
326+ unfocus();
308327 }
309328 }
310329 }
qml/Components/PassphraseLockscreen.qml+3
@@ -31,8 +31,10 @@ Item {
3131
3232 function clear(playAnimation) {
3333 pinentryField.text = "";
34+ pinentryField.enabled = true
3435 if (playAnimation) {
3536 wrongPasswordAnimation.start();
37+ pinentryField.forceActiveFocus();
3638 } else {
3739 pinentryField.focus = false
3840 }
@@ -78,6 +80,7 @@ Item {
7880
7981 onAccepted: {
8082 if (pinentryField.text) {
83+ pinentryField.enabled = false;
8184 root.entered(pinentryField.text);
8285 }
8386 }
qml/Dash/Apps/CloseIcon.qmldeleted−62
@@ -1,62 +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−
19−Item {
20− id: root
21−
22− Image {
23− id: closeIcon
24− anchors.centerIn: parent
25− source: "graphics/close_btn.png"
26−
27− state: (root.enabled) ? "shown" : "hidden"
28−
29− states: [
30− State {
31− name: "shown"
32− PropertyChanges {
33− target: closeIcon
34− height: root.height
35− width: root.width
36− }
37− },
38− State {
39− name: "hidden"
40− PropertyChanges {
41− target: closeIcon
42− height: 0
43− width: 0
44− }
45− }
46−
47− ]
48− transitions: [
49− Transition {
50− to: "shown"
51− NumberAnimation {
52− properties: "width, height"; duration: 300
53− easing { type: Easing.OutBack; overshoot: 5 }
54− }
55− },
56− Transition {
57− to: "hidden"
58− NumberAnimation { properties: "width, height"; duration: 250; }
59− }
60− ]
61− }
62−}
qml/Dash/Apps/RunningApplicationTile.qmldeleted−128
@@ -1,128 +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−import "../../Components/"
21−import "../../Components/ListItems"
22−
23−AbstractButton {
24− id: root
25− property var application
26−
27− signal requestedApplicationActivation(var application)
28− signal requestedApplicationTermination(var application)
29− signal requestedActivationMode()
30− signal requestedTerminationMode()
31−
32− // Was: childrenRect.height
33− // To avoid "binding loop" warning
34− height: shapedApplicationImage.height + labelContainer.height
35−
36− width: shapedApplicationImage.width <= units.gu(11) ? units.gu(11) : height
37−
38− property bool terminationModeEnabled: false
39−
40− onClicked: {
41− if (!terminationModeEnabled)
42− requestedApplicationActivation(application)
43− }
44−
45− onPressAndHold: {
46− if (terminationModeEnabled) {
47− requestedActivationMode()
48− } else {
49− requestedTerminationMode()
50− }
51− }
52−
53− UbuntuShape {
54− id: shapedApplicationImage
55− anchors { top: parent.top; horizontalCenter: parent.horizontalCenter }
56−
57− height: units.gu(17)
58− width: applicationImage.width
59− radius: "medium"
60−
61− image: Image {
62− id: applicationImage
63− source: application.screenshot
64− // height : width = ss.height : ss.width
65− height: shapedApplicationImage.height
66− fillMode: Image.PreserveAspectCrop
67− width: Math.min(height, height * sourceSize.width / sourceSize.height)
68− }
69−
70− }
71−
72− UbuntuShape {
73− id: borderPressed
74−
75− anchors.fill: shapedApplicationImage
76− radius: "medium"
77− borderSource: "radius_pressed.sci"
78− opacity: root.pressed ? 1.0 : 0.0
79− Behavior on opacity { NumberAnimation { duration: 200; easing.type: Easing.OutQuint } }
80− }
81−
82− // FIXME: label code duplicated with Tile
83− Item {
84− id: labelContainer
85− anchors {
86− left: parent.left
87− right: parent.right
88− top: shapedApplicationImage.bottom
89− }
90− height: units.gu(2)
91−
92− Label {
93− id: label
94− anchors {
95− baseline: parent.bottom
96− left: parent.left
97− right: parent.right
98− }
99− text: (application) ? application.name : ""
100−
101− // TODO karni: Update Ubuntu.Components.Themes.Palette and use theme color instead
102− color: "grey"
103− opacity: 0.9
104− fontSize: "small"
105− elide: Text.ElideMiddle
106− horizontalAlignment: Text.AlignHCenter
107− }
108− }
109−
110− CloseIcon {
111− objectName: "closeIcon " + model.name
112− anchors {
113− left: shapedApplicationImage.left
114− leftMargin: -units.gu(1)
115− top: parent.top
116− topMargin: -units.gu(1)
117− }
118− height: units.gu(6)
119− width: units.gu(6)
120− id: closeIcon
121− enabled: root.terminationModeEnabled
122−
123− MouseArea {
124− anchors { fill: parent; margins: -units.gu(1) }
125− onClicked: requestedApplicationTermination(application)
126− }
127− }
128−}
qml/Dash/Apps/RunningApplicationsGrid.qmldeleted−105
@@ -1,105 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import "../../Components"
19−
20−import Ubuntu.Gestures 0.1
21−import Unity.Application 0.1
22−
23−ResponsiveFlowView {
24− id: root
25− clip: true
26−
27− signal updateScreenshots
28− property alias enableHeightBehavior: heightBehaviour.enabled
29− property bool enableHeightBehaviorOnNextCreation: model.count === 0
30−
31− Behavior on height {
32− id: heightBehaviour
33− enabled: false
34− NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
35− }
36−
37− Connections {
38− target: root.model
39− onCountChanged: {
40− heightBehaviour.enabled = true;
41− }
42− }
43−
44− property bool canEnableTerminationMode: true
45−
46− onCanEnableTerminationModeChanged: {
47− if (!canEnableTerminationMode)
48− terminationModeEnabled = false
49− }
50−
51− // when false, it means it's on activation mode
52− property bool terminationModeEnabled: false
53−
54− maximumNumberOfColumns: 10
55− minimumHorizontalSpacing: units.gu(2)
56− referenceDelegateWidth: units.gu(11)
57− verticalSpacing: units.gu(2)
58−
59− delegate: Item {
60− width: runningAppTile.width + root.horizontalSpacing
61− height: runningAppTile.height + root.verticalSpacing
62−
63− RunningApplicationTile {
64− id: runningAppTile
65− objectName: "runningAppTile " + model.name
66− anchors {
67− top: parent.top
68− horizontalCenter: parent.horizontalCenter
69− }
70− application: model
71− onRequestedActivationMode: { root.terminationModeEnabled = false }
72− onRequestedTerminationMode: {
73− if (canEnableTerminationMode)
74− root.terminationModeEnabled = true
75− }
76− onRequestedApplicationTermination: {
77− ApplicationManager.stopApplication(model.appId)
78− }
79− onRequestedApplicationActivation: {
80− ApplicationManager.requestFocusApplication(model.appId)
81− }
82−
83− terminationModeEnabled: root.terminationModeEnabled
84− }
85− }
86−
87− move: Transition {
88− NumberAnimation { properties: "x,y"; duration: 400; easing.type: Easing.OutCubic }
89− }
90−
91− MouseArea {
92− anchors.fill: parent
93− z: -1 // behind all RunningApplicationTiles
94− enabled: root.terminationModeEnabled
95− onPressed: { root.terminationModeEnabled = false; }
96− }
97−
98− PressedOutsideNotifier {
99− anchors.fill: parent
100− enabled: root.terminationModeEnabled
101− onPressedOutside: {
102− root.terminationModeEnabled = false;
103− }
104− }
105−}
qml/Dash/Apps/graphics/close_btn@9.pngdeleted

Binary file not shown.

qml/Dash/CardTool.qml+27−2
@@ -165,15 +165,40 @@ Item {
165165 }
166166 }
167167
168+ Item {
169+ id: attributesModel
170+ property int numOfAttributes: 0
171+ property var model: []
172+ property bool hasAttributes: {
173+ var attributes = components["attributes"];
174+ var hasAttributesFlag = (attributes != undefined) && attributes["field"];
175+
176+ if (hasAttributesFlag) {
177+ if (attributes["max-count"]) {
178+ numOfAttributes = attributes["max-count"];
179+ }
180+ }
181+ return hasAttributesFlag
182+ }
183+
184+ onNumOfAttributesChanged: {
185+ model = []
186+ for (var i = 0; i < numOfAttributes; i++) {
187+ model.push( {"value":"text"+(i+1), "icon":"image://theme/ok" } );
188+ }
189+ }
190+ }
191+
168192 Loader {
169193 id: cardLoader
170− property var fields: ["art", "mascot", "title", "subtitle", "summary"]
194+ property var fields: ["art", "mascot", "title", "subtitle", "summary", "attributes"]
171195 property var maxData: {
172196 "art": Qt.resolvedUrl("graphics/checkers.png"),
173197 "mascot": Qt.resolvedUrl("graphics/checkers.png"),
174198 "title": "—\n—",
175199 "subtitle": "—",
176− "summary": "—\n—\n—\n—\n—"
200+ "summary": "—\n—\n—\n—\n—",
201+ "attributes": attributesModel.model
177202 }
178203 sourceComponent: cardTool.cardComponent
179204 onLoaded: {
qml/Dash/DashApps.qmldeleted−40
@@ -1,40 +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 Utils 0.1
20−import Unity.Application 0.1
21−import "../Components"
22−import "../Components/ListItems"
23−import "Apps"
24−
25−GenericScopeView {
26− id: scopeView
27− objectName: "DashApps"
28−
29− property var runningApps: ApplicationManager
30−
31− QtObject {
32− id: countObject
33− property int count: scopeView.scope.searchQuery.length == 0 ? scopeView.runningApps.count : 0
34− }
35−
36− onScopeChanged: {
37− scopeView.scope.categories.addSpecialCategory("running.apps.category", i18n.tr("Recent"), "", "{ \"template\": { \"category-layout\": \"running-apps\" } }", countObject);
38− enableHeightBehaviorOnNextCreation = scopeView.runningApps.count === 0
39− }
40−}
qml/Dash/DashContent.qml+5−3
@@ -121,10 +121,12 @@ Item {
121121 Loader {
122122 width: ListView.view.width
123123 height: ListView.view.height
124+ opacity: { // hide delegate if offscreen
125+ var xPositionRelativetoView = ListView.view.contentX - x
126+ return (xPositionRelativetoView > -width && xPositionRelativetoView < width) ? 1 : 0
127+ }
124128 asynchronous: true
125− // TODO This if will eventually go away since we're killing DashApps.qml
126− // once we move app closing to the spread
127− source: (scope.id == "clickscope") ? "DashApps.qml" : "GenericScopeView.qml"
129+ source: "GenericScopeView.qml"
128130 objectName: scope.id + " loader"
129131
130132 readonly property bool moving: item ? item.moving : false
qml/Dash/GenericScopeView.qml+1−10
@@ -18,7 +18,6 @@ import QtQuick 2.0
1818import Ubuntu.Components 0.1
1919import Utils 0.1
2020import Unity 0.2
21−import Unity.Application 0.1
2221import Dash 0.1
2322import "../Components"
2423import "../Components/ListItems" as ListItems
@@ -194,7 +193,6 @@ FocusScope {
194193 switch (cardTool.categoryLayout) {
195194 case "carousel": return "CardCarousel.qml";
196195 case "vertical-journal": return "CardVerticalJournal.qml";
197− case "running-apps": return "Apps/RunningApplicationsGrid.qml";
198196 case "grid":
199197 default: return "CardGrid.qml";
200198 }
@@ -205,14 +203,7 @@ FocusScope {
205203 item.enableHeightBehavior = scopeView.enableHeightBehaviorOnNextCreation;
206204 scopeView.enableHeightBehaviorOnNextCreation = false;
207205 }
208− if (source.toString().indexOf("Apps/RunningApplicationsGrid.qml") != -1) {
209− // TODO: this is still a kludge :D Ideally add some kind of hook so that we
210− // can do this from DashApps.qml or think a better way that needs no special casing
211− item.model = Qt.binding(function() { return runningApps; })
212− item.canEnableTerminationMode = Qt.binding(function() { return scopeView.isCurrent })
213− } else {
214− item.model = Qt.binding(function() { return results })
215− }
206+ item.model = Qt.binding(function() { return results })
216207 item.objectName = Qt.binding(function() { return categoryId })
217208 item.scopeStyle = scopeView.scopeStyle;
218209 if (baseItem.expandable) {
qml/Dash/graphics/phone/screenshots/vkb_portrait.pngnew file

Binary file not shown.

qml/Launcher/Launcher.qml+1−1
@@ -181,7 +181,7 @@ Item {
181181 bottom: parent.bottom
182182 }
183183 x: -width
184− opacity: (x == -width && dragArea.status === DirectionalDragArea.WaitingForTouch) ? 0 : 1
184+ visible: x > -width || dragArea.status === DirectionalDragArea.Undecided
185185 model: LauncherModel
186186
187187 property bool animate: true
qml/Launcher/LauncherDelegate.qml+9−6
@@ -70,28 +70,31 @@ Item {
7070 height: root.itemHeight - units.gu(1)
7171 }
7272
73− BorderImage {
73+ UbuntuShape {
7474 objectName: "countEmblem"
7575 anchors {
7676 right: parent.right
7777 top: parent.top
7878 margins: units.dp(3)
7979 }
80− width: Math.min(root.itemWidth, Math.max(units.gu(3), countLabel.implicitWidth + units.gu(2.5)))
81− height: units.gu(3)
82− source: "graphics/notification.sci"
80+ width: Math.min(root.itemWidth, Math.max(units.gu(2), countLabel.implicitWidth + units.gu(1)))
81+ height: units.gu(2)
82+ color: UbuntuColors.orange
8383 visible: root.count > 0
84+ borderSource: "none"
8485
8586 Label {
8687 id: countLabel
8788 text: root.count
8889 anchors.centerIn: parent
90+ // FIXME: verticalCenter seems to be off wee bit and QML doesn't have a centerLine
91+ // property for Text: https://bugreports.qt-project.org/browse/QTBUG-40479
92+ anchors.verticalCenterOffset: -units.dp(.5)
8993 width: root.itemWidth - units.gu(1)
9094 horizontalAlignment: Text.AlignHCenter
9195 elide: Text.ElideRight
9296 color: "white"
93− fontSize: "small"
94− font.bold: true
97+ fontSize: "x-small"
9598 }
9699 }
97100
qml/Launcher/graphics/notification@30.pngdeleted

Binary file not shown.

qml/Launcher/graphics/notification@30.scideleted−5
@@ -1,5 +0,0 @@
1−border.left: 36
2−border.top: 36
3−border.bottom: 36
4−border.right: 36
5−source: notification@30.png
qml/Panel/Indicators.qml+3
@@ -37,6 +37,7 @@ Showable {
3737 readonly property int lockThreshold: openedHeight / 2
3838 property bool fullyOpened: height == openedHeight
3939 property bool partiallyOpened: height > panelHeight && !fullyOpened
40+ property bool fullyClosed: height <= panelHeight
4041 property bool contentEnabled: true
4142 property bool initalizeItem: true
4243 readonly property alias content: menuContent
@@ -187,6 +188,7 @@ Showable {
187188 bottom: handle.top
188189 }
189190 indicatorsModel: visibleIndicators.model
191+ visible: indicators.partiallyOpened || indicators.fullyOpened
190192 clip: !indicators.fullyOpened
191193 activeHeader: indicators.state == "hint" || indicators.state == "reveal"
192194 enabled: contentEnabled
@@ -219,6 +221,7 @@ Showable {
219221 }
220222 height: Math.max(Math.min(handleImage.height, indicators.height - handleImage.height), 0)
221223 clip: height < handleImage.height
224+ visible: menuContent.visible
222225
223226 BorderImage {
224227 id: handleImage
qml/Panel/Indicators/DefaultIndicatorPage.qml+1−1
@@ -126,7 +126,7 @@ IndicatorBase {
126126 id: loader
127127 objectName: "menuItem" + index
128128 asynchronous: false
129− visible: height > 0
129+ visible: status == Loader.Ready
130130
131131 property int modelIndex: index
132132
qml/Shell.qml+183−399
@@ -19,9 +19,9 @@ import AccountsService 0.1
1919import GSettings 1.0
2020import Unity.Application 0.1
2121import Ubuntu.Components 0.1
22−import Ubuntu.Components.Popups 0.1
2322import Ubuntu.Gestures 0.1
2423import Unity.Launcher 0.1
24+import Utils 0.1
2525import LightDM 0.1 as LightDM
2626import Powerd 0.1
2727import SessionBroadcast 0.1
@@ -31,10 +31,11 @@ import "Launcher"
3131import "Panel"
3232import "Components"
3333import "Notifications"
34+import "Stages"
3435import Unity.Notifications 1.0 as NotificationBackend
3536import Unity.Session 0.1
3637
37−FocusScope {
38+Item {
3839 id: shell
3940
4041 // this is only here to select the width / height of the window if not running fullscreen
@@ -47,7 +48,7 @@ FocusScope {
4748 property url background
4849 readonly property real panelHeight: panel.panelHeight
4950
50− property bool dashShown: dash.shown
51+ property bool dashShown: dash.shown && dash.available && underlay.visible
5152
5253 property bool sideStageEnabled: shell.width >= units.gu(100)
5354 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
@@ -55,14 +56,10 @@ FocusScope {
5556 function activateApplication(appId) {
5657 if (ApplicationManager.findApplication(appId)) {
5758 ApplicationManager.requestFocusApplication(appId);
58− stages.show(true);
59− if (stages.locked && ApplicationManager.focusedApplicationId == appId) {
60− applicationsDisplayLoader.item.select(appId);
61− }
6259 } else {
6360 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
6461 ApplicationManager.startApplication(appId, execFlags);
65− stages.show(false);
62+ stages.show();
6663 }
6764 }
6865
@@ -89,82 +86,74 @@ FocusScope {
8986 id: volumeControl
9087 }
9188
92− Keys.onVolumeUpPressed: volumeControl.volumeUp()
93− Keys.onVolumeDownPressed: volumeControl.volumeDown()
89+ WindowKeysFilter {
90+ // Handle but do not filter out volume keys
91+ Keys.onVolumeUpPressed: { volumeControl.volumeUp(); event.accepted = false; }
92+ Keys.onVolumeDownPressed: { volumeControl.volumeDown(); event.accepted = false; }
9493
95− Item {
96− id: underlayClipper
97− anchors.fill: parent
98− anchors.rightMargin: stages.overlayWidth
99− clip: stages.overlayMode && !stages.painting
94+ Keys.onPressed: {
95+ if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
96+ dialogs.onPowerKeyPressed();
97+ event.accepted = true;
98+ } else {
99+ event.accepted = false;
100+ }
101+ }
100102
101− InputFilterArea {
102− anchors.fill: parent
103− blockInput: parent.clip
103+ Keys.onReleased: {
104+ if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
105+ dialogs.onPowerKeyReleased();
106+ event.accepted = true;
107+ } else {
108+ event.accepted = false;
109+ }
104110 }
111+ }
105112
106− Item {
107− id: underlay
108− objectName: "underlay"
109− anchors.fill: parent
110− anchors.rightMargin: -parent.anchors.rightMargin
113+ Item {
114+ id: underlay
115+ objectName: "underlay"
116+ anchors.fill: parent
111117
112− // Whether the underlay is fully covered by opaque UI elements.
113− property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicators.width
118+ // Whether the underlay is fully covered by opaque UI elements.
119+ property bool fullyCovered: (panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth)
120+ || stages.fullyShown || greeterWrapper.fullyShown
121+ visible: !fullyCovered
114122
115− // Whether the user should see the topmost application surface (if there's one at all).
116− readonly property bool applicationSurfaceShouldBeSeen: stages.shown && !stages.painting && !stages.overlayMode
123+ Image {
124+ anchors.fill: dash
125+ source: shell.width > shell.height ? "Dash/graphics/paper_landscape.png" : "Dash/graphics/paper_portrait.png"
126+ fillMode: Image.PreserveAspectCrop
127+ horizontalAlignment: Image.AlignRight
128+ verticalAlignment: Image.AlignTop
129+ }
117130
118− // NB! Application surfaces are stacked behind the shell one. So they can only be seen by the user
119− // through the translucent parts of the shell surface.
120− visible: !fullyCovered && !applicationSurfaceShouldBeSeen
131+ Dash {
132+ id: dash
133+ objectName: "dash"
121134
122− Rectangle {
123− anchors.fill: parent
124− color: "black"
125− opacity: dash.disappearingAnimationProgress
126− }
135+ available: !LightDM.Greeter.active
136+ hides: [stages, launcher, panel.indicators]
137+ shown: disappearingAnimationProgress !== 1.0 && greeterWrapper.showProgress !== 1.0 &&
138+ !(panel.indicators.fullyOpened && !sideStageEnabled)
139+ enabled: disappearingAnimationProgress === 0.0 && greeterWrapper.showProgress === 0.0 && edgeDemo.dashEnabled
127140
128− Image {
129− anchors.fill: dash
130− source: shell.width > shell.height ? "Dash/graphics/paper_landscape.png" : "Dash/graphics/paper_portrait.png"
131− fillMode: Image.PreserveAspectCrop
132− horizontalAlignment: Image.AlignRight
133− verticalAlignment: Image.AlignTop
141+ anchors {
142+ fill: parent
143+ topMargin: panel.panelHeight
134144 }
135145
136− Dash {
137− id: dash
138− objectName: "dash"
139−
140− available: !LightDM.Greeter.active
141− hides: [stages, launcher, panel.indicators]
142− shown: disappearingAnimationProgress !== 1.0 && greeterWrapper.showProgress !== 1.0
143− enabled: disappearingAnimationProgress === 0.0 && greeterWrapper.showProgress === 0.0 && edgeDemo.dashEnabled
144−
145− anchors {
146− fill: parent
147− topMargin: panel.panelHeight
148− }
149−
150− contentScale: 1.0 - 0.2 * disappearingAnimationProgress
151− opacity: 1.0 - disappearingAnimationProgress
152− property real disappearingAnimationProgress: {
153− if (stages.overlayMode) {
154− return 0;
155− } else {
156− return stages.showProgress
157− }
158− }
146+ contentScale: 1.0 - 0.2 * disappearingAnimationProgress
147+ opacity: 1.0 - disappearingAnimationProgress
148+ property real disappearingAnimationProgress: stages.showProgress
159149
160− // FIXME: only necessary because stages.showProgress is not animated
161− Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
162− }
150+ // FIXME: only necessary because stages.showProgress is not animated
151+ Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
163152 }
164153 }
165154
166155 EdgeDragArea {
167− id: stagesDragHandle
156+ id: stagesDragArea
168157 direction: Direction.Leftwards
169158
170159 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
@@ -174,20 +163,20 @@ FocusScope {
174163
175164 onTouchXChanged: {
176165 if (status == DirectionalDragArea.Recognized) {
177− if (ApplicationManager.count == 0) {
178− progress = Math.max(stages.width - stagesDragHandle.width + touchX, stages.width * .3)
166+ if (ApplicationManager.empty) {
167+ progress = Math.max(stages.width - stagesDragArea.width + touchX, stages.width * .3);
179168 } else {
180− progress = stages.width - stagesDragHandle.width + touchX
169+ progress = stages.width - stagesDragArea.width + touchX;
181170 }
182171 }
183172 }
184173
185174 onDraggingChanged: {
186175 if (!dragging) {
187− if (ApplicationManager.count > 0 && progress < stages.width - units.gu(10)) {
188− stages.show(true)
176+ if (!ApplicationManager.empty && progress < stages.width - units.gu(10)) {
177+ stages.show();
189178 }
190− stagesDragHandle.progress = stages.width;
179+ stagesDragArea.progress = Qt.binding(function () { return stages.width; });
191180 }
192181 }
193182 }
@@ -198,253 +187,93 @@ FocusScope {
198187 width: parent.width
199188 height: parent.height
200189
190+ visible: !fullyHidden && !ApplicationManager.empty
191+
201192 x: {
202193 if (shown) {
203− if (overlayMode || locked || greeter.fakeActiveForApp !== "") {
194+ if (locked || greeter.fakeActiveForApp !== "") {
204195 return 0;
205196 }
206− return launcher.progress
197+ return launcher.progress;
207198 } else {
208− return stagesDragHandle.progress
199+ return stagesDragArea.progress
209200 }
210201 }
211−
212202 Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.FastDuration } }
213203
214204 property bool shown: false
205+ onShownChanged: {
206+ if (shown) {
207+ if (ApplicationManager.count > 0) {
208+ ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
209+ }
210+ } else {
211+ if (ApplicationManager.focusedApplicationId) {
212+ ApplicationManager.updateScreenshot(ApplicationManager.focusedApplicationId);
213+ ApplicationManager.unfocusCurrentApplication();
214+ }
215+ }
216+ }
215217
216− property real showProgress: overlayMode ? 0 : MathUtils.clamp(1 - x / shell.width, 0, 1)
218+ // Avoid a silent "divide by zero -> NaN" situation during init as shell.width will be
219+ // zero. That breaks the property binding and the function won't be reevaluated once
220+ // shell.width is set, with the NaN result staying there for good.
221+ property real showProgress: shell.width ? MathUtils.clamp(1 - x / shell.width, 0, 1) : 0
217222
218223 property bool fullyShown: x == 0
219224 property bool fullyHidden: x == width
220225
221− property bool painting: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.painting : false
222− property bool fullscreen: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.fullscreen : false
223− property bool overlayMode: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayMode : false
224− property int overlayWidth: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayWidth : false
225226 property bool locked: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.locked : false
226227
227− function show(focusApp) {
228+ // It might technically not be fullyShown but visually it just looks so.
229+ property bool roughlyFullyShown: x >= 0 && x <= units.gu(1)
230+
231+ function show() {
228232 shown = true;
229− panel.indicators.hide();
230− edgeDemo.stopDemo();
231− greeter.hide();
232− if (!ApplicationManager.focusedApplicationId && ApplicationManager.count > 0 && focusApp) {
233− ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
234− }
235233 }
236234
237235 function hide() {
238236 shown = false;
239− if (ApplicationManager.focusedApplicationId) {
240− ApplicationManager.unfocusCurrentApplication();
241− }
242237 }
243238
244239 Connections {
245240 target: ApplicationManager
246−
247241 onFocusRequested: {
248242 if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== appId) {
249243 lockscreen.show();
250244 }
251245 greeter.hide();
252− stages.show(true);
246+ stages.show();
253247 }
254248
255249 onFocusedApplicationIdChanged: {
256250 if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== ApplicationManager.focusedApplicationId) {
257251 lockscreen.show();
258252 }
259− if (ApplicationManager.focusedApplicationId.length > 0) {
260− stages.show(false);
261− } else {
262− if (!stages.overlayMode) {
263− stages.hide();
264− }
265− }
253+ panel.indicators.hide();
266254 }
267255
268256 onApplicationAdded: {
269− stages.show(false);
270− }
271−
272− onApplicationRemoved: {
273− if (ApplicationManager.focusedApplicationId.length == 0) {
274− stages.hide();
275− }
276− }
277− }
278−
279− property bool dialogShown: false
280−
281− Component {
282− id: logoutDialog
283− Dialog {
284− id: dialogueLogout
285− title: "Logout"
286− text: "Are you sure that you want to logout?"
287− Button {
288− text: "Cancel"
289− onClicked: {
290− PopupUtils.close(dialogueLogout);
291− stages.dialogShown = false;
292− }
293− }
294− Button {
295− text: "Yes"
296− onClicked: {
297− DBusUnitySessionService.Logout();
298− PopupUtils.close(dialogueLogout);
299− stages.dialogShown = false;
300− }
301− }
302− }
303− }
304−
305− Component {
306− id: shutdownDialog
307− Dialog {
308− id: dialogueShutdown
309− title: "Shutdown"
310− text: "Are you sure that you want to shutdown?"
311− Button {
312− text: "Cancel"
313− onClicked: {
314− PopupUtils.close(dialogueShutdown);
315− stages.dialogShown = false;
316− }
317− }
318− Button {
319− text: "Yes"
320− onClicked: {
321− dBusUnitySessionServiceConnection.closeAllApps();
322− DBusUnitySessionService.Shutdown();
323− PopupUtils.close(dialogueShutdown);
324− stages.dialogShown = false;
325− }
326− }
327− }
328− }
329−
330− Component {
331− id: rebootDialog
332− Dialog {
333− id: dialogueReboot
334− title: "Reboot"
335− text: "Are you sure that you want to reboot?"
336− Button {
337− text: "Cancel"
338− onClicked: {
339− PopupUtils.close(dialogueReboot)
340− stages.dialogShown = false;
341− }
342− }
343− Button {
344− text: "Yes"
345− onClicked: {
346− dBusUnitySessionServiceConnection.closeAllApps();
347− DBusUnitySessionService.Reboot();
348− PopupUtils.close(dialogueReboot);
349− stages.dialogShown = false;
350− }
351− }
352− }
353− }
354−
355− Component {
356− id: powerDialog
357− Dialog {
358− id: dialoguePower
359− title: "Power"
360− text: i18n.tr("Are you sure you would like to turn power off?")
361− Button {
362− text: i18n.tr("Power off")
363− onClicked: {
364− dBusUnitySessionServiceConnection.closeAllApps();
365− PopupUtils.close(dialoguePower);
366− stages.dialogShown = false;
367− shutdownFadeOutRectangle.enabled = true;
368− shutdownFadeOutRectangle.visible = true;
369− shutdownFadeOut.start();
370− }
371− }
372− Button {
373− text: i18n.tr("Restart")
374− onClicked: {
375− dBusUnitySessionServiceConnection.closeAllApps();
376− DBusUnitySessionService.Reboot();
377− PopupUtils.close(dialoguePower);
378− stages.dialogShown = false;
379− }
380− }
381− Button {
382− text: i18n.tr("Cancel")
383− onClicked: {
384− PopupUtils.close(dialoguePower);
385− stages.dialogShown = false;
386− }
387− }
388− }
389− }
390−
391− function showPowerDialog() {
392− if (!stages.dialogShown) {
393− stages.dialogShown = true;
394− PopupUtils.open(powerDialog);
395− }
396− }
397−
398− Connections {
399− id: dBusUnitySessionServiceConnection
400− objectName: "dBusUnitySessionServiceConnection"
401− target: DBusUnitySessionService
402−
403− function closeAllApps() {
404− while (true) {
405− var app = ApplicationManager.get(0);
406− if (app === null) {
407− break;
408− }
409− ApplicationManager.stopApplication(app.appId);
410− }
411− }
412−
413− onLogoutRequested: {
414− // Display a dialog to ask the user to confirm.
415− if (!stages.dialogShown) {
416− stages.dialogShown = true;
417− PopupUtils.open(logoutDialog);
257+ if (greeter.shown) {
258+ greeter.hide();
418259 }
419− }
420−
421− onShutdownRequested: {
422− // Display a dialog to ask the user to confirm.
423− if (!stages.dialogShown) {
424− stages.dialogShown = true;
425− PopupUtils.open(shutdownDialog);
260+ if (!stages.shown) {
261+ stages.show();
426262 }
427263 }
428264
429− onRebootRequested: {
430− // Display a dialog to ask the user to confirm.
431− if (!stages.dialogShown) {
432− stages.dialogShown = true;
433− PopupUtils.open(rebootDialog);
265+ onEmptyChanged: {
266+ if (ApplicationManager.empty) {
267+ stages.hide();
434268 }
435269 }
436−
437− onLogoutReady: {
438− closeAllApps();
439− Qt.quit();
440− }
441270 }
442271
443272 Loader {
444273 id: applicationsDisplayLoader
445274 anchors.fill: parent
446275
447− source: shell.sideStageEnabled ? "Stages/StageWithSideStage.qml" : "Stages/PhoneStage.qml"
276+ source: shell.sideStageEnabled ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
448277
449278 Binding {
450279 target: applicationsDisplayLoader.item
@@ -453,18 +282,20 @@ FocusScope {
453282 }
454283 Binding {
455284 target: applicationsDisplayLoader.item
456− property: "moving"
457− value: !stages.fullyShown
285+ property: "dragAreaWidth"
286+ value: shell.edgeSize
458287 }
459288 Binding {
460289 target: applicationsDisplayLoader.item
461− property: "shown"
462− value: stages.shown
290+ property: "maximizedAppTopMargin"
291+ // Not just using panel.panelHeight as that changes depending on the focused app.
292+ value: panel.indicators.panelHeight
463293 }
464294 Binding {
465295 target: applicationsDisplayLoader.item
466− property: "dragAreaWidth"
467− value: shell.edgeSize
296+ property: "interactive"
297+ value: stages.roughlyFullyShown && !greeter.shown && !lockscreen.shown
298+ && panel.indicators.fullyClosed
468299 }
469300 Binding {
470301 target: applicationsDisplayLoader.item
@@ -474,6 +305,33 @@ FocusScope {
474305 }
475306 }
476307
308+ InputMethod {
309+ id: inputMethod
310+ objectName: "inputMethod"
311+ anchors { fill: parent; topMargin: panel.panelHeight }
312+ z: notifications.useModal || panel.indicators.shown ? overlay.z + 1 : overlay.z - 1
313+ }
314+
315+ Connections {
316+ target: SurfaceManager
317+ onSurfaceCreated: {
318+ if (surface.type == MirSurfaceItem.InputMethod) {
319+ inputMethod.surface = surface;
320+ }
321+ }
322+
323+ onSurfaceDestroyed: {
324+ if (inputMethod.surface == surface) {
325+ inputMethod.surface = null;
326+ surface.parent = null;
327+ }
328+ if (!surface.parent) {
329+ // there's no one displaying it. delete it right away
330+ surface.release();
331+ }
332+ }
333+ }
334+
477335 Lockscreen {
478336 id: lockscreen
479337 objectName: "lockscreen"
@@ -486,10 +344,11 @@ FocusScope {
486344 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
487345 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
488346 y: panel.panelHeight
489− x: required ? 0 : - width
347+ visible: required
490348 width: parent.width
491349 height: parent.height - panel.panelHeight
492350 background: shell.background
351+ alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
493352 minPinLength: 4
494353 maxPinLength: 4
495354
@@ -504,7 +363,7 @@ FocusScope {
504363 onShownChanged: if (shown) greeter.fakeActiveForApp = ""
505364
506365 Component.onCompleted: {
507− if (LightDM.Users.count == 1) {
366+ if (greeter.narrowMode) {
508367 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
509368 }
510369 }
@@ -516,14 +375,17 @@ FocusScope {
516375 onShowGreeter: greeter.show()
517376
518377 onShowPrompt: {
519− if (LightDM.Users.count == 1) {
520− // TODO: There's no better way for now to determine if its a PIN or a passphrase.
521− if (text == "PIN") {
522− lockscreen.alphaNumeric = false
523− } else {
524− lockscreen.alphaNumeric = true
525− }
526− lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
378+ if (greeter.narrowMode) {
379+ lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text.toLowerCase());
380+ lockscreen.show();
381+ }
382+ }
383+
384+ onPromptlessChanged: {
385+ if (LightDM.Greeter.promptless) {
386+ lockscreen.hide()
387+ } else {
388+ lockscreen.reset();
527389 lockscreen.show();
528390 }
529391 }
@@ -537,6 +399,9 @@ FocusScope {
537399 greeter.login();
538400 } else {
539401 lockscreen.clear(true);
402+ if (greeter.narrowMode) {
403+ LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
404+ }
540405 }
541406 }
542407 }
@@ -566,6 +431,7 @@ FocusScope {
566431 StandardAnimation {}
567432 }
568433
434+ property bool fullyShown: showProgress === 1.0
569435 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
570436 onShowProgressChanged: if (LightDM.Greeter.promptless && showProgress === 0) greeter.login()
571437
@@ -590,25 +456,24 @@ FocusScope {
590456
591457 function login() {
592458 enabled = false;
593− LightDM.Greeter.startSessionSync();
594− sessionStarted();
595− greeter.hide();
596− lockscreen.hide();
597− launcher.hide();
459+ if (LightDM.Greeter.startSessionSync()) {
460+ sessionStarted();
461+ greeter.hide();
462+ lockscreen.hide();
463+ launcher.hide();
464+ }
598465 enabled = true;
599466 }
600467
601468 onShownChanged: {
602469 if (shown) {
603− lockscreen.reset();
604− if (!LightDM.Greeter.promptless) {
605− lockscreen.show();
606− }
607− // If there is only one user, we start authenticating with that one here.
608− // If there are more users, the Greeter will handle that
609− if (LightDM.Users.count == 1) {
470+ if (greeter.narrowMode) {
610471 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
611472 }
473+ if (!LightDM.Greeter.promptless) {
474+ lockscreen.reset();
475+ lockscreen.show();
476+ }
612477 greeter.fakeActiveForApp = "";
613478 greeter.forceActiveFocus();
614479 }
@@ -632,12 +497,6 @@ FocusScope {
632497 }
633498 }
634499
635− InputFilterArea {
636− anchors.fill: parent
637− blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
638− || panel.indicators.shown
639− }
640−
641500 Connections {
642501 id: powerConnection
643502 target: Powerd
@@ -680,7 +539,7 @@ FocusScope {
680539 return;
681540 }
682541
683− if (stages.shown && !stages.overlayMode && !stages.locked) {
542+ if (!stages.locked) {
684543 stages.hide();
685544 launcher.fadeOut();
686545 } else {
@@ -695,6 +554,7 @@ FocusScope {
695554
696555 Item {
697556 id: overlay
557+ z: 10
698558
699559 anchors.fill: parent
700560
@@ -709,29 +569,13 @@ FocusScope {
709569 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
710570 panelHeight: units.gu(3)
711571 }
712− property string focusedAppId: ApplicationManager.focusedApplicationId
713− property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
714− fullscreenMode: (focusedApplication && stages.fullscreen && !LightDM.Greeter.active) || greeter.fakeActiveForApp !== ""
715572
716− InputFilterArea {
717− anchors {
718− top: parent.top
719− left: parent.left
720− right: parent.right
721− }
722− height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
723− blockInput: true
724− }
725− }
573+ property bool topmostApplicationIsFullscreen:
574+ ApplicationManager.focusedApplicationId &&
575+ ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
726576
727− InputFilterArea {
728− blockInput: launcher.shown
729− anchors {
730− top: parent.top
731− bottom: parent.bottom
732− left: parent.left
733− }
734− width: launcher.width
577+ fullscreenMode: (stages.roughlyFullyShown && topmostApplicationIsFullscreen
578+ && !LightDM.Greeter.active) || greeter.fakeActiveForApp !== ""
735579 }
736580
737581 Launcher {
@@ -774,11 +618,6 @@ FocusScope {
774618 MouseArea {
775619 anchors.fill: parent
776620 }
777−
778− InputFilterArea {
779− anchors.fill: parent
780− blockInput: modalNotificationBackground.visible
781− }
782621 }
783622
784623 Notifications {
@@ -804,36 +643,7 @@ FocusScope {
804643 PropertyChanges { target: notifications; width: units.gu(38) }
805644 }
806645 ]
807−
808− InputFilterArea {
809− anchors { left: parent.left; right: parent.right }
810− height: parent.contentHeight
811− blockInput: height > 0
812− }
813− }
814− }
815−
816− focus: true
817− onFocusChanged: if (!focus) forceActiveFocus();
818−
819− InputFilterArea {
820− anchors {
821− top: parent.top
822− bottom: parent.bottom
823− left: parent.left
824− }
825− width: shell.edgeSize
826− blockInput: true
827− }
828−
829− InputFilterArea {
830− anchors {
831− top: parent.top
832− bottom: parent.bottom
833− right: parent.right
834646 }
835− width: shell.edgeSize
836− blockInput: true
837647 }
838648
839649 Binding {
@@ -842,24 +652,22 @@ FocusScope {
842652 value: "unity8"
843653 }
844654
845− OSKController {
846− anchors.topMargin: panel.panelHeight
847− anchors.fill: parent // as needs to know the geometry of the shell
848− }
849−
850− //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
851− MouseArea {
852− anchors.bottom: parent.bottom
853− anchors.left: parent.left
854− anchors.right: parent.right
855− height: ApplicationManager.keyboardVisible ? ApplicationManager.keyboardHeight : 0
856−
857− enabled: ApplicationManager.keyboardVisible
655+ Dialogs {
656+ id: dialogs
657+ anchors.fill: parent
658+ z: overlay.z + 10
659+ onPowerOffClicked: {
660+ shutdownFadeOutRectangle.enabled = true;
661+ shutdownFadeOutRectangle.visible = true;
662+ shutdownFadeOut.start();
663+ }
858664 }
859665
860666 Label {
667+ id: alphaDisclaimerLabel
861668 anchors.centerIn: parent
862669 visible: ApplicationManager.fake ? ApplicationManager.fake : false
670+ z: dialogs.z + 10
863671 text: "EARLY ALPHA\nNOT READY FOR USE"
864672 color: "lightgrey"
865673 opacity: 0.2
@@ -873,6 +681,7 @@ FocusScope {
873681
874682 EdgeDemo {
875683 id: edgeDemo
684+ z: alphaDisclaimerLabel.z + 10
876685 greeter: greeter
877686 launcher: launcher
878687 dash: dash
@@ -885,35 +694,9 @@ FocusScope {
885694 onShowHome: showHome()
886695 }
887696
888− Keys.onPressed: {
889− if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
890− if (!powerKeyTimer.running) {
891− powerKeyTimer.start();
892− }
893− event.accepted = true;
894− }
895− }
896−
897− Keys.onReleased: {
898− if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
899− powerKeyTimer.stop();
900− event.accepted = true;
901− }
902− }
903−
904− Timer {
905− id: powerKeyTimer
906− interval: 2000
907− repeat: false
908− triggeredOnStart: false
909−
910− onTriggered: {
911− stages.showPowerDialog();
912− }
913− }
914−
915697 Rectangle {
916698 id: shutdownFadeOutRectangle
699+ z: edgeDemo.z + 10
917700 enabled: false
918701 visible: false
919702 color: "black"
@@ -930,4 +713,5 @@ FocusScope {
930713 }
931714 }
932715 }
716+
933717}
qml/Stages/Animations/BaseSurfaceAnimation.qmlnew file+93
@@ -0,0 +1,93 @@
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+
19+/* This is the base case for surface animations, used when adding/removing * child surfaces.
20+ * The class is meant to be overridden and changes/animations provided for state changes.
21+ * NB. It is important to release the surface at the end of the "out" animation.
22+ *
23+ * Example - Simple fade in/out
24+ *
25+ * BaseSurfaceAnimation {
26+ * outChanges: [ PropertyChanges { target: animation.surface; opacity: 0.0 } ]
27+ * outAnimations: [
28+ SequentialAnimation {
29+ * NumberAnimation { target: animation.surface; property: "opacity"; duration: 300 }
30+ * ScriptAction { script: { if (animation.parent.removing) animation.surface.release(); } }
31+ * }
32+ * ]
33+ *
34+ * inChanges: [ PropertyChanges { target: animation.surface; opacity: 1.0 } ]
35+ * inAnimations: [ NumberAnimation { target: animation.surface; property: "opacity"; duration: 300 } ]
36+ * }
37+ */
38+Item {
39+ id: base
40+ property Item surface: null
41+
42+ // changes applied when state changes to "in"
43+ property list<QtObject> outChanges
44+ // transition animations when changing state to "in"
45+ property list<QtObject> outAnimations
46+
47+ // changes applied when state changes to "out"
48+ property list<QtObject> inChanges
49+ // transition animations when changing state to "out"
50+ property list<QtObject> inAnimations
51+
52+ function start() {
53+ // "prep" state forces outChanges without transition animations.
54+ state = "prep"
55+ state = "in";
56+ }
57+ function end() {
58+ state = "out";
59+ }
60+
61+ states: [
62+ State {
63+ name: "baseAnimation"
64+ PropertyChanges { target: base.surface; anchors.fill: undefined }
65+ },
66+
67+ State {
68+ name: "prep"
69+ extend: "baseAnimation"
70+ changes: outChanges
71+ },
72+ State {
73+ name: "out"
74+ extend: "prep"
75+ },
76+ State {
77+ name: "in"
78+ extend: "baseAnimation"
79+ changes: inChanges
80+ }
81+ ]
82+
83+ transitions: [
84+ Transition {
85+ to: "out"
86+ animations: outAnimations
87+ },
88+ Transition {
89+ to: "in"
90+ animations: inAnimations
91+ }
92+ ]
93+}
qml/Stages/Animations/DarkenAndFadeInAnimation.qmlnew file+48
@@ -0,0 +1,48 @@
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 1.1
19+
20+BaseSurfaceAnimation {
21+ id: animation
22+
23+ property Rectangle darkenItem: Rectangle {
24+ parent: animation.surface.parent
25+ anchors.fill: parent
26+ color: Qt.rgba(0,0,0,0.0)
27+ }
28+
29+ outChanges: [ PropertyChanges { target: animation.surface; opacity: 0.0 } ]
30+ outAnimations: [
31+ SequentialAnimation {
32+ UbuntuNumberAnimation { target: animation.surface; property: "opacity"; duration: UbuntuAnimation.FastDuration }
33+ ColorAnimation { target: darkenItem; duration: UbuntuAnimation.FastDuration }
34+ ScriptAction { script: { if (animation.parent.removing) animation.surface.release(); } }
35+ }
36+ ]
37+
38+ inChanges: [
39+ PropertyChanges { target: darkenItem; color: Qt.rgba(0,0,0,0.7) },
40+ PropertyChanges { target: animation.surface; opacity: 1.0 }
41+ ]
42+ inAnimations: [
43+ SequentialAnimation {
44+ ColorAnimation { target: darkenItem; duration: UbuntuAnimation.FastDuration }
45+ UbuntuNumberAnimation { target: animation.surface; property: "opacity"; duration: UbuntuAnimation.FastDuration }
46+ }
47+ ]
48+}
qml/Stages/Animations/SwipeFromBottomAnimation.qmlnew file+48
@@ -0,0 +1,48 @@
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+
19+BaseSurfaceAnimation {
20+ id: animation
21+
22+ outChanges: [ AnchorChanges { target: animation.surface; anchors.top: animation.parent.bottom } ]
23+ outAnimations: [
24+ SequentialAnimation {
25+ PropertyAction { target: animation.parent; property: "clip"; value: true }
26+ AnchorAnimation { easing.type: Easing.InOutQuad; duration: 400 }
27+ PropertyAction { target: animation.surface; property: "visible"; value: !animation.parent.removing }
28+ PropertyAction { target: animation.parent; property: "clip"; value: false }
29+ ScriptAction { script: { if (animation.parent.removing) animation.surface.release(); } }
30+ }
31+ ]
32+
33+ inChanges: [
34+ AnchorChanges {
35+ target: animation.surface;
36+ anchors.top: animation.parent.top
37+ anchors.right: undefined
38+ anchors.bottom: undefined
39+ anchors.left: undefined
40+ } ]
41+ inAnimations: [
42+ SequentialAnimation {
43+ PropertyAction { target: animation.parent; property: "clip"; value: true }
44+ AnchorAnimation { easing.type: Easing.InOutQuad; duration: 400 }
45+ PropertyAction { target: animation.parent; property: "clip"; value: false }
46+ }
47+ ]
48+}
qml/Stages/Animations/SwipeUpAnimation.qmlnew file+49
@@ -0,0 +1,49 @@
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+
19+BaseSurfaceAnimation {
20+ id: animation
21+
22+ outAnimations: [
23+ SequentialAnimation {
24+ PropertyAction { target: animation.parent; property: "clip"; value: true }
25+ PropertyAction { target: animation.surface; property: "visible"; value: true }
26+ AnchorAnimation { easing.type: Easing.InOutQuad; duration: 400 }
27+ PropertyAction { target: animation.parent; property: "clip"; value: false }
28+ ScriptAction { script: { if (animation.parent.removing) animation.surface.release(); } }
29+ }
30+ ]
31+
32+ inChanges: [
33+ AnchorChanges {
34+ target: animation.surface;
35+ anchors.top: undefined
36+ anchors.right: undefined
37+ anchors.bottom: animation.parent.top
38+ anchors.left: undefined
39+ }
40+ ]
41+ inAnimations: [
42+ SequentialAnimation {
43+ PropertyAction { target: animation.parent; property: "clip"; value: true }
44+ AnchorAnimation { easing.type: Easing.InOutQuad; duration: 400 }
45+ PropertyAction { target: animation.surface; property: "visible"; value: false}
46+ PropertyAction { target: animation.parent; property: "clip"; value: false }
47+ }
48+ ]
49+}
qml/Stages/PhoneStage.qml+171−263
@@ -18,7 +18,6 @@ import QtQuick 2.0
1818import Ubuntu.Components 0.1
1919import Ubuntu.Gestures 0.1
2020import Unity.Application 0.1
21−import LightDM 0.1 as LightDM
2221import Utils 0.1
2322import "../Components"
2423
@@ -26,88 +25,51 @@ Item {
2625 id: root
2726
2827 // Controls to be set from outside
29− property bool shown: false
30− property bool moving: false
3128 property int dragAreaWidth
29+ property real maximizedAppTopMargin
30+ property bool interactive
31+ property bool spreadEnabled: true // If false, animations and right edge will be disabled
3232
3333 // State information propagated to the outside
34− readonly property bool painting: mainScreenshotImage.visible || fadeInScreenshotImage.visible || appSplash.visible || spreadView.visible
35− property bool fullscreen: priv.focusedApplication ? priv.focusedApplication.fullscreen : false
36− property bool locked: spreadView.visible
37− property bool spreadEnabled: true
38−
39− // Not used for PhoneStage, only useful for SideStage and similar
40− property bool overlayMode: false
41− property int overlayWidth: 0
34+ readonly property bool locked: spreadView.phase == 2
4235
4336 function select(appId) {
44− spreadView.snapTo(priv.indexOf(appId))
37+ spreadView.snapTo(priv.indexOf(appId));
4538 }
4639
47− onMovingChanged: {
48− if (moving) {
49− if (ApplicationManager.focusedApplicationId) {
50− priv.requestNewScreenshot();
51− } else {
52− mainScreenshotImage.anchors.leftMargin = 0;
53− mainScreenshotImage.source = ApplicationManager.get(0).screenshot;
54− mainScreenshotImage.visible = true;
55− }
56− } else {
57− mainScreenshotImage.visible = false;
58− }
40+ onWidthChanged: {
41+ spreadView.selectedIndex = -1;
42+ spreadView.phase = 0;
43+ spreadView.contentX = -spreadView.shift;
5944 }
6045
6146 Connections {
6247 target: ApplicationManager
6348
6449 onFocusRequested: {
65− if (spreadView.visible) {
50+ if (spreadView.phase > 0) {
6651 spreadView.snapTo(priv.indexOf(appId));
6752 } else {
6853 priv.switchToApp(appId);
6954 }
7055 }
7156
72− onFocusedApplicationIdChanged: {
73− if (ApplicationManager.focusedApplicationId.length > 0) {
74− if (priv.secondApplicationStarting || priv.applicationStarting) {
75− appSplashTimer.restart();
76− } else {
77− var application = priv.focusedApplication;
78− root.fullscreen = application.fullscreen;
79− mainScreenshotImage.source = application.screenshot;
80− }
57+ onApplicationAdded: {
58+ if (spreadView.phase == 2) {
59+ spreadView.snapTo(ApplicationManager.count - 1);
8160 } else {
82− spreadView.selectedIndex = -1;
8361 spreadView.phase = 0;
8462 spreadView.contentX = -spreadView.shift;
85− }
86− }
87−
88− onApplicationAdded: {
89− if (!priv.focusedApplication) {
90− mainScreenshotImage.source = "";
91− mainScreenshotImage.visible = false;
92− priv.applicationStarting = true;
93− } else {
94− mainScreenshotImage.source = "";
95− priv.newFocusedAppId = appId;
96− priv.secondApplicationStarting = true;
97− priv.requestNewScreenshot();
98− }
99−
100− if (spreadView.visible) {
101− spreadView.snapTo(0);
63+ priv.switchToApp(appId);
10264 }
10365 }
10466
10567 onApplicationRemoved: {
106− if (ApplicationManager.count == 0) {
107− mainScreenshotImage.source = ""
108− mainScreenshotImage.visible = false;
109− } else {
110− mainScreenshotImage.source = ApplicationManager.get(0).screenshot;
68+ // Unless we're closing the app ourselves in the spread,
69+ // lets make sure the spread doesn't mess up by the changing app list.
70+ if (spreadView.closingIndex == -1) {
71+ spreadView.phase = 0;
72+ spreadView.contentX = -spreadView.shift;
11173 }
11274 }
11375 }
@@ -117,46 +79,11 @@ Item {
11779
11880 property string focusedAppId: ApplicationManager.focusedApplicationId
11981 property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
120− property url focusedScreenshot: focusedApplication ? focusedApplication.screenshot : ""
121−
122− property bool waitingForScreenshot: false
123−
124− property bool applicationStarting: false
125− property bool secondApplicationStarting: false
126−
127− property string newFocusedAppId
128−
129− onFocusedScreenshotChanged: {
130− if (root.moving && priv.waitingForScreenshot) {
131− mainScreenshotImage.anchors.leftMargin = 0;
132− mainScreenshotImage.source = priv.focusedScreenshot
133− mainScreenshotImage.visible = true;
134− } else if (priv.secondApplicationStarting && priv.waitingForScreenshot) {
135− applicationSwitchingAnimation.start();
136− if (LightDM.Greeter.active && !LightDM.Greeter.promptless) {
137− // When greeter is up, no fancy animations, since user may
138− // glimpse an app they shouldn't
139− applicationSwitchingAnimation.complete();
140− }
141− }
142− waitingForScreenshot = false;
143− }
144−
145− function requestNewScreenshot() {
146− waitingForScreenshot = true;
147− ApplicationManager.updateScreenshot(focusedAppId);
148− }
14982
15083 function switchToApp(appId) {
15184 if (priv.focusedAppId) {
152− priv.newFocusedAppId = appId;
153− root.fullscreen = ApplicationManager.findApplication(appId).fullscreen;
154− applicationSwitchingAnimation.start();
155− if (LightDM.Greeter.active && !LightDM.Greeter.promptless) {
156− // When greeter is up, no fancy animations, since user may
157− // glimpse an app they shouldn't
158− applicationSwitchingAnimation.complete();
159− }
85+ spreadView.focusChanging = true;
86+ ApplicationManager.focusApplication(appId);
16087 } else {
16188 ApplicationManager.focusApplication(appId);
16289 }
@@ -173,158 +100,6 @@ Item {
173100
174101 }
175102
176− // FIXME: the signal connections seems to get lost.
177− Connections {
178− target: priv.focusedApplication
179− onScreenshotChanged: priv.focusedScreenshot = priv.focusedApplication.screenshot
180− }
181− Binding {
182− target: root
183− property: "fullscreen"
184− value: priv.focusedApplication ? priv.focusedApplication.fullscreen : false
185− }
186−
187− Timer {
188− id: appSplashTimer
189− // FIXME: We really need to show something meaningful in the app surface instead of guessing
190− // when it might be ready
191− interval: 500
192− repeat: false
193− onTriggered: {
194− priv.applicationStarting = false;
195− priv.secondApplicationStarting = false;
196− }
197− }
198−
199− SequentialAnimation {
200− id: applicationSwitchingAnimation
201− // setup
202− PropertyAction { target: mainScreenshotImage; property: "anchors.leftMargin"; value: 0 }
203− PropertyAction { target: mainScreenshotImage; property: "source"; value: priv.focusedScreenshot }
204− PropertyAction { targets: [mainScreenshotImage, fadeInScreenshotImage]; property: "visible"; value: true }
205− PropertyAction { target: fadeInScreenshotImage; property: "source"; value: {
206− var newFocusedApp = ApplicationManager.findApplication(priv.newFocusedAppId);
207− return newFocusedApp ? newFocusedApp.screenshot : "" }
208− }
209− PropertyAction { target: fadeInScreenshotImage; property: "opacity"; value: 0 }
210− PropertyAction { target: fadeInScreenshotImage; property: "scale"; value: .8 }
211−
212−
213− // The actual animation
214− ParallelAnimation {
215− UbuntuNumberAnimation { target: mainScreenshotImage; property: "anchors.leftMargin"; to: root.width; duration: UbuntuAnimation.SlowDuration }
216− UbuntuNumberAnimation { target: fadeInScreenshotImage; properties: "opacity,scale"; to: 1; duration: UbuntuAnimation.SlowDuration }
217− }
218−
219− // restore stuff
220− ScriptAction { script: ApplicationManager.focusApplication(priv.newFocusedAppId); }
221− PropertyAction { target: fadeInScreenshotImage; property: "visible"; value: false }
222− PropertyAction { target: mainScreenshotImage; property: "visible"; value: false }
223− }
224−
225− // FIXME: Drop this and make the imageprovider show a splashscreen instead
226− Rectangle {
227− id: appSplash2
228− anchors.fill: parent
229− color: "black"
230− visible: priv.secondApplicationStarting
231− }
232− Image {
233− id: fadeInScreenshotImage
234− anchors { left: parent.left; bottom: parent.bottom }
235− width: parent.width
236− scale: .7
237− visible: false
238− }
239−
240− Rectangle {
241− id: appSplash
242− anchors.fill: parent
243− color: "black"
244− visible: priv.applicationStarting
245−
246− WaitingDots {
247− }
248− }
249− Image {
250− id: mainScreenshotImage
251− anchors { left: parent.left; bottom: parent.bottom }
252− width: parent.width
253− visible: false
254− }
255−
256− EdgeDragArea {
257− id: spreadDragArea
258− direction: Direction.Leftwards
259− enabled: ApplicationManager.count > 1 && spreadView.phase != 2 && root.spreadEnabled
260−
261− anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
262− width: root.dragAreaWidth
263−
264− // Sitting at the right edge of the screen, this EdgeDragArea directly controls the spreadView when
265− // attachedToView is true. When the finger movement passes positionMarker3 we detach it from the
266− // spreadView and make the spreadView snap to positionMarker4.
267− property bool attachedToView: true
268−
269− property var gesturePoints: new Array()
270−
271− onTouchXChanged: {
272− if (!dragging && !priv.waitingForScreenshot) {
273− // Initial touch. Let's update the screenshot and reset the spreadView to the starting position.
274− priv.requestNewScreenshot();
275− spreadView.phase = 0;
276− spreadView.contentX = -spreadView.shift;
277− }
278− if (dragging && attachedToView) {
279− // Gesture recognized. Let's move the spreadView with the finger
280− spreadView.contentX = -touchX - spreadView.shift;
281− }
282− if (attachedToView && spreadView.shiftedContentX >= spreadView.width * spreadView.positionMarker3) {
283− // We passed positionMarker3. Detach from spreadView and snap it.
284− attachedToView = false;
285− spreadView.snap();
286− }
287− gesturePoints.push(touchX);
288− }
289−
290− onStatusChanged: {
291− if (status == DirectionalDragArea.Recognized) {
292− attachedToView = true;
293− }
294− }
295−
296− onDraggingChanged: {
297− if (dragging) {
298− // Gesture recognized. Start recording this gesture
299− gesturePoints = [];
300− return;
301− }
302−
303− // Ok. The user released. Find out if it was a one-way movement.
304− var oneWayFlick = true;
305− var smallestX = spreadDragArea.width;
306− for (var i = 0; i < gesturePoints.length; i++) {
307− if (gesturePoints[i] >= smallestX) {
308− oneWayFlick = false;
309− break;
310− }
311− smallestX = gesturePoints[i];
312− }
313− gesturePoints = [];
314−
315− if (oneWayFlick && spreadView.shiftedContentX > units.gu(2) &&
316− spreadView.shiftedContentX < spreadView.positionMarker1 * spreadView.width) {
317− // If it was a short one-way movement, do the Alt+Tab switch
318− // no matter if we didn't cross positionMarker1 yet.
319− spreadView.snapTo(1);
320− } else if (!dragging && attachedToView) {
321− // otherwise snap to the closest snap position we can find
322− // (might be back to start, to app 1 or to spread)
323− spreadView.snap();
324− }
325− }
326− }
327−
328103 Rectangle {
329104 id: coverFlipBackground
330105 anchors.fill: parent
@@ -332,16 +107,12 @@ Item {
332107 visible: spreadView.visible
333108 }
334109
335− InputFilterArea {
336− anchors.fill: root
337− blockInput: spreadView.visible
338− }
339110
340111 Flickable {
341112 id: spreadView
342113 objectName: "spreadView"
343114 anchors.fill: parent
344− visible: spreadDragArea.status == DirectionalDragArea.Recognized || phase > 1 || snapAnimation.running
115+ interactive: (spreadDragArea.status == DirectionalDragArea.Recognized || phase > 1) && draggedIndex == -1
345116 contentWidth: spreadRow.width - shift
346117 contentX: -shift
347118
@@ -350,8 +121,8 @@ Item {
350121 // that, the beginning of the gesture starts with a negative value for contentX
351122 // so the flickable wants to pull it into the view already. "shift" tunes the
352123 // distance where to "lock" the content.
353− property real shift: width / 2
354− property real shiftedContentX: contentX + shift
124+ readonly property real shift: width / 2
125+ readonly property real shiftedContentX: contentX + shift
355126
356127 property int tileDistance: width / 4
357128
@@ -375,6 +146,10 @@ Item {
375146 property int phase: 0
376147
377148 property int selectedIndex: -1
149+ property int draggedIndex: -1
150+ property int closingIndex: -1
151+
152+ property bool focusChanging: false
378153
379154 onShiftedContentXChanged: {
380155 switch (phase) {
@@ -398,9 +173,9 @@ Item {
398173 snapAnimation.targetContentX = -shift;
399174 snapAnimation.start();
400175 } else if (shiftedContentX < positionMarker2 * width) {
401− snapTo(1)
176+ snapTo(1);
402177 } else if (shiftedContentX < positionMarker3 * width) {
403− snapTo(1)
178+ snapTo(1);
404179 } else if (phase < 2){
405180 // Add 1 pixel to make sure we definitely hit positionMarker4 even with rounding errors of the animation.
406181 snapAnimation.targetContentX = width * positionMarker4 + 1 - shift;
@@ -408,9 +183,18 @@ Item {
408183 }
409184 }
410185 function snapTo(index) {
186+ if (ApplicationManager.count <= index) {
187+ // In case we're trying to snap to some non existing app, lets snap back to the first one
188+ index = 0;
189+ }
411190 spreadView.selectedIndex = index;
412− root.fullscreen = ApplicationManager.get(index).fullscreen;
413− snapAnimation.targetContentX = -shift;
191+ // If we're not in full spread mode yet, always unwind to start pos
192+ // otherwise unwind up to progress 0 of the selected index
193+ if (spreadView.phase < 2) {
194+ snapAnimation.targetContentX = -shift;
195+ } else {
196+ snapAnimation.targetContentX = -shift + index * spreadView.tileDistance;
197+ }
414198 snapAnimation.start();
415199 }
416200
@@ -429,7 +213,8 @@ Item {
429213 script: {
430214 if (spreadView.selectedIndex >= 0) {
431215 ApplicationManager.focusApplication(ApplicationManager.get(spreadView.selectedIndex).appId);
432− spreadView.selectedIndex = -1
216+
217+ spreadView.selectedIndex = -1;
433218 spreadView.phase = 0;
434219 spreadView.contentX = -spreadView.shift;
435220 }
@@ -443,6 +228,15 @@ Item {
443228 // tileDistance * app count (with a minimum of 3 apps, in order to also allow moving 1 and 2 apps a bit)
444229 // + some constant value (still scales with the screen width) which looks good and somewhat fills the screen
445230 width: Math.max(3, ApplicationManager.count) * spreadView.tileDistance + (spreadView.width - spreadView.tileDistance) * 1.5
231+ Behavior on width {
232+ enabled: spreadView.closingIndex >= 0
233+ UbuntuNumberAnimation {}
234+ }
235+ onWidthChanged: {
236+ if (spreadView.closingIndex >= 0) {
237+ spreadView.contentX = Math.min(spreadView.contentX, width - spreadView.width - spreadView.shift);
238+ }
239+ }
446240
447241 x: spreadView.contentX
448242
@@ -462,18 +256,55 @@ Item {
462256 height: spreadView.height
463257 selected: spreadView.selectedIndex == index
464258 otherSelected: spreadView.selectedIndex >= 0 && !selected
259+ interactive: !spreadView.interactive && spreadView.phase === 0
260+ && spreadView.shiftedContentX === 0 && root.interactive && index === 0
261+ swipeToCloseEnabled: spreadView.interactive
262+ maximizedAppTopMargin: root.maximizedAppTopMargin
263+ dropShadow: spreadView.shiftedContentX > 0 || spreadDragArea.status == DirectionalDragArea.Undecided
465264
466− z: index
265+ z: behavioredIndex
467266 x: index == 0 ? 0 : spreadView.width + (index - 1) * spreadView.tileDistance
267+ property real behavioredIndex: index
268+ Behavior on behavioredIndex {
269+ enabled: spreadView.closingIndex >= 0
270+ UbuntuNumberAnimation {
271+ onRunningChanged: {
272+ if (!running) {
273+ spreadView.closingIndex = -1;
274+ }
275+ }
276+ }
277+ }
278+
279+ Behavior on x {
280+ enabled: spreadView.focusChanging && index == 0 && root.spreadEnabled
281+ UbuntuNumberAnimation {
282+ duration: UbuntuAnimation.FastDuration
283+ onRunningChanged: {
284+ if (!running) {
285+ spreadView.focusChanging = false;
286+ }
287+ }
288+ }
289+ }
468290
469291 // Each tile has a different progress value running from 0 to 1.
470− // A progress value of 0 means the tile is at the right edge. 1 means the tile has reched the left edge.
292+ // 0: means the tile is at the right edge.
293+ // 1: means the tile has finished the main animation towards the left edge.
294+ // >1: after the main animation has finished, tiles will continue to move very slowly to the left
471295 progress: {
472− var tileProgress = (spreadView.shiftedContentX - index * spreadView.tileDistance) / spreadView.width;
296+ var tileProgress = (spreadView.shiftedContentX - behavioredIndex * spreadView.tileDistance) / spreadView.width;
473297 // Tile 1 needs to move directly from the beginning...
474− if (index == 1 && spreadView.phase < 2) {
298+ if (behavioredIndex == 1 && spreadView.phase < 2) {
475299 tileProgress += spreadView.tileDistance / spreadView.width;
476300 }
301+ // Limiting progress to ~0 and 1.7 to avoid binding calculations when tiles are not
302+ // visible.
303+ // < 0 : The tile is outside the screen on the right
304+ // > 1.7: The tile is *very* close to the left edge and covered by other tiles now.
305+ // Using 0.0001 to differentiate when a tile should still be visible (==0)
306+ // or we can hide it (< 0)
307+ tileProgress = Math.max(-0.0001, Math.min(1.7, tileProgress));
477308 return tileProgress;
478309 }
479310
@@ -493,7 +324,7 @@ Item {
493324
494325 EasingCurve {
495326 id: snappingCurve
496− type: EasingCurve.OutQuad
327+ type: EasingCurve.Linear
497328 period: 0.05
498329 progress: appDelegate.progress - spreadView.positionMarker1
499330 }
@@ -507,7 +338,84 @@ Item {
507338 }
508339 }
509340 }
341+
342+ onClosed: {
343+ spreadView.draggedIndex = -1;
344+ spreadView.closingIndex = index;
345+ ApplicationManager.stopApplication(ApplicationManager.get(index).appId);
346+ }
347+ }
348+ }
349+ }
350+ }
351+
352+ EdgeDragArea {
353+ id: spreadDragArea
354+ direction: Direction.Leftwards
355+ enabled: spreadView.phase != 2 && root.spreadEnabled
356+
357+ anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
358+ width: root.dragAreaWidth
359+
360+ // Sitting at the right edge of the screen, this EdgeDragArea directly controls the spreadView when
361+ // attachedToView is true. When the finger movement passes positionMarker3 we detach it from the
362+ // spreadView and make the spreadView snap to positionMarker4.
363+ property bool attachedToView: true
364+
365+ property var gesturePoints: new Array()
366+
367+ onTouchXChanged: {
368+ if (!dragging) {
369+ // Initial touch. Let's reset the spreadView to the starting position.
370+ spreadView.phase = 0;
371+ spreadView.contentX = -spreadView.shift;
372+ }
373+ if (dragging && attachedToView) {
374+ // Gesture recognized. Let's move the spreadView with the finger
375+ spreadView.contentX = -touchX + spreadDragArea.width - spreadView.shift;
376+ }
377+ if (attachedToView && spreadView.shiftedContentX >= spreadView.width * spreadView.positionMarker3) {
378+ // We passed positionMarker3. Detach from spreadView and snap it.
379+ attachedToView = false;
380+ spreadView.snap();
381+ }
382+ gesturePoints.push(touchX);
383+ }
384+
385+ onStatusChanged: {
386+ if (status == DirectionalDragArea.Recognized) {
387+ attachedToView = true;
388+ }
389+ }
390+
391+ onDraggingChanged: {
392+ if (dragging) {
393+ // Gesture recognized. Start recording this gesture
394+ gesturePoints = [];
395+ return;
396+ }
397+
398+ // Ok. The user released. Find out if it was a one-way movement.
399+ var oneWayFlick = true;
400+ var smallestX = spreadDragArea.width;
401+ for (var i = 0; i < gesturePoints.length; i++) {
402+ if (gesturePoints[i] >= smallestX) {
403+ oneWayFlick = false;
404+ break;
510405 }
406+ smallestX = gesturePoints[i];
407+ }
408+ gesturePoints = [];
409+
410+ if (oneWayFlick && spreadView.shiftedContentX > units.gu(2) &&
411+ spreadView.shiftedContentX < spreadView.positionMarker1 * spreadView.width) {
412+ // If it was a short one-way movement, do the Alt+Tab switch
413+ // no matter if we didn't cross positionMarker1 yet.
414+ spreadView.snapTo(1);
415+ } else if (!dragging && attachedToView) {
416+ // otherwise snap to the closest snap position we can find
417+ // (might be back to start, to app 1 or to spread)
418+ spreadView.snap();
511419 }
512420 }
513421 }
qml/Stages/Splash.qmlnew file+68
@@ -0,0 +1,68 @@
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+
17+import QtQuick 2.0
18+import Ubuntu.Components 0.1
19+import "../Components"
20+
21+Rectangle {
22+ id: root
23+ color: "black"
24+
25+ property string name: ""
26+ property url image: ""
27+
28+ UbuntuShape {
29+ id: iconShape
30+ anchors.horizontalCenter: parent.horizontalCenter
31+ anchors.verticalCenter: parent.verticalCenter
32+ anchors.verticalCenterOffset: -units.gu(4)
33+ width: units.gu(8)
34+ height: units.gu(7.5)
35+
36+ radius: "medium"
37+ borderSource: "none"
38+
39+ image: Image {
40+ id: iconImage
41+ sourceSize.width: iconShape.width
42+ sourceSize.height: iconShape.height
43+ source: root.image
44+ fillMode: Image.PreserveAspectCrop
45+ }
46+ }
47+
48+ Label {
49+ text: root.name
50+ anchors.horizontalCenter: parent.horizontalCenter
51+ anchors.top: iconShape.bottom
52+ anchors.topMargin: units.gu(2)
53+ fontSize: "large"
54+ }
55+
56+ WaitingDots {
57+ visible: parent.visible
58+ anchors.horizontalCenter: parent.horizontalCenter
59+ anchors.bottom: parent.bottom
60+ anchors.bottomMargin: units.gu(12)
61+ }
62+
63+ MouseArea {
64+ anchors.fill: parent
65+ enabled: parent.visible
66+ // absorb all mouse events
67+ }
68+}
qml/Stages/SpreadDelegate.qml+187−23
@@ -17,39 +17,203 @@
1717*/
1818
1919import QtQuick 2.0
20+import Unity.Application 0.1
21+import Ubuntu.Components 1.1
22+import "../Components"
2023
2124Item {
2225 id: root
2326
27+ // to be set from outside
28+ property bool interactive: true
29+ property bool dropShadow: true
30+ property real maximizedAppTopMargin
31+ property alias swipeToCloseEnabled: dragArea.enabled
32+
33+ readonly property bool isFullscreen: surface !== null && surfaceContainer.surface.anchors.topMargin == 0
34+
2435 signal clicked()
36+ signal closed()
37+
38+ SurfaceContainer {
39+ id: surfaceContainer
40+ anchors.fill: parent
41+ surface: model.surface
42+ property bool appHasCreatedASurface: false
43+
44+ onSurfaceChanged: {
45+ if (surface) {
46+ if (!appHasCreatedASurface) {
47+ surface.visible = false; // hide until splash screen removed
48+ appHasCreatedASurface = true;
49+ }
50+ }
51+ }
52+
53+ function revealSurface() {
54+ surface.visible = true;
55+ splashLoader.source = "";
56+ }
57+
58+ Binding {
59+ target: surfaceContainer.surface
60+ property: "anchors.topMargin"
61+ value: {
62+ return surfaceContainer.surface.state === MirSurfaceItem.Fullscreen ? 0 : maximizedAppTopMargin;
63+ }
64+ }
2565
26− property real topMarginProgress
66+ Binding {
67+ target: surface
68+ property: "enabled"
69+ value: root.interactive
70+ }
71+ Binding {
72+ target: surface
73+ property: "focus"
74+ value: root.interactive
75+ }
76+
77+ Timer { //FIXME - need to delay removing splash screen to allow surface resize to complete
78+ id: surfaceRevealDelay
79+ interval: 100
80+ onTriggered: surfaceContainer.revealSurface()
81+ }
82+
83+ Connections {
84+ target: surface
85+ // FIXME: I would rather not need to do this, but currently it doesn't get
86+ // active focus without it and I don't know why.
87+ onFocusChanged: forceSurfaceActiveFocusIfReady();
88+ onParentChanged: forceSurfaceActiveFocusIfReady();
89+ onEnabledChanged: forceSurfaceActiveFocusIfReady();
90+ function forceSurfaceActiveFocusIfReady() {
91+ if (surface.focus && surface.parent === surfaceContainer && surface.enabled) {
92+ surface.forceActiveFocus();
93+ }
94+ }
95+ }
96+
97+ BorderImage {
98+ id: dropShadowImage
99+ anchors {
100+ fill: parent
101+ leftMargin: -units.gu(2)
102+ rightMargin: -units.gu(2)
103+ bottomMargin: -units.gu(2)
104+ topMargin: -units.gu(2) + (root.isFullscreen ? 0 : maximizedAppTopMargin)
105+ }
106+ source: "graphics/dropshadow.png"
107+ border { left: 50; right: 50; top: 50; bottom: 50 }
108+ opacity: root.dropShadow ? .4 : 0
109+ Behavior on opacity { UbuntuNumberAnimation {} }
110+ }
27111
28− QtObject {
29− id: priv
30− property real heightDifference: root.height - appImage.implicitHeight
112+ transform: Translate {
113+ y: dragArea.distance
114+ }
31115 }
32116
33− Image {
34− id: dropShadow
35− anchors.fill: appImage
36− anchors.margins: -units.gu(2)
37− source: "graphics/dropshadow.png"
38− opacity: .4
117+
118+ StateGroup {
119+ id: appSurfaceState
120+ states: [
121+ State {
122+ name: "noSurfaceYet"
123+ when: !surfaceContainer.appHasCreatedASurface
124+ StateChangeScript {
125+ script: { splashLoader.setSource("Splash.qml", { "name": model.name, "image": model.icon }); }
126+ }
127+ },
128+ State {
129+ name: "hasSurface"
130+ when: surfaceContainer.appHasCreatedASurface && (surfaceContainer.surface !== null)
131+ StateChangeScript { script: { surfaceRevealDelay.start(); } }
132+ },
133+ State {
134+ name: "surfaceLostButAppStillAlive"
135+ when: surfaceContainer.appHasCreatedASurface && (surfaceContainer.surface === null)
136+ // TODO - use app snapshot
137+ }
138+ ]
139+ state: "noSurfaceYet"
39140 }
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
141+
142+ Loader {
143+ id: splashLoader
144+ anchors.fill: surfaceContainer
50145 }
51− MouseArea {
52− anchors.fill: appImage
53− onClicked: root.clicked()
146+
147+ DraggingArea {
148+ id: dragArea
149+ anchors.fill: parent
150+
151+ property bool moving: false
152+ property real distance: 0
153+
154+ onMovingChanged: {
155+ spreadView.draggedIndex = moving ? index : -1
156+ }
157+
158+ onDragValueChanged: {
159+ if (!dragging) {
160+ return;
161+ }
162+ moving = moving || Math.abs(dragValue) > units.gu(1)
163+ if (moving) {
164+ distance = dragValue;
165+ }
166+ }
167+
168+ onClicked: {
169+ if (!moving) {
170+ root.clicked();
171+ }
172+ }
173+
174+ onDragEnd: {
175+ // velocity and distance values specified by design prototype
176+ if ((dragVelocity < -units.gu(40) && distance < -units.gu(8)) || distance < -root.height / 2) {
177+ animation.animate("up")
178+ } else if ((dragVelocity > units.gu(40) && distance > units.gu(8)) || distance > root.height / 2) {
179+ animation.animate("down")
180+ } else {
181+ animation.animate("center")
182+ }
183+ }
184+
185+ UbuntuNumberAnimation {
186+ id: animation
187+ target: dragArea
188+ property: "distance"
189+ property bool requestClose: false
190+
191+ function animate(direction) {
192+ animation.from = dragArea.distance;
193+ switch (direction) {
194+ case "up":
195+ animation.to = -root.height * 1.5;
196+ requestClose = true;
197+ break;
198+ case "down":
199+ animation.to = root.height * 1.5;
200+ requestClose = true;
201+ break;
202+ default:
203+ animation.to = 0
204+ }
205+ animation.start();
206+ }
207+
208+ onRunningChanged: {
209+ if (!running) {
210+ dragArea.moving = false;
211+ dragArea.distance = 0;
212+ if (requestClose) {
213+ root.closed();
214+ }
215+ }
216+ }
217+ }
54218 }
55219}
qml/Stages/StageWithSideStage.qmldeleted−413
@@ -1,413 +0,0 @@
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: "black"
286−
287− WaitingDots {
288− }
289− }
290−
291− SidestageHandle {
292− id: sideStageHandle
293− anchors { top: parent.top; right: sideStageImage.left; bottom: parent.bottom }
294− width: root.dragAreaWidth
295− visible: root.shown && priv.sideStageAppId && sideStageImage.x < root.width
296−
297− }
298− MouseArea {
299− id: sideStageHandleMouseArea
300− anchors { top: parent.top; right: parent.right; bottom: parent.bottom; rightMargin: sideStageImage.shown ? sideStageImage.width : 0}
301− width: root.dragAreaWidth
302− visible: priv.sideStageAppId
303−
304− property var dragPoints: new Array()
305−
306− onPressed: {
307− priv.requestNewScreenshot(ApplicationInfoInterface.SideStage)
308− if (priv.mainStageAppId) {
309− priv.requestNewScreenshot(ApplicationInfoInterface.MainStage)
310− }
311− }
312−
313− onMouseXChanged: {
314− dragPoints.push(mouseX)
315−
316− var dragPoint = root.width + mouseX;
317− if (sideStageImage.shown) {
318− dragPoint -= sideStageImage.width
319− }
320− sideStageImage.x = Math.max(root.width - sideStageImage.width, dragPoint)
321− }
322−
323− onReleased: {
324− var distance = 0;
325− var lastX = dragPoints[0];
326− var oneWayFlick = true;
327− for (var i = 0; i < dragPoints.length; ++i) {
328− if (dragPoints[i] < lastX) {
329− oneWayFlick = false;
330− }
331− distance += dragPoints[i] - lastX;
332− lastX = dragPoints[i];
333− }
334− dragPoints = [];
335−
336− if (oneWayFlick || distance > sideStageImage.width / 2) {
337− sideStageImage.snapTo(root.width)
338− } else {
339− sideStageImage.snapToApp(priv.sideStageApp)
340− }
341− }
342− }
343−
344− SwitchingApplicationImage {
345− id: sideStageImage
346− width: priv.sideStageWidth
347− height: root.height
348− x: root.width
349− anchors.bottom: parent.bottom
350− visible: true
351− property bool shown: false
352−
353− onSwitched: {
354− mainStageImage.visible = false;
355− ApplicationManager.focusApplication(application.appId)
356− }
357−
358− function snapTo(targetX) {
359− sideStageSnapAnimation.targetX = targetX
360− sideStageImage.visible = true;
361− if (priv.mainStageAppId) {
362− mainStageImage.application = priv.mainStageApp
363− mainStageImage.visible = true;
364− }
365− sideStageSnapAnimation.start();
366− }
367−
368− function snapToApp(application) {
369− sideStageImage.application = application
370− sideStageSnapAnimation.snapToId = application.appId;
371− snapTo(root.width - sideStageImage.width);
372− }
373−
374− SequentialAnimation {
375− id: sideStageSnapAnimation
376− property int targetX: root.width
377− property string snapToId
378−
379− UbuntuNumberAnimation { target: sideStageImage; property: "x"; to: sideStageSnapAnimation.targetX; duration: UbuntuAnimation.SlowDuration }
380− ScriptAction {
381− script: {
382− if (sideStageSnapAnimation.targetX == root.width) {
383− if (priv.mainStageAppId) {
384− ApplicationManager.focusApplication(priv.mainStageAppId)
385− } else {
386− priv.overlayOverride = true;
387− ApplicationManager.unfocusCurrentApplication();
388− }
389− sideStageImage.shown = false;
390− }
391− if (sideStageSnapAnimation.snapToId) {
392− ApplicationManager.focusApplication(sideStageSnapAnimation.snapToId)
393− sideStageSnapAnimation.snapToId = "";
394− sideStageImage.shown = true;
395− priv.overlayOverride = false;
396− }
397− sideStageImage.visible = false;
398− mainStageImage.visible = false;
399− }
400− }
401− }
402− }
403−
404− Rectangle {
405− id: sideStageSplash
406− anchors.fill: parent
407− anchors.leftMargin: sideStageImage.x
408− color: "black"
409−
410− WaitingDots {
411− }
412− }
413−}
qml/Stages/SurfaceContainer.qmlnew file+123
@@ -0,0 +1,123 @@
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+
17+import QtQuick 2.0
18+import "Animations"
19+
20+Item {
21+ id: container
22+ property Item surface: null
23+ property bool removing: false
24+
25+ onSurfaceChanged: {
26+ if (surface) {
27+ surface.parent = container;
28+ surface.z = 1;
29+ state = "initial"
30+ }
31+ }
32+
33+ Connections {
34+ target: surface
35+ onRemoved: {
36+ container.removing = true;
37+
38+ var childSurfaces = surface.childSurfaces;
39+ for (var i=0; i<childSurfaces.length; i++) {
40+ childSurfaces[i].removed();
41+ }
42+
43+ //if we don't have children, nothing will tell us to animate out, so do it.
44+ if (childSurfaces.length === 0) {
45+ animateOut();
46+ }
47+ // tell our parent to animate out.
48+ if (surface.parentSurface) {
49+ surface.parentSurface.parent.animateOut();
50+ }
51+ }
52+ }
53+
54+ Repeater {
55+ model: container.surface ? container.surface.childSurfaces : 0
56+
57+ delegate: Loader {
58+ z: 2
59+ anchors {
60+ fill: container
61+ topMargin: container.surface.anchors.topMargin
62+ rightMargin: container.surface.anchors.rightMargin
63+ bottomMargin: container.surface.anchors.bottomMargin
64+ leftMargin: container.surface.anchors.leftMargin
65+ }
66+
67+ // Only way to do recursive qml items.
68+ source: Qt.resolvedUrl("SurfaceContainer.qml")
69+ onLoaded: {
70+ item.surface = modelData;
71+ item.animateIn(swipeFromBottom);
72+ container.animateIn(swipeUp);
73+ }
74+ }
75+ }
76+
77+ function animateIn(component) {
78+ var animation = component.createObject(container, { "surface": container.surface });
79+ animation.start();
80+
81+ var tmp = d.animations;
82+ tmp.push(animation);
83+ d.animations = tmp;
84+ }
85+
86+ function animateOut() {
87+ if (d.animations.length > 0) {
88+ var tmp = d.animations;
89+ var popped = tmp.pop();
90+ popped.end();
91+ d.animations = tmp;
92+ } else {
93+ container.state = "initial";
94+ }
95+ }
96+
97+ QtObject {
98+ id: d
99+ property var animations: []
100+ property var currentAnimation: animations.length > 0 ? animations[animations.length-1] : undefined
101+ }
102+
103+ Component {
104+ id: swipeFromBottom
105+ SwipeFromBottomAnimation {}
106+ }
107+ Component {
108+ id: swipeUp
109+ SwipeUpAnimation {}
110+ }
111+ Component {
112+ id: darkenFade
113+ DarkenAndFadeInAnimation {}
114+ }
115+
116+ states: [
117+ State {
118+ name: "initial"
119+ PropertyChanges { target: surface; anchors.fill: container }
120+ }
121+ // TODO: more animations!
122+ ]
123+}
qml/Stages/SwitchingApplicationImage.qmldeleted−81
@@ -1,81 +0,0 @@
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/TabletStage.qmlnew file+568
@@ -0,0 +1,568 @@
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+ objectName: "stages"
27+ anchors.fill: parent
28+
29+ // Controls to be set from outside
30+ property bool shown: false
31+ property bool moving: false
32+ property int dragAreaWidth
33+ property real maximizedAppTopMargin
34+ property bool interactive
35+
36+ // State information propagated to the outside
37+ readonly property bool locked: spreadView.phase == 2
38+
39+ QtObject {
40+ id: priv
41+
42+ property string focusedAppId: ApplicationManager.focusedApplicationId
43+ property string oldFocusedAppId: ""
44+
45+ property string mainStageAppId
46+ property string sideStageAppId
47+
48+ // For convenience, keep properties of the first two apps in the model
49+ property string appId0
50+ property string appId1
51+
52+ onFocusedAppIdChanged: {
53+ if (priv.focusedAppId.length > 0) {
54+ var focusedApp = ApplicationManager.findApplication(focusedAppId);
55+ if (focusedApp.stage == ApplicationInfoInterface.SideStage) {
56+ priv.sideStageAppId = focusedAppId;
57+ } else {
58+ priv.mainStageAppId = focusedAppId;
59+ }
60+ }
61+
62+ appId0 = ApplicationManager.count >= 1 ? ApplicationManager.get(0).appId : "";
63+ appId1 = ApplicationManager.count > 1 ? ApplicationManager.get(1).appId : "";
64+ }
65+
66+ function indexOf(appId) {
67+ for (var i = 0; i < ApplicationManager.count; i++) {
68+ if (ApplicationManager.get(i).appId == appId) {
69+ return i;
70+ }
71+ }
72+ return -1;
73+ }
74+
75+ function evaluateOneWayFlick(gesturePoints) {
76+ // Need to have at least 3 points to recognize it as a flick
77+ if (gesturePoints.length < 3) {
78+ return false;
79+ }
80+ // Need to have a movement of at least 2 grid units to recognize it as a flick
81+ if (Math.abs(gesturePoints[gesturePoints.length - 1] - gesturePoints[0]) < units.gu(2)) {
82+ return false;
83+ }
84+
85+ var oneWayFlick = true;
86+ var smallestX = gesturePoints[0];
87+ var leftWards = gesturePoints[1] < gesturePoints[0];
88+ for (var i = 1; i < gesturePoints.length; i++) {
89+ if ((leftWards && gesturePoints[i] >= smallestX)
90+ || (!leftWards && gesturePoints[i] <= smallestX)) {
91+ oneWayFlick = false;
92+ break;
93+ }
94+ smallestX = gesturePoints[i];
95+ }
96+ return oneWayFlick;
97+ }
98+ }
99+
100+ Connections {
101+ target: ApplicationManager
102+ onFocusRequested: {
103+ if (spreadView.interactive) {
104+ spreadView.snapTo(priv.indexOf(appId));
105+ } else {
106+ ApplicationManager.focusApplication(appId);
107+ }
108+ }
109+
110+ onApplicationRemoved: {
111+ if (priv.mainStageAppId == appId) {
112+ priv.mainStageAppId = "";
113+ }
114+ if (priv.sideStageAppId == appId) {
115+ priv.sideStageAppId = "";
116+ }
117+ if (ApplicationManager.count == 0) {
118+ spreadView.phase = 0;
119+ spreadView.contentX = 0;
120+ }
121+ }
122+ }
123+
124+ Flickable {
125+ id: spreadView
126+ anchors.fill: parent
127+ contentWidth: spreadRow.width
128+ interactive: (spreadDragArea.status == DirectionalDragArea.Recognized || phase > 1) && draggedIndex == -1
129+
130+ property int tileDistance: units.gu(20)
131+ property int sideStageWidth: units.gu(40)
132+ property bool sideStageVisible: priv.sideStageAppId
133+
134+ // Phase of the animation:
135+ // 0: Starting from right edge, a new app (index 1) comes in from the right
136+ // 1: The app has reached the first snap position.
137+ // 2: The list is dragged further and snaps into the spread view when entering phase 2
138+ property int phase
139+
140+ readonly property int phase0Width: sideStageWidth
141+ readonly property int phase1Width: sideStageWidth
142+
143+ // Those markers mark the various positions in the spread (ratio to screen width from right to left):
144+ // 0 - 1: following finger, snap back to the beginning on release
145+ readonly property real positionMarker1: 0.2
146+ // 1 - 2: curved snapping movement, snap to nextInStack on release
147+ readonly property real positionMarker2: sideStageWidth / spreadView.width
148+ // 2 - 3: movement follows finger, snaps to phase 2 (full spread) on release
149+ readonly property real positionMarker3: 0.6
150+ // passing 3, we detach movement from the finger and snap to phase 2 (full spread)
151+ readonly property real positionMarker4: 0.8
152+
153+ readonly property int startSnapPosition: phase0Width * 0.5
154+ readonly property int endSnapPosition: phase0Width * 0.75
155+ readonly property real snapPosition: 0.75
156+
157+ property int selectedIndex: -1
158+ property int draggedIndex: -1
159+ property int closingIndex: -1
160+
161+ property bool sideStageDragging: sideStageDragHandle.dragging
162+ property real sideStageDragProgress: sideStageDragHandle.progress
163+
164+ onSideStageDragProgressChanged: {
165+ if (sideStageDragProgress == 1) {
166+ ApplicationManager.focusApplication(priv.mainStageAppId);
167+ priv.sideStageAppId = "";
168+ }
169+ }
170+
171+ property int nextInStack: {
172+ switch (state) {
173+ case "main":
174+ if (ApplicationManager.count > 1) {
175+ return 1;
176+ }
177+ return -1;
178+ case "mainAndOverlay":
179+ if (ApplicationManager.count <= 2) {
180+ return -1;
181+ }
182+ if (priv.appId0 == priv.mainStageAppId || priv.appId0 == priv.sideStageAppId) {
183+ if (priv.appId1 == priv.mainStageAppId || priv.appId1 == priv.sideStageAppId) {
184+ return 2;
185+ }
186+ return 1;
187+ }
188+ return 0;
189+ case "overlay":
190+ return 1;
191+ }
192+ print("Unhandled nextInStack case! This shouldn't happen any more when the Dash is an app!");
193+ return -1;
194+ }
195+ property int nextZInStack: indexToZIndex(nextInStack)
196+
197+ states: [
198+ State {
199+ name: "empty"
200+ },
201+ State {
202+ name: "main"
203+ },
204+ State { // Side Stage only in overlay mode
205+ name: "overlay"
206+ },
207+ State { // Main Stage and Side Stage in overlay mode
208+ name: "mainAndOverlay"
209+ },
210+ State { // Main Stage and Side Stage in split mode
211+ name: "mainAndSplit"
212+ }
213+ ]
214+ state: {
215+ if (priv.mainStageAppId && !priv.sideStageAppId) {
216+ return "main";
217+ }
218+ if (!priv.mainStageAppId && priv.sideStageAppId) {
219+ return "overlay";
220+ }
221+ if (priv.mainStageAppId && priv.sideStageAppId) {
222+ return "mainAndOverlay";
223+ }
224+ return "empty";
225+ }
226+
227+ onContentXChanged: {
228+ if (spreadView.phase == 0 && spreadView.contentX > spreadView.width * spreadView.positionMarker2) {
229+ spreadView.phase = 1;
230+ } else if (spreadView.phase == 1 && spreadView.contentX > spreadView.width * spreadView.positionMarker4) {
231+ spreadView.phase = 2;
232+ } else if (spreadView.phase == 1 && spreadView.contentX < spreadView.width * spreadView.positionMarker2) {
233+ spreadView.phase = 0;
234+ }
235+ }
236+
237+ function snap() {
238+ if (contentX < phase0Width) {
239+ snapAnimation.targetContentX = 0;
240+ snapAnimation.start();
241+ } else if (contentX < phase1Width) {
242+ snapTo(1);
243+ } else {
244+ // Add 1 pixel to make sure we definitely hit positionMarker4 even with rounding errors of the animation.
245+ snapAnimation.targetContentX = spreadView.width * spreadView.positionMarker4 + 1;
246+ snapAnimation.start();
247+ }
248+ }
249+ function snapTo(index) {
250+ spreadView.selectedIndex = index;
251+ snapAnimation.targetContentX = 0;
252+ snapAnimation.start();
253+ }
254+
255+ // We need to shuffle z ordering a bit in order to keep side stage apps above main stage apps.
256+ // We don't want to really reorder them in the model because that allows us to keep track
257+ // of the last focused order.
258+ function indexToZIndex(index) {
259+ var app = ApplicationManager.get(index);
260+ if (!app) {
261+ return index;
262+ }
263+
264+ var isActive = app.appId == priv.mainStageAppId || app.appId == priv.sideStageAppId;
265+ if (isActive && app.stage == ApplicationInfoInterface.MainStage) {
266+ // if this app is active, and its the MainStage, always put it to index 0
267+ return 0;
268+ }
269+ if (isActive && app.stage == ApplicationInfoInterface.SideStage) {
270+ if (!priv.mainStageAppId) {
271+ // Only have SS apps running. Put the active one at 0
272+ return 0;
273+ }
274+
275+ // Precondition now: There's an active MS app and this is SS app:
276+ if (spreadView.nextInStack >= 0 && ApplicationManager.get(spreadView.nextInStack).stage == ApplicationInfoInterface.MainStage) {
277+ // If the next app coming from the right is a MS app, we need to elevate this SS ap above it.
278+ // Put it to at least level 2, or higher if there's more apps coming in before this one.
279+ return Math.max(index, 2);
280+ } else {
281+ // if this is no next app to come in from the right, place this one at index 1, just on top the active MS app.
282+ return 1;
283+ }
284+ }
285+ if (index <= 2 && app.stage == ApplicationInfoInterface.MainStage && priv.sideStageAppId) {
286+ // Ok, this is an inactive MS app. If there's an active SS app around, we need to place this one
287+ // in between the active MS app and the active SS app, so that it comes in from there when dragging from the right.
288+ // If there's now active SS app, just leave it where it is.
289+ return priv.indexOf(priv.sideStageAppId) < index ? index - 1 : index;
290+ }
291+ if (index == spreadView.nextInStack && app.stage == ApplicationInfoInterface.SideStage) {
292+ // This is a SS app and the next one to come in from the right:
293+ if (priv.sideStageAppId && priv.mainStageAppId) {
294+ // If there's both, an active MS and an active SS app, put this one right on top of that
295+ return 2;
296+ }
297+ // Or if there's only one other active app, put it on top of that.
298+ // The case that there isn't any other active app is already handled above.
299+ return 1;
300+ }
301+ if (index == 2 && spreadView.nextInStack == 1 && priv.sideStageAppId) {
302+ // If its index 2 but not the next one to come in, it means
303+ // we've pulled another one down to index 2. Move this one up to 2 instead.
304+ return 3;
305+ }
306+ // don't touch all others... (mostly index > 3 + simple cases where the above doesn't shuffle much)
307+ return index;
308+ }
309+
310+ SequentialAnimation {
311+ id: snapAnimation
312+ property int targetContentX: 0
313+
314+ UbuntuNumberAnimation {
315+ target: spreadView
316+ property: "contentX"
317+ to: snapAnimation.targetContentX
318+ duration: UbuntuAnimation.FastDuration
319+ }
320+
321+ ScriptAction {
322+ script: {
323+ if (spreadView.selectedIndex >= 0) {
324+ var newIndex = spreadView.selectedIndex;
325+ spreadView.selectedIndex = -1;
326+ ApplicationManager.focusApplication(ApplicationManager.get(newIndex).appId);
327+ spreadView.phase = 0;
328+ spreadView.contentX = 0;
329+ }
330+ }
331+ }
332+ }
333+
334+ Rectangle {
335+ id: spreadRow
336+ color: "black"
337+ x: spreadView.contentX
338+ height: root.height
339+ width: spreadView.width + Math.max(spreadView.width, ApplicationManager.count * spreadView.tileDistance)
340+
341+ Rectangle {
342+ id: sideStageBackground
343+ color: "black"
344+ anchors.fill: parent
345+ anchors.leftMargin: spreadView.width - (1 - sideStageDragHandle.progress) * spreadView.sideStageWidth
346+ z: spreadView.indexToZIndex(priv.indexOf(priv.sideStageAppId))
347+ opacity: spreadView.phase == 0 ? 1 : 0
348+ Behavior on opacity { UbuntuNumberAnimation {} }
349+ }
350+
351+ Item {
352+ id: sideStageDragHandle
353+ anchors { top: parent.top; bottom: parent.bottom; left: parent.left; leftMargin: spreadView.width - spreadView.sideStageWidth - width }
354+ width: units.gu(2)
355+ z: sideStageBackground.z
356+ opacity: spreadView.phase <= 0 && spreadView.sideStageVisible ? 1 : 0
357+ property real progress: 0
358+ property bool dragging: false
359+
360+ Behavior on opacity { UbuntuNumberAnimation {} }
361+
362+ Connections {
363+ target: spreadView
364+ onSideStageVisibleChanged: {
365+ if (spreadView.sideStageVisible) {
366+ sideStageDragHandle.progress = 0;
367+ }
368+ }
369+ }
370+
371+ Image {
372+ anchors.centerIn: parent
373+ anchors.horizontalCenterOffset: parent.progress * spreadView.sideStageWidth - (width - parent.width) / 2
374+ width: sideStageDragHandleMouseArea.pressed ? parent.width * 2 : parent.width
375+ height: parent.height
376+ source: "graphics/sidestage_handle@20.png"
377+ Behavior on width { UbuntuNumberAnimation {} }
378+ }
379+
380+ MouseArea {
381+ id: sideStageDragHandleMouseArea
382+ anchors.fill: parent
383+ enabled: spreadView.contentX == 0
384+ property int startX
385+ property var gesturePoints: new Array()
386+
387+ onPressed: {
388+ gesturePoints = [];
389+ startX = mouseX;
390+ sideStageDragHandle.progress = 0;
391+ sideStageDragHandle.dragging = true;
392+ }
393+ onMouseXChanged: {
394+ if (priv.mainStageAppId) {
395+ sideStageDragHandle.progress = Math.max(0, (-startX + mouseX) / spreadView.sideStageWidth);
396+ }
397+ gesturePoints.push(mouseX);
398+ }
399+ onReleased: {
400+ if (priv.mainStageAppId) {
401+ var oneWayFlick = priv.evaluateOneWayFlick(gesturePoints);
402+ sideStageDragSnapAnimation.to = sideStageDragHandle.progress > 0.5 || oneWayFlick ? 1 : 0;
403+ sideStageDragSnapAnimation.start();
404+ } else {
405+ sideStageDragHandle.dragging = false;
406+ }
407+ }
408+ }
409+ UbuntuNumberAnimation {
410+ id: sideStageDragSnapAnimation
411+ target: sideStageDragHandle
412+ property: "progress"
413+
414+ onRunningChanged: {
415+ if (!running) {
416+ sideStageDragHandle.dragging = false;;
417+ }
418+ }
419+ }
420+ }
421+
422+ Repeater {
423+ id: spreadRepeater
424+ model: ApplicationManager
425+
426+ delegate: TransformedTabletSpreadDelegate {
427+ id: spreadTile
428+ height: spreadView.height
429+ width: model.stage == ApplicationInfoInterface.MainStage ? spreadView.width : spreadView.sideStageWidth
430+ x: spreadView.width
431+ z: spreadView.indexToZIndex(index)
432+ active: model.appId == priv.mainStageAppId || model.appId == priv.sideStageAppId
433+ zIndex: z
434+ selected: spreadView.selectedIndex == index
435+ otherSelected: spreadView.selectedIndex >= 0 && !selected
436+ isInSideStage: priv.sideStageAppId == model.appId
437+ interactive: !spreadView.interactive && spreadView.phase === 0 && root.interactive
438+ swipeToCloseEnabled: spreadView.interactive
439+ maximizedAppTopMargin: root.maximizedAppTopMargin
440+ dropShadow: spreadView.contentX > 0 || spreadDragArea.status == DirectionalDragArea.Undecided
441+
442+ property real behavioredZIndex: zIndex
443+ Behavior on behavioredZIndex {
444+ enabled: spreadView.closingIndex >= 0
445+ UbuntuNumberAnimation {}
446+ }
447+
448+ // This is required because none of the bindings are triggered in some cases:
449+ // When an app is closed, it might happen that ApplicationManager.get(nextInStack)
450+ // returns a different app even though the nextInStackIndex and all the related
451+ // bindings (index, mainStageApp, sideStageApp, etc) don't change. Let's force a
452+ // binding update in that case.
453+ Connections {
454+ target: ApplicationManager
455+ onApplicationRemoved: spreadTile.z = Qt.binding(function() {
456+ return spreadView.indexToZIndex(index);
457+ })
458+ }
459+
460+ progress: {
461+ var tileProgress = (spreadView.contentX - behavioredZIndex * spreadView.tileDistance) / spreadView.width;
462+ // Some tiles (nextInStack, active) need to move directly from the beginning, normalize progress to immediately start at 0
463+ if ((index == spreadView.nextInStack && spreadView.phase < 2) || (active && spreadView.phase < 1)) {
464+ tileProgress += behavioredZIndex * spreadView.tileDistance / spreadView.width;
465+ }
466+ return tileProgress;
467+ }
468+
469+ animatedProgress: {
470+ if (spreadView.phase == 0 && (spreadTile.active || spreadView.nextInStack == index)) {
471+ if (progress < spreadView.positionMarker1) {
472+ return progress;
473+ } else if (progress < spreadView.positionMarker1 + snappingCurve.period) {
474+ return spreadView.positionMarker1 + snappingCurve.value * 3;
475+ } else {
476+ return spreadView.positionMarker2;
477+ }
478+ }
479+ return progress;
480+ }
481+
482+ onClicked: {
483+ if (spreadView.phase == 2) {
484+ if (ApplicationManager.focusedApplicationId == ApplicationManager.get(index).appId) {
485+ spreadView.snapTo(index);
486+ } else {
487+ ApplicationManager.requestFocusApplication(ApplicationManager.get(index).appId);
488+ }
489+ }
490+ }
491+
492+ onClosed: {
493+ spreadView.draggedIndex = -1;
494+ spreadView.closingIndex = index;
495+ ApplicationManager.stopApplication(ApplicationManager.get(index).appId);
496+ }
497+
498+ EasingCurve {
499+ id: snappingCurve
500+ type: EasingCurve.Linear
501+ period: (spreadView.positionMarker2 - spreadView.positionMarker1) / 3
502+ progress: spreadTile.progress - spreadView.positionMarker1
503+ }
504+ }
505+ }
506+ }
507+ }
508+
509+ EdgeDragArea {
510+ id: spreadDragArea
511+ anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
512+ width: root.dragAreaWidth
513+ direction: Direction.Leftwards
514+
515+ property bool attachedToView: false
516+ property var gesturePoints: new Array()
517+
518+ onTouchXChanged: {
519+ if (!dragging) {
520+ spreadView.phase = 0;
521+ spreadView.contentX = 0;
522+ }
523+
524+ if (attachedToView) {
525+ spreadView.contentX = -touchX + spreadDragArea.width;
526+ if (spreadView.contentX > spreadView.phase0Width + spreadView.phase1Width / 2) {
527+ attachedToView = false;
528+ spreadView.snap();
529+ }
530+ }
531+ gesturePoints.push(touchX);
532+ }
533+
534+ onStatusChanged: {
535+ if (status == DirectionalDragArea.Recognized) {
536+ attachedToView = true;
537+ }
538+ }
539+
540+ onDraggingChanged: {
541+ if (dragging) {
542+ // Gesture recognized. Start recording this gesture
543+ gesturePoints = [];
544+ return;
545+ }
546+
547+ // Ok. The user released. Find out if it was a one-way movement.
548+ var oneWayFlick = priv.evaluateOneWayFlick(gesturePoints);
549+ gesturePoints = [];
550+
551+ if (oneWayFlick && spreadView.contentX < spreadView.positionMarker1 * spreadView.width) {
552+ // If it was a short one-way movement, do the Alt+Tab switch
553+ // no matter if we didn't cross positionMarker1 yet.
554+ spreadView.snapTo(spreadView.nextInStack);
555+ } else if (!dragging && attachedToView) {
556+ if (spreadView.contentX < spreadView.width * spreadView.positionMarker1) {
557+ spreadView.snap();
558+ } else if (spreadView.contentX < spreadView.width * spreadView.positionMarker2) {
559+ spreadView.snapTo(spreadView.nextInStack);
560+ } else {
561+ // otherwise snap to the closest snap position we can find
562+ // (might be back to start, to app 1 or to spread)
563+ spreadView.snap();
564+ }
565+ }
566+ }
567+ }
568+}
qml/Stages/TransformedSpreadDelegate.qml+36−30
@@ -23,7 +23,9 @@ import Ubuntu.Components 0.1
2323SpreadDelegate {
2424 id: root
2525
26+ // Set this to true when this tile is selected in the spread. The animation will change to bring the tile to front.
2627 property bool selected: false
28+ // Set this to true when another tile in the spread is selected. The animation will change to fade this tile out.
2729 property bool otherSelected: false
2830
2931 // The progress animates the tiles. A value > 0 makes it appear from the right edge. At 1 it reaches the end position.
@@ -44,6 +46,9 @@ SpreadDelegate {
4446 property real startDistance: units.gu(5)
4547 property real endDistance: units.gu(.5)
4648
49+ // Hiding tiles when their progress is negative or reached the maximum
50+ visible: progress >= 0 && progress < 1.7
51+
4752 onSelectedChanged: {
4853 if (selected) {
4954 priv.snapshot();
@@ -106,16 +111,6 @@ SpreadDelegate {
106111 selectedTopMarginProgress = topMarginProgress;
107112 }
108113
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−
119114 function linearAnimation(startProgress, endProgress, startValue, endValue, progress) {
120115 // progress : progressDiff = value : valueDiff => value = progress * valueDiff / progressDiff
121116 return (progress - startProgress) * (endValue - startValue) / (endProgress - startProgress) + startValue;
@@ -127,22 +122,20 @@ SpreadDelegate {
127122 return helperEasingCurve.value * (endValue - startValue) + startValue;
128123 }
129124
130− property real animatedEndDistance: linearAnimation(0, 2, root.endDistance, 0, root.progress)
131−
132125 // The following blocks handle the animation of the tile in the spread.
133126 // At the beginning, each tile is attached at the right edge, outside the screen.
134127 // 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
128+ // The first phases are handled special for the first 2 tiles. as we do the alt-tab and snapping
129+ // in there. Once we reached phase 3, the animation is the same for all tiles.
130+ // When a tile is selected, the animation state is snapshotted, and the spreadView is unwound.
131+ // All tiles are kept in place and faded out to 0 opacity except
139132 // the selected tile, which is animated from the snapshotted position to be fullscreen.
140133
141− property real xTranslate: {
134+ readonly property real xTranslate: {
142135 if (otherSelected) {
143136 if (spreadView.phase < 2 && index == 0) {
144− return linearAnimation(selectedProgress, negativeProgress,
145− selectedXTranslate, selectedXTranslate - spreadView.tileDistance, root.progress);
137+ return linearAnimation(selectedProgress, 0, selectedXTranslate,
138+ selectedXTranslate - spreadView.tileDistance, root.progress);
146139 }
147140
148141 return selectedXTranslate;
@@ -160,7 +153,7 @@ SpreadDelegate {
160153 // Apply the same animation as with the rest but add spreadView.width to align it with the others.
161154 return -easingCurve.value * spreadView.width + spreadView.width;
162155 } else if (priv.isSelected) {
163− return linearAnimation(selectedProgress, negativeProgress, selectedXTranslate, 0, root.progress);
156+ return linearAnimation(selectedProgress, 0, selectedXTranslate, 0, root.progress);
164157 }
165158
166159 case 1:
@@ -177,23 +170,28 @@ SpreadDelegate {
177170 if (priv.isSelected) {
178171 // Distance to left edge
179172 var targetTranslate = -spreadView.width - ((index - 1) * root.startDistance);
180− return linearAnimation(selectedProgress, negativeProgress,
173+ return linearAnimation(selectedProgress, 0,
181174 selectedXTranslate, targetTranslate, root.progress);
182175 }
183176
184177 // Fix it at the right edge...
185178 var rightEdgeOffset = -((index - 1) * root.startDistance);
186179 // ...and use our easing to move them to the left. Stop a bit earlier for each tile
180+ var animatedEndDistance = linearAnimation(0, 2, root.endDistance, 0, root.progress);
187181 return -easingCurve.value * spreadView.width + (index * animatedEndDistance) + rightEdgeOffset;
188182
189183 }
190184
191− property real angle: {
185+ readonly property real angle: {
186+ if (spreadView.focusChanging) {
187+ return 0;
188+ }
189+
192190 if (priv.otherSelected) {
193191 return priv.selectedAngle;
194192 }
195193 if (priv.isSelected) {
196− return linearAnimation(selectedProgress, negativeProgress, selectedAngle, 0, root.progress);
194+ return linearAnimation(selectedProgress, 0, selectedAngle, 0, root.progress);
197195 }
198196 switch (index) {
199197 case 0:
@@ -217,12 +215,15 @@ SpreadDelegate {
217215 return root.startAngle - easingCurve.value * (root.startAngle - root.endAngle);
218216 }
219217
220− property real scale: {
218+ readonly property real scale: {
219+ if (spreadView.focusChanging) {
220+ return 1;
221+ }
221222 if (priv.otherSelected) {
222223 return priv.selectedScale;
223224 }
224225 if (priv.isSelected) {
225− return linearAnimation(selectedProgress, negativeProgress, selectedScale, 1, root.progress);
226+ return linearAnimation(selectedProgress, 0, selectedScale, 1, root.progress);
226227 }
227228
228229 switch (index) {
@@ -247,7 +248,7 @@ SpreadDelegate {
247248 return root.startScale - easingCurve.value * (root.startScale - root.endScale);
248249 }
249250
250− property real opacity: {
251+ readonly property real opacity: {
251252 if (priv.otherSelected) {
252253 return linearAnimation (selectedProgress, Math.max(0, selectedProgress - .5),
253254 selectedOpacity, 0, root.progress);
@@ -265,9 +266,9 @@ SpreadDelegate {
265266 return 1;
266267 }
267268
268− property real topMarginProgress: {
269+ readonly property real topMarginProgress: {
269270 if (priv.isSelected) {
270− return linearAnimation(selectedProgress, negativeProgress, selectedTopMarginProgress, 0, root.progress);
271+ return linearAnimation(selectedProgress, 0, selectedTopMarginProgress, 0, root.progress);
271272 }
272273
273274 switch (index) {
@@ -290,6 +291,7 @@ SpreadDelegate {
290291
291292 return easingCurve.value;
292293 }
294+
293295 }
294296
295297 transform: [
@@ -303,18 +305,22 @@ SpreadDelegate {
303305 xScale: priv.scale
304306 yScale: xScale
305307 },
308+ Scale {
309+ origin { x: 0; y: (spreadView.height * priv.scale) + maximizedAppTopMargin * 3 }
310+ xScale: 1
311+ yScale: isFullscreen ? 1 - priv.topMarginProgress * maximizedAppTopMargin / spreadView.height : 1
312+ },
306313 Translate {
307314 x: priv.xTranslate
308315 }
309316 ]
310317 opacity: priv.opacity
311− topMarginProgress: priv.topMarginProgress
312318
313319 EasingCurve {
314320 id: easingCurve
315321 type: EasingCurve.OutSine
316322 period: 1 - spreadView.positionMarker2
317− progress: root.animatedProgress
323+ progress: root.progress
318324 }
319325
320326 // This is used as a calculation helper to figure values for progress other than the current one
qml/Stages/TransformedTabletSpreadDelegate.qmlnew file+369
@@ -0,0 +1,369 @@
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+import Unity.Application 0.1
23+
24+SpreadDelegate {
25+ id: root
26+
27+ // Set this to true when this tile is selected in the spread. The animation will change to bring the tile to front.
28+ property bool selected: false
29+ // Set this to true when another tile in the spread is selected. The animation will change to fade this tile out.
30+ property bool otherSelected: false
31+ // Set this to true when this tile a currently active on either the MS or the SS.
32+ property bool active: false
33+
34+ property int zIndex
35+ property real progress: 0
36+ property real animatedProgress: 0
37+
38+ property real startDistance: units.gu(5)
39+ property real endDistance: units.gu(.5)
40+
41+ property real startScale: 1.1
42+ property real endScale: 0.7
43+ property real dragStartScale: startScale + .2
44+
45+ property real startAngle: 15
46+ property real endAngle: 5
47+
48+ property bool isInSideStage: false
49+
50+ onSelectedChanged: {
51+ if (selected) {
52+ priv.snapshot();
53+ }
54+ priv.isSelected = selected;
55+ }
56+
57+ onOtherSelectedChanged: {
58+ if (otherSelected) {
59+ priv.snapshot();
60+ }
61+ priv.otherSelected = otherSelected;
62+ }
63+
64+ Connections {
65+ target: spreadView
66+
67+ onPhaseChanged: {
68+ if (spreadView.phase == 1) {
69+ var phase2Progress = spreadView.positionMarker4 - (root.zIndex * spreadView.tileDistance / spreadView.width);
70+ priv.phase2StartTranslate = priv.easingAnimation(0, 1, 0, -spreadView.width + (root.zIndex * root.endDistance), phase2Progress);
71+ priv.phase2StartScale = priv.easingAnimation(0, 1, root.startScale, root.endScale, phase2Progress);
72+ priv.phase2StartAngle = priv.easingAnimation(0, 1, root.startAngle, root.endAngle, phase2Progress);
73+ priv.phase2StartTopMarginProgress = priv.easingAnimation(0, 1, 0, 1, phase2Progress);
74+ }
75+ }
76+ }
77+
78+ // Required to be an item because we're using states on it.
79+ Item {
80+ id: priv
81+
82+ // true if this is the next tile on the stack that comes in when dragging from the right
83+ property bool nextInStack: spreadView.nextZInStack == zIndex
84+ // true if the next tile in the stack is the nextInStack one. This one will be moved a bit to the left
85+ property bool movedActive: spreadView.nextZInStack == zIndex + 1
86+ property real animatedEndDistance: linearAnimation(0, 2, root.endDistance, 0, root.progress)
87+
88+ property real phase2StartTranslate
89+ property real phase2StartScale
90+ property real phase2StartAngle
91+ property real phase2StartTopMarginProgress
92+
93+ property bool isSelected: false
94+ property bool otherSelected: false
95+ property real selectedProgress
96+ property real selectedXTranslate
97+ property real selectedAngle
98+ property real selectedScale
99+ property real selectedOpacity
100+ property real selectedTopMarginProgress
101+
102+ function snapshot() {
103+ selectedProgress = root.progress;
104+ selectedXTranslate = xTranslate;
105+ selectedAngle = angle;
106+ selectedScale = priv.scale;
107+ selectedOpacity = priv.opacityTransform;
108+ selectedTopMarginProgress = topMarginProgress;
109+ }
110+
111+ // This calculates how much negative progress there can be if unwinding the spread completely
112+ // the progress for each tile starts at 0 when it crosses the right edge, so the later a tile comes in,
113+ // the bigger its negativeProgress can be.
114+ property real negativeProgress: {
115+ if (nextInStack && spreadView.phase < 2) {
116+ return 0;
117+ }
118+ return -root.zIndex * spreadView.tileDistance / spreadView.width;
119+ }
120+
121+ function linearAnimation(startProgress, endProgress, startValue, endValue, progress) {
122+ // progress : progressDiff = value : valueDiff => value = progress * valueDiff / progressDiff
123+ return (progress - startProgress) * (endValue - startValue) / (endProgress - startProgress) + startValue;
124+ }
125+
126+ function easingAnimation(startProgress, endProgress, startValue, endValue, progress) {
127+ helperEasingCurve.progress = progress - startProgress;
128+ helperEasingCurve.period = endProgress - startProgress;
129+ return helperEasingCurve.value * (endValue - startValue) + startValue;
130+ }
131+
132+ property real xTranslate: {
133+ var newTranslate = 0;
134+
135+ if (otherSelected) {
136+ return priv.selectedXTranslate;
137+ }
138+
139+ if (isSelected) {
140+ if (model.stage == ApplicationInfoInterface.MainStage) {
141+ return linearAnimation(selectedProgress, negativeProgress, selectedXTranslate, -spreadView.width, root.progress);
142+ } else {
143+ return linearAnimation(selectedProgress, negativeProgress, selectedXTranslate, -spreadView.sideStageWidth, root.progress);
144+ }
145+ }
146+
147+ // The tile should move a bit to the left if a new one comes on top of it, but not for the Side Stage and not
148+ // when we're only dragging the side stage in on top of a main stage app
149+ var shouldMoveAway = spreadView.nextInStack >= 0 && priv.movedActive && model.stage === ApplicationInfoInterface.MainStage &&
150+ ApplicationManager.get(spreadView.nextInStack).stage === ApplicationInfoInterface.MainStage;
151+
152+ if (active) {
153+ newTranslate -= root.width
154+ // Only do the hide animation for active apps in the mainstage, and not if we only drag the ss in
155+ if (spreadView.phase == 0 && shouldMoveAway) {
156+ newTranslate += linearAnimation(0, spreadView.positionMarker2, 0, -units.gu(4), root.animatedProgress);
157+ }
158+ }
159+ if (nextInStack && spreadView.phase == 0) {
160+ if (model.stage == ApplicationInfoInterface.MainStage) {
161+ if (spreadView.sideStageVisible && root.progress > 0) {
162+ // Move it so it appears from behind the side stage immediately
163+ newTranslate += -spreadView.sideStageWidth;
164+ }
165+ }
166+
167+ if (model.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
168+ // This is when we only drag the side stage in, without rotation or snapping
169+ newTranslate = linearAnimation(0, spreadView.positionMarker2, 0, -spreadView.sideStageWidth, root.progress);
170+ } else {
171+ newTranslate += linearAnimation(0, spreadView.positionMarker2, 0, -spreadView.sideStageWidth * spreadView.snapPosition, root.animatedProgress);
172+ }
173+ }
174+
175+ if (spreadView.phase == 1) {
176+ if (nextInStack) {
177+ if (model.stage == ApplicationInfoInterface.MainStage) {
178+ var startValue = -spreadView.sideStageWidth * spreadView.snapPosition + (spreadView.sideStageVisible ? -spreadView.sideStageWidth : 0);
179+ newTranslate += linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, startValue, priv.phase2StartTranslate, root.animatedProgress);
180+ } else {
181+ var endValue = -spreadView.width + spreadView.width * root.zIndex / 6;
182+ if (!spreadView.sideStageVisible) {
183+ newTranslate += linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, -spreadView.sideStageWidth, priv.phase2StartTranslate, root.progress);
184+ } else {
185+ newTranslate += linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, -spreadView.sideStageWidth * spreadView.snapPosition, priv.phase2StartTranslate, root.animatedProgress);
186+ }
187+ }
188+ } else if (root.active) {
189+ var startProgress = spreadView.positionMarker2 - (zIndex * spreadView.positionMarker2 / 2);
190+ var endProgress = spreadView.positionMarker4 - (zIndex * spreadView.tileDistance / spreadView.width);
191+ var startTranslate = -root.width + (shouldMoveAway ? -units.gu(4) : 0);
192+ newTranslate = linearAnimation(startProgress, endProgress, startTranslate, priv.phase2StartTranslate, root.progress);
193+ } else {
194+ var startProgress = spreadView.positionMarker2 - (zIndex * spreadView.positionMarker2 / 2);
195+ var endProgress = spreadView.positionMarker4 - (zIndex * spreadView.tileDistance / spreadView.width);
196+ newTranslate = linearAnimation(startProgress, endProgress, 0, priv.phase2StartTranslate, root.progress);
197+ }
198+ }
199+
200+ if (spreadView.phase == 2) {
201+ newTranslate = -easingCurve.value * (spreadView.width - root.zIndex * animatedEndDistance);
202+ }
203+
204+ return newTranslate;
205+ }
206+
207+ property real scale: {
208+ if (otherSelected) {
209+ return selectedScale;
210+ }
211+
212+ if (isSelected) {
213+ return linearAnimation(selectedProgress, negativeProgress, selectedScale, 1, root.progress);
214+ }
215+
216+ if (spreadView.phase == 0) {
217+ if (nextInStack) {
218+ if (model.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
219+ return 1;
220+ } else {
221+ var targetScale = root.dragStartScale - ((root.dragStartScale - 1) * spreadView.snapPosition);
222+ return linearAnimation(0, spreadView.positionMarker2, root.dragStartScale, targetScale, root.animatedProgress);
223+ }
224+ } else if (active) {
225+ return 1;
226+ } else {
227+ return linearAnimation(0, spreadView.positionMarker2, root.startScale, root.endScale, root.progress);
228+ }
229+ }
230+
231+ if (spreadView.phase == 1) {
232+ if (nextInStack) {
233+ var startScale = 1;
234+ if (model.stage !== ApplicationInfoInterface.SideStage || spreadView.sideStageVisible) {
235+ startScale = root.dragStartScale - ((root.dragStartScale - 1) * spreadView.snapPosition);
236+ }
237+ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, startScale, priv.phase2StartScale, root.animatedProgress);
238+ }
239+ var startProgress = spreadView.positionMarker2 - (zIndex * spreadView.positionMarker2 / 2);
240+ var endProgress = spreadView.positionMarker4 - (zIndex * spreadView.tileDistance / spreadView.width);
241+ return linearAnimation(startProgress, endProgress, 1, priv.phase2StartScale, root.animatedProgress);
242+ }
243+
244+ if (spreadView.phase == 2) {
245+ return root.startScale - easingCurve.value * (root.startScale - root.endScale);
246+ }
247+
248+ return 1;
249+ }
250+
251+ property real angle: {
252+ if (otherSelected) {
253+ return selectedAngle;
254+ }
255+ if (isSelected) {
256+ return linearAnimation(selectedProgress, negativeProgress, selectedAngle, 0, root.progress);
257+ }
258+
259+ // The tile should rotate a bit when another one comes on top, but not when only dragging the side stage in
260+ var shouldMoveAway = spreadView.nextInStack >= 0 && movedActive &&
261+ (ApplicationManager.get(spreadView.nextInStack).stage === ApplicationInfoInterface.MainStage ||
262+ model.stage == ApplicationInfoInterface.SideStage);
263+
264+ if (spreadView.phase == 0) {
265+ if (nextInStack) {
266+ if (model.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
267+ return 0;
268+ } else {
269+ return linearAnimation(0, spreadView.positionMarker2, root.startAngle, root.startAngle * (1-spreadView.snapPosition), root.animatedProgress);
270+ }
271+ }
272+ if (shouldMoveAway) {
273+ return linearAnimation(0, spreadView.positionMarker2, 0, root.startAngle * (1-spreadView.snapPosition), root.animatedProgress);
274+ }
275+ }
276+ if (spreadView.phase == 1) {
277+ if (nextInStack) {
278+ if (model.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
279+ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, 0, priv.phase2StartAngle, root.animatedProgress);
280+ } else {
281+ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, root.startAngle * (1-spreadView.snapPosition), priv.phase2StartAngle, root.animatedProgress);
282+ }
283+ }
284+ var startProgress = spreadView.positionMarker2 - (zIndex * spreadView.positionMarker2 / 2);
285+ var endProgress = spreadView.positionMarker4 - (zIndex * spreadView.tileDistance / spreadView.width);
286+ var startAngle = shouldMoveAway ? root.startAngle * (1-spreadView.snapPosition) : 0;
287+ return linearAnimation(startProgress, endProgress, startAngle, priv.phase2StartAngle, root.progress);
288+ }
289+ if (spreadView.phase == 2) {
290+ return root.startAngle - easingCurve.value * (root.startAngle - root.endAngle);
291+ }
292+
293+ return 0;
294+ }
295+
296+ property real opacityTransform: {
297+ if (otherSelected && spreadView.phase == 2) {
298+ return linearAnimation(selectedProgress, negativeProgress, selectedOpacity, 0, root.progress);
299+ }
300+
301+ return 1;
302+ }
303+
304+ property real topMarginProgress: {
305+ if (priv.isSelected) {
306+ return linearAnimation(selectedProgress, negativeProgress, selectedTopMarginProgress, 0, root.progress);
307+ }
308+ switch (spreadView.phase) {
309+ case 0:
310+ return 0;
311+ case 1:
312+ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
313+ 0, priv.phase2StartTopMarginProgress, root.progress);
314+ }
315+ return 1;
316+ }
317+
318+ states: [
319+ State {
320+ name: "sideStageDragging"; when: spreadView.sideStageDragging && root.isInSideStage
321+ PropertyChanges { target: priv; xTranslate: -spreadView.sideStageWidth + spreadView.sideStageWidth * spreadView.sideStageDragProgress }
322+ }
323+ ]
324+ }
325+
326+ transform: [
327+ Rotation {
328+ origin { x: 0; y: spreadView.height / 2 }
329+ axis { x: 0; y: 1; z: 0 }
330+ angle: priv.angle
331+ },
332+ Scale {
333+ origin { x: 0; y: spreadView.height / 2 }
334+ xScale: priv.scale
335+ yScale: xScale
336+ },
337+ Scale {
338+ origin { x: 0; y: (spreadView.height * priv.scale) + maximizedAppTopMargin * 3 }
339+ xScale: 1
340+ yScale: isFullscreen ? 1 - priv.topMarginProgress * maximizedAppTopMargin / spreadView.height : 1
341+ },
342+ Translate {
343+ x: priv.xTranslate
344+ }
345+ ]
346+ opacity: priv.opacityTransform
347+
348+ UbuntuNumberAnimation {
349+ id: fadeBackInAnimation
350+ target: root
351+ property: "opacity"
352+ duration: UbuntuAnimation.SlowDuration
353+ from: 0
354+ to: 1
355+ }
356+
357+ EasingCurve {
358+ id: easingCurve
359+ type: EasingCurve.OutSine
360+ period: 1
361+ progress: root.progress
362+ }
363+
364+ EasingCurve {
365+ id: helperEasingCurve
366+ type: easingCurve.type
367+ period: easingCurve.period
368+ }
369+}
run.sh+15−6
@@ -8,6 +8,7 @@ GDB=false
88FAKE=false
99PINLOCK=false
1010KEYLOCK=false
11+USE_MOCKS=false
1112MOUSE_TOUCH=true
1213
1314usage() {
@@ -31,9 +32,9 @@ eval set -- "$ARGS"
3132while [ $# -gt 0 ]
3233do
3334 case "$1" in
34− -f|--fake) FAKE=true;;
35− -p|--pinlock) PINLOCK=true;;
36− -k|--keylock) KEYLOCK=true;;
35+ -f|--fake) FAKE=true; USE_MOCKS=true;;
36+ -p|--pinlock) PINLOCK=true; USE_MOCKS=true;;
37+ -k|--keylock) KEYLOCK=true; USE_MOCKS=true;;
3738 -g|--gdb) GDB=true;;
3839 -h|--help) usage;;
3940 -m|--nomousetouch) MOUSE_TOUCH=false;;
@@ -43,20 +44,28 @@ do
4344done
4445
4546if $FAKE; then
46− export QML2_IMPORT_PATH=$QML2_IMPORT_PATH:$PWD/builddir/tests/mocks:$PWD/builddir/plugins:$PWD/builddir/modules
4747 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/builddir/tests/mocks/libusermetrics:$PWD/builddir/tests/mocks/LightDM/single
4848fi
4949
5050if $PINLOCK; then
51− export QML2_IMPORT_PATH=$QML2_IMPORT_PATH:$PWD/builddir/tests/mocks:$PWD/builddir/plugins:$PWD/builddir/modules
5251 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/builddir/tests/mocks/libusermetrics:$PWD/builddir/tests/mocks/LightDM/single-pin
5352fi
5453
5554if $KEYLOCK; then
56− export QML2_IMPORT_PATH=$QML2_IMPORT_PATH:$PWD/builddir/tests/mocks:$PWD/builddir/plugins:$PWD/builddir/modules
5755 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/builddir/tests/mocks/libusermetrics:$PWD/builddir/tests/mocks/LightDM/single-passphrase
5856fi
5957
58+if $USE_MOCKS; then
59+ rm -f $PWD/builddir/nonmirplugins/LightDM # undo symlink (from below) for cleanliness
60+ export QML2_IMPORT_PATH=$QML2_IMPORT_PATH:$PWD/builddir/tests/mocks:$PWD/builddir/plugins:$PWD/builddir/modules
61+else
62+ # Still fake no-login user for convenience (it's annoying to be prompted for your password when testing)
63+ # And in particular, just link our LightDM mock into the nonmirplugins folder. We don't want the rest of
64+ # our plugins to be used.
65+ ln -s $PWD/builddir/tests/mocks/LightDM $PWD/builddir/nonmirplugins/
66+ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/builddir/tests/mocks/LightDM/single
67+fi
68+
6069QML_PHONE_SHELL_ARGS=""
6170if $MOUSE_TOUCH; then
6271 QML_PHONE_SHELL_ARGS="$QML_PHONE_SHELL_ARGS -mousetouch"
src/CMakeLists.txt−3
@@ -1,8 +1,5 @@
1−pkg_check_modules(UNITY-MIR REQUIRED unity-mir)
2−
31include_directories(
42 ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
5− ${UNITY-MIR_INCLUDE_DIRS}
63)
74
85file(GLOB_RECURSE QML_FILES
src/main.cpp+21−89
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2012 Canonical, Ltd.
2+ * Copyright (C) 2012-2014 Canonical, Ltd.
33 *
44 * Authors:
55 * Gerry Boland <gerry.boland@canonical.com>
@@ -23,12 +23,10 @@
2323#include <QtGui/QGuiApplication>
2424#include <QtQml/QQmlEngine>
2525#include <QtQml/QQmlContext>
26−#include <qpa/qplatformnativeinterface.h>
2726#include <QLibrary>
2827#include <QDebug>
29−#include <libintl.h>
30−#include <dlfcn.h>
3128#include <csignal>
29+#include <libintl.h>
3230
3331// local
3432#include <paths.h>
@@ -36,11 +34,13 @@
3634#include "ApplicationArguments.h"
3735#include "CachingNetworkManagerFactory.h"
3836
39−#include <unity-mir/qmirserver.h>
40−
41−int startShell(int argc, const char** argv, void* server)
37+int main(int argc, const char *argv[])
4238{
43− const bool isUbuntuMirServer = qgetenv("QT_QPA_PLATFORM") == "ubuntumirserver";
39+ bool isMirServer = false;
40+ if (qgetenv("QT_QPA_PLATFORM") == "ubuntumirclient") {
41+ setenv("QT_QPA_PLATFORM", "mirserver", 1 /* overwrite */);
42+ isMirServer = true;
43+ }
4444
4545 QGuiApplication::setApplicationName("unity8");
4646 QGuiApplication *application;
@@ -70,21 +70,7 @@ int startShell(int argc, const char** argv, void* server)
7070Load the testability driver");
7171 parser.addOption(testabilityOption);
7272
73− if (isUbuntuMirServer) {
74− QLibrary unityMir("unity-mir", 1);
75− unityMir.load();
76−
77− typedef QGuiApplication* (*createServerApplication_t)(int&, const char **, void*);
78− createServerApplication_t createQMirServerApplication = (createServerApplication_t) unityMir.resolve("createQMirServerApplication");
79− if (!createQMirServerApplication) {
80− qDebug() << "unable to resolve symbol: createQMirServerApplication";
81− return 4;
82− }
83−
84− application = createQMirServerApplication(argc, argv, server);
85− } else {
86− application = new QGuiApplication(argc, (char**)argv);
87− }
73+ application = new QGuiApplication(argc, (char**)argv);
8874
8975 // Treat args with single dashes the same as arguments with two dashes
9076 // Ex: -fullscreen == --fullscreen
@@ -100,8 +86,8 @@ Load the testability driver");
10086 if (parser.isSet(windowGeometryOption) &&
10187 parser.value(windowGeometryOption).split('x').size() == 2)
10288 {
103− QStringList geom = parser.value(windowGeometryOption).split('x');
104− qmlArgs.setSize(geom.at(0).toInt(), geom.at(1).toInt());
89+ QStringList geom = parser.value(windowGeometryOption).split('x');
90+ qmlArgs.setSize(geom.at(0).toInt(), geom.at(1).toInt());
10591 }
10692
10793 // The testability driver is only loaded by QApplication but not by QGuiApplication.
@@ -125,7 +111,8 @@ Load the testability driver");
125111
126112 QQuickView* view = new QQuickView();
127113 view->setResizeMode(QQuickView::SizeRootObjectToView);
128− view->setTitle("Qml Phone Shell");
114+ view->setColor("black");
115+ view->setTitle("Unity8 Shell");
129116 view->engine()->setBaseUrl(QUrl::fromLocalFile(::qmlDirectory()));
130117 view->rootContext()->setContextProperty("applicationArguments", &qmlArgs);
131118 view->rootContext()->setContextProperty("indicatorProfile", indicatorProfile);
@@ -141,18 +128,9 @@ Load the testability driver");
141128 application->installNativeEventFilter(mouseTouchAdaptor);
142129 }
143130
144− QPlatformNativeInterface* nativeInterface = QGuiApplication::platformNativeInterface();
145− /* Shell is declared as a system session so that it always receives all
146− input events.
147− FIXME: use the enum value corresponding to SYSTEM_SESSION_TYPE (= 1)
148− when it becomes available.
149− */
150− nativeInterface->setProperty("ubuntuSessionType", 1);
151− view->setProperty("role", 2); // INDICATOR_ACTOR_ROLE
152−
153131 QUrl source(::qmlDirectory()+"Shell.qml");
154132 prependImportPaths(view->engine(), ::overrideImportPaths());
155− if (!isUbuntuMirServer) {
133+ if (!isMirServer) {
156134 prependImportPaths(view->engine(), ::nonMirImportPaths());
157135 }
158136 appendImportPaths(view->engine(), ::fallbackImportPaths());
@@ -161,10 +139,15 @@ Load the testability driver");
161139 view->engine()->setNetworkAccessManagerFactory(managerFactory);
162140
163141 view->setSource(source);
164− view->setColor("transparent");
165142 QObject::connect(view->engine(), SIGNAL(quit()), application, SLOT(quit()));
166143
167− if (qgetenv("QT_QPA_PLATFORM") == "ubuntu" || isUbuntuMirServer || parser.isSet(fullscreenOption)) {
144+ if (!isMirServer && qEnvironmentVariableIsSet("UNITY_MIR_EMITS_SIGSTOP")) {
145+ // Emit SIGSTOP as expected by upstart, under Mir it's qtmir that will raise it.
146+ // see http://upstart.ubuntu.com/cookbook/#expect-stop
147+ raise(SIGSTOP);
148+ }
149+
150+ if (isMirServer || parser.isSet(fullscreenOption)) {
168151 view->showFullScreen();
169152 } else {
170153 view->show();
@@ -178,54 +161,3 @@ Load the testability driver");
178161
179162 return result;
180163}
181−
182−int main(int argc, const char *argv[])
183−{
184− /* Workaround Qt platform integration plugin not advertising itself
185− as having the following capabilities:
186− - QPlatformIntegration::ThreadedOpenGL
187− - QPlatformIntegration::BufferQueueingOpenGL
188− */
189− setenv("QML_FORCE_THREADED_RENDERER", "1", 1);
190− setenv("QML_FIXED_ANIMATION_STEP", "1", 1);
191−
192− // For ubuntumirserver/ubuntumirclient
193− if (qgetenv("QT_QPA_PLATFORM").startsWith("ubuntumir")) {
194− setenv("QT_QPA_PLATFORM", "ubuntumirserver", 1);
195−
196− // If we use unity-mir directly, we automatically link to the Mir-server
197− // platform-api bindings, which result in unexpected behaviour when
198− // running the non-Mir scenario.
199− QLibrary unityMir("unity-mir", 1);
200− unityMir.load();
201− if (!unityMir.isLoaded()) {
202− qDebug() << "Library unity-mir not found/loaded";
203− return 1;
204− }
205−
206− typedef QMirServer* (*createServer_t)(int, const char **);
207− createServer_t createQMirServer = (createServer_t) unityMir.resolve("createQMirServer");
208− if (!createQMirServer) {
209− qDebug() << "unable to resolve symbol: createQMirServer";
210− return 2;
211− }
212−
213− QMirServer* mirServer = createQMirServer(argc, argv);
214−
215− typedef int (*runWithClient_t)(QMirServer*, std::function<int(int, const char**, void*)>);
216− runWithClient_t runWithClient = (runWithClient_t) unityMir.resolve("runQMirServerWithClient");
217− if (!runWithClient) {
218− qDebug() << "unable to resolve symbol: runWithClient";
219− return 3;
220− }
221−
222− return runWithClient(mirServer, startShell);
223− } else {
224− if (!qgetenv("UNITY_MIR_EMITS_SIGSTOP").isEmpty()) {
225− // Emit SIGSTOP as expected by upstart, under Mir it's unity-mir that will raise it.
226− // see http://upstart.ubuntu.com/cookbook/#expect-stop
227− raise(SIGSTOP);
228− }
229− return startShell(argc, argv, nullptr);
230− }
231−}
tests/autopilot/unity8/shell/emulators/dash.py−4
@@ -188,10 +188,6 @@ class GenericScopeView(emulators.UnityEmulatorBase):
188188 raise emulators.UnityEmulatorException(
189189 'No category found with name {}'.format(category))
190190
191−
192−class DashApps(GenericScopeView):
193− """Autopilot emulator for the applications scope."""
194−
195191 def get_applications(self, category):
196192 """Return the list of applications on a category.
197193
tests/autopilot/unity8/shell/tests/test_emulators.py+1−1
@@ -142,7 +142,7 @@ class DashEmulatorTestCase(DashBaseTestCase):
142142 scope_id = 'clickscope'
143143 scope = self.dash.open_scope(scope_id)
144144 self._assert_scope_is_opened(scope, scope_id)
145− self.assertIsInstance(scope, dash_emulators.DashApps)
145+ self.assertIsInstance(scope, dash_emulators.GenericScopeView)
146146
147147
148148class GenericScopeViewEmulatorTestCase(DashBaseTestCase):
tests/autopilot/unity8/shell/tests/test_upstart.py+18−3
@@ -25,7 +25,8 @@ import signal
2525import subprocess
2626import time
2727
28−from testtools.matchers._basic import Equals
28+import fixtures
29+from testtools.matchers import Equals, MismatchError
2930from autopilot.matchers import Eventually
3031from autopilot.introspection import get_proxy_object_for_existing_process
3132
@@ -100,8 +101,21 @@ class UpstartIntegrationTests(UnityTestCase):
100101 emulator_base=UnityEmulatorBase,))
101102
102103 def test_no_sigstop(self):
103− self.patch_environment("UNITY_MIR_EMITS_SIGSTOP", "")
104+ self.useFixture(
105+ fixtures.EnvironmentVariable(
106+ 'UNITY_MIR_EMITS_SIGSTOP', newvalue=None))
104107 self._launch_unity()
108+
109+ try:
110+ self.assertThat(
111+ lambda: os.WIFSTOPPED(self._get_status()),
112+ Eventually(Equals(True)))
113+ except MismatchError:
114+ pass
115+ else:
116+ self.process.send_signal(signal.SIGCONT)
117+ self.fail('Unity8 raised SIGSTOP')
118+
105119 self._set_proxy()
106120
107121 logger.debug("Unity started, waiting for it to be ready.")
@@ -109,7 +123,8 @@ class UpstartIntegrationTests(UnityTestCase):
109123 logger.debug("Unity loaded and ready.")
110124
111125 def test_expect_sigstop(self):
112− self.patch_environment("UNITY_MIR_EMITS_SIGSTOP", "1")
126+ self.useFixture(
127+ fixtures.EnvironmentVariable('UNITY_MIR_EMITS_SIGSTOP', '1'))
113128 self._launch_unity()
114129 self.assertThat(
115130 lambda: os.WIFSTOPPED(self._get_status()),
tests/mocks/AccountsService/AccountsService.cpp+12−2
@@ -29,12 +29,14 @@ AccountsService::AccountsService(QObject* parent)
2929
3030QString AccountsService::user() const
3131{
32− return "testuser";
32+ return m_user;
3333}
3434
3535void AccountsService::setUser(const QString &user)
3636{
37− Q_UNUSED(user)
37+ m_user = user;
38+ Q_EMIT userChanged();
39+ Q_EMIT passwordDisplayHintChanged();
3840}
3941
4042bool AccountsService::demoEdges() const
@@ -68,3 +70,11 @@ void AccountsService::setStatsWelcomeScreen(bool statsWelcomeScreen)
6870 m_statsWelcomeScreen = statsWelcomeScreen;
6971 statsWelcomeScreenChanged();
7072}
73+
74+AccountsService::PasswordDisplayHint AccountsService::passwordDisplayHint() const
75+{
76+ if (m_user == "has-pin")
77+ return PasswordDisplayHint::Numeric;
78+ else
79+ return PasswordDisplayHint::Keyboard;
80+}
tests/mocks/AccountsService/AccountsService.h+12
@@ -27,6 +27,7 @@
2727class AccountsService: public QObject
2828{
2929 Q_OBJECT
30+ Q_ENUMS(PasswordDisplayHint)
3031 Q_PROPERTY (QString user
3132 READ user
3233 WRITE setUser
@@ -43,8 +44,16 @@ class AccountsService: public QObject
4344 READ statsWelcomeScreen
4445 WRITE setStatsWelcomeScreen // only available in mock
4546 NOTIFY statsWelcomeScreenChanged)
47+ Q_PROPERTY (PasswordDisplayHint passwordDisplayHint
48+ READ passwordDisplayHint
49+ NOTIFY passwordDisplayHintChanged)
4650
4751public:
52+ enum PasswordDisplayHint {
53+ Keyboard,
54+ Numeric,
55+ };
56+
4857 explicit AccountsService(QObject *parent = 0);
4958
5059 QString user() const;
@@ -55,15 +64,18 @@ public:
5564 void setBackgroundFile(const QString &backgroundFile);
5665 bool statsWelcomeScreen() const;
5766 void setStatsWelcomeScreen(bool statsWelcomeScreen);
67+ PasswordDisplayHint passwordDisplayHint() const;
5868
5969Q_SIGNALS:
6070 void userChanged();
6171 void demoEdgesChanged();
6272 void backgroundFileChanged();
6373 void statsWelcomeScreenChanged();
74+ void passwordDisplayHintChanged();
6475
6576private:
6677 QString m_backgroundFile;
78+ QString m_user;
6779 bool m_statsWelcomeScreen;
6880};
6981
tests/mocks/AccountsService/CMakeLists.txt+4−1
@@ -5,4 +5,7 @@ add_library(MockAccountsService-qml MODULE
55
66qt5_use_modules(MockAccountsService-qml DBus Qml)
77
8−add_unity8_mock(AccountsService 0.1 AccountsService TARGETS MockAccountsService-qml)
8+add_unity8_mock(AccountsService 0.1 AccountsService
9+ PREFIX mocks
10+ TARGETS MockAccountsService-qml
11+ )
tests/mocks/AccountsService/plugin.cpp+1
@@ -32,5 +32,6 @@ static QObject *service_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
3232void AccountsServicePlugin::registerTypes(const char *uri)
3333{
3434 Q_ASSERT(uri == QLatin1String("AccountsService"));
35+ qRegisterMetaType<AccountsService::PasswordDisplayHint>("AccountsService::PasswordDisplayHint");
3536 qmlRegisterSingletonType<AccountsService>(uri, 0, 1, "AccountsService", service_provider);
3637}
tests/mocks/LightDM/GreeterPrivate.h+2
@@ -24,6 +24,7 @@
2424namespace QLightDM
2525{
2626class Greeter;
27+class GreeterImpl;
2728
2829class GreeterPrivate
2930{
@@ -40,6 +41,7 @@ public:
4041 void handleRespond(const QString &response);
4142
4243protected:
44+ GreeterImpl *m_impl; // if the backend needs more private data
4345 Greeter * const q_ptr;
4446
4547private:
tests/mocks/LightDM/demo/CMakeLists.txt+4−2
@@ -11,11 +11,13 @@ set(LibLightDM_SOURCES
1111add_library(MockLightDM-demo STATIC ${LibLightDM_SOURCES})
1212
1313include_directories(
14+ ${CMAKE_CURRENT_BINARY_DIR}
1415 ${LIBUSERMETRICSOUTPUT_INCLUDE_DIRS}
1516)
1617
1718target_link_libraries(MockLightDM-demo
1819 ${LIBUSERMETRICSOUTPUT_LDFLAGS}
19− )
20+ -lpam
21+)
2022
21−qt5_use_modules(MockLightDM-demo Gui)
23+qt5_use_modules(MockLightDM-demo Concurrent Gui)
tests/mocks/LightDM/demo/GreeterPrivate.cpp+202−30
@@ -18,53 +18,225 @@
1818
1919#include "../Greeter.h"
2020#include "../GreeterPrivate.h"
21−#include <QtCore/QDir>
22−#include <QtCore/QSettings>
21+#include <QFuture>
22+#include <QFutureInterface>
23+#include <QFutureWatcher>
24+#include <QQueue>
25+#include <QtConcurrent>
26+#include <QVector>
27+#include <security/pam_appl.h>
2328
2429namespace QLightDM
2530{
2631
32+class GreeterImpl : public QObject
33+{
34+ Q_OBJECT
35+
36+ typedef QFutureInterface<QString> ResponseFuture;
37+
38+public:
39+ explicit GreeterImpl(Greeter *parent, GreeterPrivate *greeterPrivate)
40+ : QObject(parent),
41+ greeter(parent),
42+ greeterPrivate(greeterPrivate),
43+ pamHandle(NULL)
44+ {
45+ qRegisterMetaType<QLightDM::GreeterImpl::ResponseFuture>("QLightDM::GreeterImpl::ResponseFuture");
46+
47+ connect(&futureWatcher, SIGNAL(finished()),
48+ this, SLOT(finishPam()));
49+ connect(this, SIGNAL(showMessage(QString, QLightDM::Greeter::MessageType)),
50+ greeter, SIGNAL(showMessage(QString, QLightDM::Greeter::MessageType)));
51+ // This next connect is how we pass ResponseFutures between threads
52+ connect(this, SIGNAL(showPrompt(QString, QLightDM::Greeter::PromptType, QLightDM::GreeterImpl::ResponseFuture)),
53+ this, SLOT(handlePrompt(QString, QLightDM::Greeter::PromptType, QLightDM::GreeterImpl::ResponseFuture)));
54+ }
55+
56+ void start(QString username)
57+ {
58+ // Clear out any existing PAM interactions first (we can't simply
59+ // cancel our QFuture because QtConcurrent::run doesn't support cancel)
60+ if (pamHandle != NULL) {
61+ pam_handle *handle = pamHandle;
62+ pamHandle = NULL; // to disable normal finishPam() handling
63+ while (respond(QString())); // clear our local queue of QFutures
64+ pam_end(handle, PAM_CONV_ERR);
65+ }
66+
67+ // Now actually start a new conversation with PAM
68+ pam_conv conversation;
69+ conversation.conv = converseWithPam;
70+ conversation.appdata_ptr = static_cast<void*>(this);
71+
72+ if (pam_start("lightdm", username.toUtf8(), &conversation, &pamHandle) == PAM_SUCCESS) {
73+ futureWatcher.setFuture(QtConcurrent::run(authenticateWithPam, pamHandle));
74+ } else {
75+ greeterPrivate->authenticated = false;
76+ Q_EMIT greeter->showMessage("Internal error: could not start PAM authentication", QLightDM::Greeter::MessageTypeError);
77+ Q_EMIT greeter->authenticationComplete();
78+ }
79+ }
80+
81+ static int authenticateWithPam(pam_handle* pamHandle)
82+ {
83+ int pamStatus = pam_authenticate(pamHandle, 0);
84+ if (pamStatus == PAM_SUCCESS) {
85+ pamStatus = pam_acct_mgmt(pamHandle, 0);
86+ }
87+ if (pamStatus == PAM_NEW_AUTHTOK_REQD) {
88+ pamStatus = pam_chauthtok(pamHandle, PAM_CHANGE_EXPIRED_AUTHTOK);
89+ }
90+ if (pamStatus == PAM_SUCCESS) {
91+ pam_setcred(pamHandle, PAM_REINITIALIZE_CRED);
92+ }
93+ return pamStatus;
94+ }
95+
96+ static int converseWithPam(int num_msg, const pam_message** msg,
97+ pam_response** resp, void* appdata_ptr)
98+ {
99+ if (num_msg <= 0)
100+ return PAM_CONV_ERR;
101+
102+ auto* tmp_response = static_cast<pam_response*>(calloc(num_msg, sizeof(pam_response)));
103+ if (!tmp_response)
104+ return PAM_CONV_ERR;
105+
106+ GreeterImpl* impl = static_cast<GreeterImpl*>(appdata_ptr);
107+
108+ int count;
109+ QVector<ResponseFuture> responses;
110+
111+ for (count = 0; count < num_msg; ++count)
112+ {
113+ switch (msg[count]->msg_style)
114+ {
115+ case PAM_PROMPT_ECHO_ON:
116+ {
117+ QString message(msg[count]->msg);
118+ responses.append(ResponseFuture());
119+ responses.last().reportStarted();
120+ Q_EMIT impl->showPrompt(message, Greeter::PromptTypeQuestion, responses.last());
121+ break;
122+ }
123+ case PAM_PROMPT_ECHO_OFF:
124+ {
125+ QString message(msg[count]->msg);
126+ responses.append(ResponseFuture());
127+ responses.last().reportStarted();
128+ Q_EMIT impl->showPrompt(message, Greeter::PromptTypeSecret, responses.last());
129+ break;
130+ }
131+ case PAM_TEXT_INFO:
132+ {
133+ QString message(msg[count]->msg);
134+ Q_EMIT impl->showMessage(message, Greeter::MessageTypeInfo);
135+ break;
136+ }
137+ default:
138+ {
139+ QString message(msg[count]->msg);
140+ Q_EMIT impl->showMessage(message, Greeter::MessageTypeError);
141+ break;
142+ }
143+ }
144+ }
145+
146+ int i = 0;
147+ bool raise_error = false;
148+
149+ for (auto &response : responses)
150+ {
151+ pam_response* resp_item = &tmp_response[i++];
152+ resp_item->resp_retcode = 0;
153+ resp_item->resp = strdup(response.future().result().toUtf8());
154+
155+ if (!resp_item->resp)
156+ {
157+ raise_error = true;
158+ break;
159+ }
160+ }
161+
162+ if (raise_error)
163+ {
164+ for (int i = 0; i < count; ++i)
165+ free(tmp_response[i].resp);
166+
167+ free(tmp_response);
168+ return PAM_CONV_ERR;
169+ }
170+ else
171+ {
172+ *resp = tmp_response;
173+ return PAM_SUCCESS;
174+ }
175+ }
176+
177+public Q_SLOTS:
178+ bool respond(QString response)
179+ {
180+ if (!futures.isEmpty()) {
181+ futures.dequeue().reportFinished(&response);
182+ return true;
183+ } else {
184+ return false;
185+ }
186+ }
187+
188+Q_SIGNALS:
189+ void showMessage(QString text, QLightDM::Greeter::MessageType type);
190+ void showPrompt(QString text, QLightDM::Greeter::PromptType type, QLightDM::GreeterImpl::ResponseFuture response);
191+
192+private Q_SLOTS:
193+ void finishPam()
194+ {
195+ if (pamHandle == NULL) {
196+ return;
197+ }
198+
199+ int pamStatus = futureWatcher.result();
200+
201+ pam_end(pamHandle, pamStatus);
202+ pamHandle = NULL;
203+
204+ greeterPrivate->authenticated = (pamStatus == PAM_SUCCESS);
205+ Q_EMIT greeter->authenticationComplete();
206+ }
207+
208+ void handlePrompt(QString text, QLightDM::Greeter::PromptType type, QLightDM::GreeterImpl::ResponseFuture future)
209+ {
210+ futures.enqueue(future);
211+ Q_EMIT greeter->showPrompt(text, type);
212+ }
213+
214+private:
215+ Greeter *greeter;
216+ GreeterPrivate *greeterPrivate;
217+ pam_handle* pamHandle;
218+ QFutureWatcher<int> futureWatcher;
219+ QQueue<ResponseFuture> futures;
220+};
221+
27222GreeterPrivate::GreeterPrivate(Greeter* parent)
28223 : authenticated(false),
29224 authenticationUser(),
225+ m_impl(new GreeterImpl(parent, this)),
30226 q_ptr(parent)
31227{
32228}
33229
34230void GreeterPrivate::handleAuthenticate()
35231{
36− Q_Q(Greeter);
37−
38− QSettings settings(QDir::homePath() + "/.unity8-greeter-demo", QSettings::NativeFormat);
39− settings.beginGroup(authenticationUser);
40− QVariant password = settings.value("password", "none");
41−
42− if (password == "pin") {
43− Q_EMIT q->showPrompt("PIN", Greeter::PromptTypeSecret);
44− } else if (password == "keyboard") {
45− Q_EMIT q->showPrompt("Password", Greeter::PromptTypeSecret);
46− } else {
47− authenticated = true;
48− Q_EMIT q->authenticationComplete();
49− }
232+ m_impl->start(authenticationUser);
50233}
51234
52235void GreeterPrivate::handleRespond(const QString &response)
53236{
54− Q_Q(Greeter);
55−
56− QSettings settings(QDir::homePath() + "/.unity8-greeter-demo", QSettings::NativeFormat);
57− settings.beginGroup(authenticationUser);
58− QVariant password = settings.value("password", "none");
59−
60− QString passwordValue;
61− if (password == "pin") {
62− passwordValue = settings.value("passwordValue", "1234").toString();
63− } else {
64− passwordValue = settings.value("passwordValue", "password").toString();
65− }
66− authenticated = (response == passwordValue);
67− Q_EMIT q->authenticationComplete();
237+ m_impl->respond(response);
68238}
69239
70240}
241+
242+#include "GreeterPrivate.moc"
tests/mocks/LightDM/demo/UsersModelPrivate.cpp+1−1
@@ -29,7 +29,7 @@ UsersModelPrivate::UsersModelPrivate(UsersModel* parent)
2929 : q_ptr(parent)
3030{
3131 QSettings settings(QDir::homePath() + "/.unity8-greeter-demo", QSettings::NativeFormat);
32− QStringList users = settings.value("users", QStringList() << "phablet").toStringList();
32+ QStringList users = settings.value("users", QStringList() << qgetenv("USER")).toStringList();
3333
3434 Q_FOREACH(const QString &user, users)
3535 {
tests/mocks/LightDM/full/GreeterPrivate.cpp+1−1
@@ -54,7 +54,7 @@ void GreeterPrivate::handleAuthenticate()
5454 authenticated = true;
5555 Q_EMIT q->authenticationComplete();
5656 } else if (authenticationUser == "has-pin"){
57− Q_EMIT q->showPrompt("PIN", Greeter::PromptTypeSecret);
57+ Q_EMIT q->showPrompt("Password:", Greeter::PromptTypeSecret);
5858 } else if (authenticationUser == "auth-error") {
5959 authenticated = false;
6060 Q_EMIT q->authenticationComplete();
tests/mocks/LightDM/single-pin/GreeterPrivate.cpp+1−1
@@ -32,7 +32,7 @@ GreeterPrivate::GreeterPrivate(Greeter* parent)
3232void GreeterPrivate::handleAuthenticate()
3333{
3434 Q_Q(Greeter);
35− Q_EMIT q->showPrompt("PIN", Greeter::PromptTypeSecret);
35+ Q_EMIT q->showPrompt("Password:", Greeter::PromptTypeSecret);
3636}
3737
3838void GreeterPrivate::handleRespond(const QString &response)
tests/mocks/LightDM/single-pin/UsersModelPrivate.cpp+1−1
@@ -26,7 +26,7 @@ UsersModelPrivate::UsersModelPrivate(UsersModel* parent)
2626{
2727 entries =
2828 {
29− { "single", "Has PIN", 0, 0, false, false, 0, 0 },
29+ { "has-pin", "Has PIN", 0, 0, false, false, 0, 0 },
3030 };
3131}
3232
tests/mocks/Unity/Application/Application.qmltypes−2
@@ -102,8 +102,6 @@ Module {
102102 "ForceMainStage": 1
103103 }
104104 }
105− Property { name: "keyboardHeight"; type: "int"; isReadonly: true }
106− Property { name: "keyboardVisible"; type: "bool"; isReadonly: true }
107105 Property { name: "sideStageWidth"; type: "int"; isReadonly: true }
108106 Property { name: "stageHint"; type: "StageHint"; isReadonly: true }
109107 Property { name: "formFactorHint"; type: "FormFactorHint"; isReadonly: true }
tests/mocks/Unity/Application/ApplicationDBusAdaptor.cppnew file+75
@@ -0,0 +1,75 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "ApplicationInfo.h"
18+#include "ApplicationDBusAdaptor.h"
19+#include "ApplicationManager.h"
20+#include "SurfaceManager.h"
21+#include "MirSurfaceItem.h"
22+
23+quint32 nextId = 0;
24+
25+ApplicationDBusAdaptor::ApplicationDBusAdaptor(ApplicationManager* applicationManager)
26+ : QDBusAbstractAdaptor(applicationManager)
27+ , m_applicationManager(applicationManager)
28+{
29+}
30+
31+MirSurfaceItem* topSurface(MirSurfaceItem* surface)
32+{
33+ if (!surface)
34+ return nullptr;
35+
36+ MirSurfaceItem* child = NULL;
37+ Q_FOREACH (MirSurfaceItem *childSurface, surface->childSurfaceList()) {
38+ child = topSurface(childSurface);
39+ if (child) {
40+ break;
41+ }
42+ }
43+ return child ? child : surface;
44+}
45+
46+quint32 ApplicationDBusAdaptor::addChildSurface(const QString &appId, const QString &surfaceImage)
47+{
48+ qDebug() << "ApplicationDBusAdaptor::addChildSurface - " << appId;
49+
50+ ApplicationInfo* application = m_applicationManager->findApplication(appId);
51+ if (!application) {
52+ qDebug() << "ApplicationDBusAdaptor::addChildSurface - No application found for " << appId;
53+ return 0;
54+ }
55+ quint32 surfaceId = ++nextId;
56+ MirSurfaceItem* surface = new MirSurfaceItem(QString("ChildSurface%1").arg(surfaceId),
57+ MirSurfaceItem::Normal,
58+ MirSurfaceItem::Maximized,
59+ QUrl(surfaceImage));
60+ m_childItems[surfaceId] = surface;
61+ surface->setParentSurface(topSurface(application->surface()));
62+ return surfaceId;
63+}
64+
65+void ApplicationDBusAdaptor::removeChildSurface(int surfaceId)
66+{
67+ qDebug() << "ApplicationDBusAdaptor::removeChildSurface - " << surfaceId;
68+
69+ if (!m_childItems.contains(surfaceId)) {
70+ qDebug() << "ApplicationDBusAdaptor::removeChildSurface - No added surface for " << surfaceId;
71+ return;
72+ }
73+ MirSurfaceItem* surface = m_childItems.take(surfaceId);
74+ Q_EMIT surface->removed();
75+}
tests/mocks/Unity/Application/ApplicationDBusAdaptor.hnew file+43
@@ -0,0 +1,43 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef APPLICATIONDBUSADAPTOR_H
18+#define APPLICATIONDBUSADAPTOR_H
19+
20+#include <QtDBus/QtDBus>
21+
22+class ApplicationManager;
23+class SurfaceManager;
24+class MirSurfaceItem;
25+
26+class ApplicationDBusAdaptor : public QDBusAbstractAdaptor
27+{
28+ Q_OBJECT
29+ Q_CLASSINFO("D-Bus Interface", "com.canonical.Unity8.Mocks.Application")
30+public:
31+ ApplicationDBusAdaptor(ApplicationManager* applicationManager);
32+
33+public Q_SLOTS:
34+ quint32 addChildSurface(const QString& appId, const QString& surfaceImage);
35+ void removeChildSurface(int surfaceId);
36+
37+private:
38+ ApplicationManager* m_applicationManager;
39+
40+ QHash<quint32, MirSurfaceItem*> m_childItems;
41+};
42+
43+#endif // APPLICATIONDBUSADAPTOR_H
tests/mocks/Unity/Application/ApplicationInfo.cpp+69−21
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2014 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -15,6 +15,8 @@
1515 */
1616
1717#include "ApplicationInfo.h"
18+#include "MirSurfaceItem.h"
19+#include "SurfaceManager.h"
1820
1921#include <QGuiApplication>
2022#include <QQuickItem>
@@ -24,29 +26,39 @@
2426
2527ApplicationInfo::ApplicationInfo(const QString &appId, QObject *parent)
2628 : ApplicationInfoInterface(appId, parent)
27− ,m_appId(appId)
28− ,m_stage(MainStage)
29− ,m_state(Starting)
30− ,m_focused(false)
31− ,m_fullscreen(false)
32− ,m_windowItem(0)
33− ,m_windowComponent(0)
34− ,m_parentItem(0)
29+ , m_appId(appId)
30+ , m_stage(MainStage)
31+ , m_state(Starting)
32+ , m_focused(false)
33+ , m_fullscreen(false)
34+ , m_windowItem(0)
35+ , m_windowComponent(0)
36+ , m_parentItem(0)
37+ , m_surface(0)
3538{
36− QTimer::singleShot(300, this, SLOT(setRunning()));
39+ connect(this, &ApplicationInfo::stateChanged, this, &ApplicationInfo::onStateChanged);
3740}
3841
3942ApplicationInfo::ApplicationInfo(QObject *parent)
4043 : ApplicationInfoInterface(QString(), parent)
41− ,m_stage(MainStage)
42− ,m_state(Starting)
43− ,m_focused(false)
44− ,m_fullscreen(false)
45− ,m_windowItem(0)
46− ,m_windowComponent(0)
47− ,m_parentItem(0)
44+ , m_stage(MainStage)
45+ , m_state(Starting)
46+ , m_focused(false)
47+ , m_fullscreen(false)
48+ , m_windowItem(0)
49+ , m_windowComponent(0)
50+ , m_parentItem(0)
51+ , m_surface(0)
4852{
49− QTimer::singleShot(300, this, SLOT(setRunning()));
53+ connect(this, &ApplicationInfo::stateChanged, this, &ApplicationInfo::onStateChanged);
54+}
55+
56+ApplicationInfo::~ApplicationInfo()
57+{
58+ if (m_surface) {
59+ Q_EMIT SurfaceManager::singleton()->surfaceDestroyed(m_surface);
60+ m_surface->deleteLater();
61+ }
5062}
5163
5264void ApplicationInfo::onWindowComponentStatusChanged(QQmlComponent::Status status)
@@ -55,10 +67,46 @@ void ApplicationInfo::onWindowComponentStatusChanged(QQmlComponent::Status statu
5567 doCreateWindowItem();
5668}
5769
58−void ApplicationInfo::setRunning()
70+void ApplicationInfo::onStateChanged(State state)
71+{
72+ if (state == ApplicationInfo::Running) {
73+ QTimer::singleShot(1000, this, SLOT(createSurface()));
74+ } else if (state == ApplicationInfo::Stopped) {
75+ setSurface(nullptr);
76+ }
77+}
78+
79+void ApplicationInfo::createSurface()
5980{
60− m_state = Running;
61− Q_EMIT stateChanged();
81+ if (m_surface || state() == ApplicationInfo::Stopped) return;
82+
83+ setSurface(new MirSurfaceItem(name(),
84+ MirSurfaceItem::Normal,
85+ fullscreen() ? MirSurfaceItem::Fullscreen : MirSurfaceItem::Maximized,
86+ screenshot()));
87+}
88+
89+void ApplicationInfo::setSurface(MirSurfaceItem* surface)
90+{
91+ if (m_surface == surface)
92+ return;
93+
94+ if (m_surface) {
95+ m_surface->setApplication(nullptr);
96+ m_surface->setParent(nullptr);
97+ SurfaceManager::singleton()->unregisterSurface(m_surface);
98+ }
99+
100+ m_surface = surface;
101+
102+ if (m_surface) {
103+ m_surface->setApplication(this);
104+ m_surface->setParent(this);
105+ SurfaceManager::singleton()->registerSurface(m_surface);
106+ }
107+
108+ Q_EMIT surfaceChanged(m_surface);
109+ SurfaceManager::singleton()->registerSurface(m_surface);
62110}
63111
64112void ApplicationInfo::createWindowComponent()
tests/mocks/Unity/Application/ApplicationInfo.h+21−8
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2014 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -21,6 +21,7 @@
2121#include <QQmlComponent>
2222
2323class QQuickItem;
24+class MirSurfaceItem;
2425
2526// unity-api
2627#include <unity/shell/application/ApplicationInfoInterface.h>
@@ -36,6 +37,7 @@ class ApplicationInfo : public ApplicationInfoInterface {
3637
3738 Q_PROPERTY(bool fullscreen READ fullscreen WRITE setFullscreen NOTIFY fullscreenChanged)
3839 Q_PROPERTY(Stage stage READ stage WRITE setStage NOTIFY stageChanged)
40+ Q_PROPERTY(MirSurfaceItem* surface READ surface NOTIFY surfaceChanged)
3941
4042 // Only exists in this fake implementation
4143
@@ -45,9 +47,10 @@ class ApplicationInfo : public ApplicationInfoInterface {
4547 // QML component used to represent the application window
4648 Q_PROPERTY(QString windowQml READ windowQml WRITE setWindowQml NOTIFY windowQmlChanged)
4749
48− public:
50+public:
4951 ApplicationInfo(QObject *parent = NULL);
5052 ApplicationInfo(const QString &appId, QObject *parent = NULL);
53+ ~ApplicationInfo();
5154
5255 #define IMPLEMENT_PROPERTY(name, Name, type) \
5356 public: \
@@ -56,11 +59,11 @@ class ApplicationInfo : public ApplicationInfoInterface {
5659 { \
5760 if (m_##name != value) { \
5861 m_##name = value; \
59− Q_EMIT name##Changed(); \
62+ Q_EMIT name##Changed(value); \
6063 } \
6164 } \
6265 Q_SIGNALS: \
63− void name##Changed(); \
66+ void name##Changed(const type&); \
6467 private: \
6568 type m_##name;
6669
@@ -78,21 +81,31 @@ class ApplicationInfo : public ApplicationInfoInterface {
7881
7982 #undef IMPLEMENT_PROPERTY
8083
81− public:
84+public:
85+ void setSurface(MirSurfaceItem* surface);
86+ MirSurfaceItem* surface() const { return m_surface; }
87+
88+Q_SIGNALS:
89+ void surfaceChanged(MirSurfaceItem*);
90+
91+public:
8292 void showWindow(QQuickItem *parent);
8393 void hideWindow();
8494
85− private Q_SLOTS:
95+private Q_SLOTS:
8696 void onWindowComponentStatusChanged(QQmlComponent::Status status);
87− void setRunning();
97+ void onStateChanged(State state);
98+
99+ void createSurface();
88100
89− private:
101+private:
90102 void createWindowItem();
91103 void doCreateWindowItem();
92104 void createWindowComponent();
93105 QQuickItem *m_windowItem;
94106 QQmlComponent *m_windowComponent;
95107 QQuickItem *m_parentItem;
108+ MirSurfaceItem* m_surface;
96109};
97110
98111Q_DECLARE_METATYPE(ApplicationInfo*)
tests/mocks/Unity/Application/ApplicationManager.cpp+60−31
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2014 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -15,7 +15,9 @@
1515 */
1616
1717#include "ApplicationManager.h"
18+#include "ApplicationDBusAdaptor.h"
1819#include "ApplicationInfo.h"
20+#include "MirSurfaceItem.h"
1921
2022#include <paths.h>
2123
@@ -26,6 +28,22 @@
2628#include <QQmlComponent>
2729#include <QTimer>
2830#include <QDateTime>
31+#include <QtDBus/QtDBus>
32+
33+ApplicationManager *ApplicationManager::the_application_manager = nullptr;
34+
35+ApplicationManager *ApplicationManager::singleton()
36+{
37+ if (!the_application_manager) {
38+ the_application_manager = new ApplicationManager();
39+ new ApplicationDBusAdaptor(the_application_manager);
40+
41+ QDBusConnection connection = QDBusConnection::sessionBus();
42+ connection.registerService("com.canonical.Unity8");
43+ connection.registerObject("/com/canonical/Unity8/Mocks", the_application_manager);
44+ }
45+ return the_application_manager;
46+}
2947
3048ApplicationManager::ApplicationManager(QObject *parent)
3149 : ApplicationManagerInterface(parent)
@@ -36,6 +54,9 @@ ApplicationManager::ApplicationManager(QObject *parent)
3654 , m_sideStage(0)
3755 , m_rightMargin(0)
3856{
57+ m_roleNames.insert(RoleSurface, "surface");
58+ m_roleNames.insert(RoleFullscreen, "fullscreen");
59+
3960 buildListOfAvailableApplications();
4061}
4162
@@ -69,6 +90,10 @@ QVariant ApplicationManager::data(const QModelIndex& index, int role) const {
6990 return app->focused();
7091 case RoleScreenshot:
7192 return app->screenshot();
93+ case RoleSurface:
94+ return QVariant::fromValue(app->surface());
95+ case RoleFullscreen:
96+ return app->fullscreen();
7297 default:
7398 return QVariant();
7499 }
@@ -89,6 +114,17 @@ ApplicationInfo *ApplicationManager::findApplication(const QString &appId) const
89114 return nullptr;
90115}
91116
117+QModelIndex ApplicationManager::findIndex(ApplicationInfo* application)
118+{
119+ for (int i = 0; i < m_runningApplications.size(); ++i) {
120+ if (m_runningApplications.at(i) == application) {
121+ return index(i);
122+ }
123+ }
124+
125+ return QModelIndex();
126+}
127+
92128void ApplicationManager::add(ApplicationInfo *application) {
93129 if (!application) {
94130 return;
@@ -99,7 +135,14 @@ void ApplicationManager::add(ApplicationInfo *application) {
99135 endInsertRows();
100136 Q_EMIT applicationAdded(application->appId());
101137 Q_EMIT countChanged();
138+ if (count() == 1) Q_EMIT emptyChanged(isEmpty()); // was empty but not anymore
102139 Q_EMIT focusRequested(application->appId());
140+
141+ connect(application, &ApplicationInfo::surfaceChanged, this, [application, this]() {
142+ QModelIndex appIndex = findIndex(application);
143+ if (!appIndex.isValid()) return;
144+ Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << ApplicationManager::RoleSurface);
145+ });
103146}
104147
105148void ApplicationManager::remove(ApplicationInfo *application) {
@@ -110,7 +153,9 @@ void ApplicationManager::remove(ApplicationInfo *application) {
110153 endRemoveRows();
111154 Q_EMIT applicationRemoved(application->appId());
112155 Q_EMIT countChanged();
156+ if (isEmpty()) Q_EMIT emptyChanged(isEmpty());
113157 }
158+ disconnect(application, &ApplicationInfo::surfaceChanged, this, 0);
114159}
115160
116161void ApplicationManager::move(int from, int to) {
@@ -127,16 +172,6 @@ void ApplicationManager::move(int from, int to) {
127172 }
128173}
129174
130−int ApplicationManager::keyboardHeight() const
131−{
132− return 0;
133−}
134−
135−bool ApplicationManager::keyboardVisible() const
136−{
137− return false;
138−}
139−
140175int ApplicationManager::sideStageWidth() const
141176{
142177 return 0;
@@ -180,8 +215,7 @@ ApplicationInfo* ApplicationManager::startApplication(const QString &appId,
180215 application->setStage(ApplicationInfo::MainStage);
181216 }
182217 add(application);
183−
184− QMetaObject::invokeMethod(this, "focusApplication", Qt::QueuedConnection, Q_ARG(QString, appId));
218+ application->setState(ApplicationInfo::Running);
185219
186220 return application;
187221}
@@ -195,6 +229,7 @@ bool ApplicationManager::stopApplication(const QString &appId)
195229 if (application->appId() == focusedApplicationId()) {
196230 unfocusCurrentApplication();
197231 }
232+ application->setState(ApplicationInfo::Stopped);
198233 remove(application);
199234 return true;
200235}
@@ -215,7 +250,6 @@ bool ApplicationManager::updateScreenshot(const QString &appId)
215250 return false;
216251 }
217252
218− application->setScreenshot(QString("image://application/%1/%2").arg(appId).arg(QDateTime::currentMSecsSinceEpoch()));
219253 QModelIndex appIndex = index(idx);
220254 Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << RoleScreenshot);
221255 return true;
@@ -261,6 +295,7 @@ bool ApplicationManager::focusApplication(const QString &appId)
261295 if (app->focused() && app->stage() == ApplicationInfo::MainStage) {
262296 app->setFocused(false);
263297 app->hideWindow();
298+ app->setState(ApplicationInfo::Suspended);
264299 }
265300 }
266301
@@ -268,6 +303,7 @@ bool ApplicationManager::focusApplication(const QString &appId)
268303 application->setFocused(true);
269304 if (!m_mainStage)
270305 createMainStage();
306+ application->setState(ApplicationInfo::Running);
271307 application->showWindow(m_mainStage);
272308 m_mainStage->setZ(-1000);
273309 if (m_sideStage)
@@ -278,6 +314,7 @@ bool ApplicationManager::focusApplication(const QString &appId)
278314 if (app->focused() && app->stage() == ApplicationInfo::SideStage) {
279315 app->setFocused(false);
280316 app->hideWindow();
317+ app->setState(ApplicationInfo::Suspended);
281318 }
282319 }
283320
@@ -285,6 +322,7 @@ bool ApplicationManager::focusApplication(const QString &appId)
285322 application->setFocused(true);
286323 if (!m_sideStage)
287324 createSideStage();
325+ application->setState(ApplicationInfo::Running);
288326 application->showWindow(m_sideStage);
289327 m_sideStage->setZ(-1000);
290328 if (m_mainStage)
@@ -354,6 +392,9 @@ void ApplicationManager::generateQmlStrings(ApplicationInfo *application)
354392 "}").arg(qmlDirectory())
355393 .arg(application->icon().toString());
356394 application->setImageQml(imageQml);
395+
396+ application->setScreenshot(QString("file://%1/Dash/graphics/phone/screenshots/%2@12.png").arg(qmlDirectory())
397+ .arg(application->icon().toString()));
357398}
358399
359400void ApplicationManager::buildListOfAvailableApplications()
@@ -365,7 +406,6 @@ void ApplicationManager::buildListOfAvailableApplications()
365406 application->setName("Dialer");
366407 application->setIcon(QUrl("dialer"));
367408 application->setStage(ApplicationInfo::SideStage);
368− application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
369409 generateQmlStrings(application);
370410 m_availableApplications.append(application);
371411
@@ -374,7 +414,6 @@ void ApplicationManager::buildListOfAvailableApplications()
374414 application->setName("Camera");
375415 application->setIcon(QUrl("camera"));
376416 application->setFullscreen(true);
377− application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
378417 generateQmlStrings(application);
379418 m_availableApplications.append(application);
380419
@@ -382,7 +421,6 @@ void ApplicationManager::buildListOfAvailableApplications()
382421 application->setAppId("gallery-app");
383422 application->setName("Gallery");
384423 application->setIcon(QUrl("gallery"));
385− application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
386424 generateQmlStrings(application);
387425 m_availableApplications.append(application);
388426
@@ -391,7 +429,6 @@ void ApplicationManager::buildListOfAvailableApplications()
391429 application->setName("Facebook");
392430 application->setIcon(QUrl("facebook"));
393431 application->setStage(ApplicationInfo::SideStage);
394− application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
395432 generateQmlStrings(application);
396433 m_availableApplications.append(application);
397434
@@ -400,7 +437,6 @@ void ApplicationManager::buildListOfAvailableApplications()
400437 application->setFullscreen(true);
401438 application->setName("Browser");
402439 application->setIcon(QUrl("browser"));
403− application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
404440 generateQmlStrings(application);
405441 m_availableApplications.append(application);
406442
@@ -409,7 +445,6 @@ void ApplicationManager::buildListOfAvailableApplications()
409445 application->setName("Twitter");
410446 application->setIcon(QUrl("twitter"));
411447 application->setStage(ApplicationInfo::SideStage);
412− application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
413448 generateQmlStrings(application);
414449 m_availableApplications.append(application);
415450
@@ -417,7 +452,6 @@ void ApplicationManager::buildListOfAvailableApplications()
417452 application->setAppId("gmail-webapp");
418453 application->setName("GMail");
419454 application->setIcon(QUrl("gmail"));
420− application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
421455 m_availableApplications.append(application);
422456
423457 application = new ApplicationInfo(this);
@@ -425,7 +459,6 @@ void ApplicationManager::buildListOfAvailableApplications()
425459 application->setName("Weather");
426460 application->setIcon(QUrl("weather"));
427461 application->setStage(ApplicationInfo::SideStage);
428− application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
429462 generateQmlStrings(application);
430463 m_availableApplications.append(application);
431464
@@ -434,7 +467,6 @@ void ApplicationManager::buildListOfAvailableApplications()
434467 application->setName("Notepad");
435468 application->setIcon(QUrl("notepad"));
436469 application->setStage(ApplicationInfo::SideStage);
437− application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
438470 m_availableApplications.append(application);
439471
440472 application = new ApplicationInfo(this);
@@ -442,7 +474,6 @@ void ApplicationManager::buildListOfAvailableApplications()
442474 application->setName("Calendar");
443475 application->setIcon(QUrl("calendar"));
444476 application->setStage(ApplicationInfo::SideStage);
445− application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
446477 m_availableApplications.append(application);
447478
448479 application = new ApplicationInfo(this);
@@ -450,21 +481,18 @@ void ApplicationManager::buildListOfAvailableApplications()
450481 application->setName("Media Player");
451482 application->setIcon(QUrl("mediaplayer-app"));
452483 application->setFullscreen(true);
453− application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
454484 m_availableApplications.append(application);
455485
456486 application = new ApplicationInfo(this);
457487 application->setAppId("evernote");
458488 application->setName("Evernote");
459489 application->setIcon(QUrl("evernote"));
460− application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
461490 m_availableApplications.append(application);
462491
463492 application = new ApplicationInfo(this);
464493 application->setAppId("map");
465494 application->setName("Map");
466495 application->setIcon(QUrl("map"));
467− application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
468496 generateQmlStrings(application);
469497 m_availableApplications.append(application);
470498
@@ -472,28 +500,24 @@ void ApplicationManager::buildListOfAvailableApplications()
472500 application->setAppId("pinterest");
473501 application->setName("Pinterest");
474502 application->setIcon(QUrl("pinterest"));
475− application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
476503 m_availableApplications.append(application);
477504
478505 application = new ApplicationInfo(this);
479506 application->setAppId("soundcloud");
480507 application->setName("SoundCloud");
481508 application->setIcon(QUrl("soundcloud"));
482− application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
483509 m_availableApplications.append(application);
484510
485511 application = new ApplicationInfo(this);
486512 application->setAppId("wikipedia");
487513 application->setName("Wikipedia");
488514 application->setIcon(QUrl("wikipedia"));
489− application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
490515 m_availableApplications.append(application);
491516
492517 application = new ApplicationInfo(this);
493518 application->setAppId("youtube");
494519 application->setName("YouTube");
495520 application->setIcon(QUrl("youtube"));
496− application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
497521 m_availableApplications.append(application);
498522}
499523
@@ -590,3 +614,8 @@ void ApplicationManager::setRightMargin(int rightMargin)
590614 generateQmlStrings(app);
591615 }
592616}
617+
618+bool ApplicationManager::isEmpty() const
619+{
620+ return m_runningApplications.isEmpty();
621+}
tests/mocks/Unity/Application/ApplicationManager.h+15−9
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2014 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -36,8 +36,7 @@ class ApplicationManager : public ApplicationManagerInterface {
3636 Q_ENUMS(FavoriteApplication)
3737 Q_FLAGS(ExecFlags)
3838
39− Q_PROPERTY(int keyboardHeight READ keyboardHeight NOTIFY keyboardHeightChanged)
40− Q_PROPERTY(bool keyboardVisible READ keyboardVisible NOTIFY keyboardVisibleChanged)
39+ Q_PROPERTY(bool empty READ isEmpty NOTIFY emptyChanged)
4140
4241 Q_PROPERTY(int sideStageWidth READ sideStageWidth)
4342 Q_PROPERTY(StageHint stageHint READ stageHint)
@@ -53,6 +52,12 @@ class ApplicationManager : public ApplicationManagerInterface {
5352 ApplicationManager(QObject *parent = NULL);
5453 virtual ~ApplicationManager();
5554
55+ static ApplicationManager *singleton();
56+
57+ enum MoreRoles {
58+ RoleSurface = RoleScreenshot+1,
59+ RoleFullscreen,
60+ };
5661 enum Role {
5762 Dash, Default, Indicators, Notifications, Greeter, Launcher, OnScreenKeyboard,
5863 ShutdownDialog
@@ -74,8 +79,6 @@ class ApplicationManager : public ApplicationManagerInterface {
7479 };
7580 Q_DECLARE_FLAGS(ExecFlags, Flag)
7681
77− int keyboardHeight() const;
78− bool keyboardVisible() const;
7982 int sideStageWidth() const;
8083 StageHint stageHint() const;
8184 FormFactorHint formFactorHint() const;
@@ -108,11 +111,14 @@ class ApplicationManager : public ApplicationManagerInterface {
108111 int rightMargin() const;
109112 void setRightMargin(int rightMargin);
110113
114+ QModelIndex findIndex(ApplicationInfo* application);
115+
116+ bool isEmpty() const;
117+
111118 Q_SIGNALS:
112− void keyboardHeightChanged();
113− void keyboardVisibleChanged();
114119 void focusRequested(FavoriteApplication favoriteApplication);
115120 void focusRequested(const QString &appId);
121+ void emptyChanged(bool empty);
116122
117123 private:
118124 void add(ApplicationInfo *application);
@@ -124,8 +130,6 @@ class ApplicationManager : public ApplicationManagerInterface {
124130 void createMainStage();
125131 void createSideStageComponent();
126132 void createSideStage();
127− int m_keyboardHeight;
128− bool m_keyboardVisible;
129133 bool m_suspended;
130134 QList<ApplicationInfo*> m_runningApplications;
131135 QList<ApplicationInfo*> m_availableApplications;
@@ -135,6 +139,8 @@ class ApplicationManager : public ApplicationManagerInterface {
135139 QQuickItem *m_sideStage;
136140
137141 int m_rightMargin;
142+
143+ static ApplicationManager *the_application_manager;
138144};
139145
140146Q_DECLARE_OPERATORS_FOR_FLAGS(ApplicationManager::ExecFlags)
tests/mocks/Unity/Application/CMakeLists.txt+6−2
@@ -2,10 +2,14 @@ pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application)
22
33set(FakeUnityApplicationQml_SOURCES
44 plugin.cpp
5+ ApplicationDBusAdaptor.cpp
56 ApplicationInfo.cpp
67 ApplicationImage.cpp
78 ApplicationManager.cpp
89 ApplicationScreenshotProvider.cpp
10+ MirSurfaceItem.cpp
11+ SurfaceManager.cpp
12+ VirtualKeyboard.cpp
913 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
1014 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
1115)
@@ -15,8 +19,8 @@ add_library(FakeUnityApplicationQml MODULE ${FakeUnityApplicationQml_SOURCES})
1519add_library(NonMirUnityApplicationQml MODULE ${FakeUnityApplicationQml_SOURCES})
1620set_target_properties(NonMirUnityApplicationQml PROPERTIES OUTPUT_NAME FakeUnityApplicationQml)
1721
18−qt5_use_modules(FakeUnityApplicationQml Core Quick)
19−qt5_use_modules(NonMirUnityApplicationQml Core Quick)
22+qt5_use_modules(FakeUnityApplicationQml Core Quick DBus)
23+qt5_use_modules(NonMirUnityApplicationQml Core Quick DBus)
2024
2125add_unity8_mock(Unity.Application 0.1 Unity/Application PREFIX mocks TARGETS FakeUnityApplicationQml)
2226add_unity8_mock(Unity.Application 0.1 Unity/Application
tests/mocks/Unity/Application/MirSurfaceItem.cppnew file+172
@@ -0,0 +1,172 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "MirSurfaceItem.h"
18+#include "ApplicationInfo.h"
19+
20+#include <QPainter>
21+#include <QQmlEngine>
22+
23+MirSurfaceItem::MirSurfaceItem(const QString& name,
24+ MirSurfaceItem::Type type,
25+ MirSurfaceItem::State state,
26+ const QUrl& screenshot,
27+ QQuickItem *parent)
28+ : QQuickPaintedItem(parent)
29+ , m_application(nullptr)
30+ , m_name(name)
31+ , m_type(type)
32+ , m_state(state)
33+ , m_img(screenshot.isLocalFile() ? screenshot.toLocalFile() : screenshot.toString())
34+ , m_parentSurface(nullptr)
35+ , m_haveInputMethod(false)
36+{
37+ QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
38+
39+ // The virtual keyboard (input method) has a big transparent area so that
40+ // content behind it show through
41+ setFillColor(Qt::transparent);
42+
43+ connect(this, &QQuickItem::focusChanged,
44+ this, &MirSurfaceItem::onFocusChanged);
45+}
46+
47+MirSurfaceItem::~MirSurfaceItem()
48+{
49+ QList<MirSurfaceItem*> children(m_children);
50+ for (MirSurfaceItem* child : children) {
51+ child->setParentSurface(nullptr);
52+ }
53+ if (m_parentSurface)
54+ m_parentSurface->removeChildSurface(this);
55+
56+ if (m_application)
57+ m_application->setSurface(nullptr);
58+}
59+
60+void MirSurfaceItem::paint(QPainter * painter)
61+{
62+ if (!m_img.isNull()) {
63+ painter->drawImage(contentsBoundingRect(), m_img, QRect(QPoint(0,0), m_img.size()));
64+ }
65+}
66+
67+void MirSurfaceItem::release()
68+{
69+ QList<MirSurfaceItem*> children(m_children);
70+ for (MirSurfaceItem* child : children) {
71+ child->release();
72+ }
73+ if (m_parentSurface) {
74+ m_parentSurface->removeChildSurface(this);
75+ }
76+
77+ if (m_application) {
78+ m_application->setSurface(nullptr);
79+ }
80+ if (!parent()) {
81+ deleteLater();
82+ }
83+}
84+
85+void MirSurfaceItem::setApplication(ApplicationInfo* application)
86+{
87+ m_application = application;
88+}
89+
90+void MirSurfaceItem::setParentSurface(MirSurfaceItem* surface)
91+{
92+ if (m_parentSurface == surface || surface == this)
93+ return;
94+
95+ if (m_parentSurface) {
96+ m_parentSurface->removeChildSurface(this);
97+ }
98+
99+ m_parentSurface = surface;
100+
101+ if (m_parentSurface) {
102+ m_parentSurface->addChildSurface(this);
103+ }
104+ Q_EMIT parentSurfaceChanged(surface);
105+}
106+
107+void MirSurfaceItem::removeChildSurface(MirSurfaceItem* surface)
108+{
109+ if (m_children.contains(surface)) {
110+ m_children.removeOne(surface);
111+ Q_EMIT childSurfacesChanged();
112+ }
113+}
114+
115+void MirSurfaceItem::addChildSurface(MirSurfaceItem* surface)
116+{
117+ m_children.append(surface);
118+ Q_EMIT childSurfacesChanged();
119+}
120+
121+QList<MirSurfaceItem*> MirSurfaceItem::childSurfaceList()
122+{
123+ return m_children;
124+}
125+
126+QQmlListProperty<MirSurfaceItem> MirSurfaceItem::childSurfaces()
127+{
128+ return QQmlListProperty<MirSurfaceItem>(this,
129+ 0,
130+ MirSurfaceItem::childSurfaceCount,
131+ MirSurfaceItem::childSurfaceAt);
132+}
133+
134+int MirSurfaceItem::childSurfaceCount(QQmlListProperty<MirSurfaceItem> *prop)
135+{
136+ MirSurfaceItem *p = qobject_cast<MirSurfaceItem*>(prop->object);
137+ return p->m_children.count();
138+}
139+
140+MirSurfaceItem* MirSurfaceItem::childSurfaceAt(QQmlListProperty<MirSurfaceItem> *prop, int index)
141+{
142+ MirSurfaceItem *p = qobject_cast<MirSurfaceItem*>(prop->object);
143+
144+ if (index < 0 || index >= p->m_children.count())
145+ return nullptr;
146+ return p->m_children[index];
147+}
148+
149+
150+void MirSurfaceItem::touchEvent(QTouchEvent * event)
151+{
152+ if (event->type() == QEvent::TouchBegin && hasFocus()) {
153+ Q_EMIT inputMethodRequested();
154+ m_haveInputMethod = true;
155+ }
156+}
157+
158+void MirSurfaceItem::onFocusChanged()
159+{
160+ if (!hasFocus() && m_haveInputMethod) {
161+ Q_EMIT inputMethodDismissed();
162+ m_haveInputMethod = false;
163+ }
164+}
165+
166+void MirSurfaceItem::setState(MirSurfaceItem::State newState)
167+{
168+ if (newState != m_state) {
169+ m_state = newState;
170+ Q_EMIT stateChanged(newState);
171+ }
172+}
tests/mocks/Unity/Application/MirSurfaceItem.hnew file+121
@@ -0,0 +1,121 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef MIRSURFACEITEM_H
18+#define MIRSURFACEITEM_H
19+
20+#include <QQuickPaintedItem>
21+#include <QImage>
22+
23+class ApplicationInfo;
24+
25+class MirSurfaceItem : public QQuickPaintedItem
26+{
27+ Q_OBJECT
28+ Q_ENUMS(Type)
29+ Q_ENUMS(State)
30+
31+ Q_PROPERTY(Type type READ type NOTIFY typeChanged)
32+ Q_PROPERTY(State state READ state NOTIFY stateChanged)
33+ Q_PROPERTY(QString name READ name NOTIFY nameChanged)
34+ Q_PROPERTY(MirSurfaceItem* parentSurface READ parentSurface NOTIFY parentSurfaceChanged)
35+ Q_PROPERTY(QQmlListProperty<MirSurfaceItem> childSurfaces READ childSurfaces NOTIFY childSurfacesChanged DESIGNABLE false)
36+
37+public:
38+ enum Type {
39+ Normal,
40+ Utility,
41+ Dialog,
42+ Overlay,
43+ Freestyle,
44+ Popover,
45+ InputMethod,
46+ };
47+
48+ enum State {
49+ Unknown,
50+ Restored,
51+ Minimized,
52+ Maximized,
53+ VertMaximized,
54+ /* SemiMaximized, */
55+ Fullscreen,
56+ };
57+
58+ explicit MirSurfaceItem(const QString& name,
59+ Type type,
60+ State state,
61+ const QUrl& screenshot,
62+ QQuickItem *parent = 0);
63+ ~MirSurfaceItem();
64+
65+ //getters
66+ ApplicationInfo* application() const { return m_application; }
67+ Type type() const { return m_type; }
68+ State state() const { return m_state; }
69+ QString name() const { return m_name; }
70+ MirSurfaceItem* parentSurface() const { return m_parentSurface; }
71+ QList<MirSurfaceItem*> childSurfaceList();
72+
73+ void setApplication(ApplicationInfo* item);
74+ void setParentSurface(MirSurfaceItem* item);
75+
76+ Q_INVOKABLE void setState(State newState);
77+ Q_INVOKABLE void release();
78+
79+ void paint(QPainter * painter) override;
80+ void touchEvent(QTouchEvent * event) override;
81+
82+Q_SIGNALS:
83+ void typeChanged(Type);
84+ void stateChanged(State);
85+ void nameChanged(QString);
86+ void parentSurfaceChanged(MirSurfaceItem*);
87+ void childSurfacesChanged();
88+
89+ void removed();
90+
91+ void inputMethodRequested();
92+ void inputMethodDismissed();
93+
94+private Q_SLOTS:
95+ void onFocusChanged();
96+
97+protected:
98+ const QImage &screenshotImage() { return m_img; }
99+
100+private:
101+ void addChildSurface(MirSurfaceItem* surface);
102+ void removeChildSurface(MirSurfaceItem* surface);
103+
104+ QQmlListProperty<MirSurfaceItem> childSurfaces();
105+ static int childSurfaceCount(QQmlListProperty<MirSurfaceItem> *prop);
106+ static MirSurfaceItem* childSurfaceAt(QQmlListProperty<MirSurfaceItem> *prop, int index);
107+
108+ ApplicationInfo* m_application;
109+ const QString m_name;
110+ const Type m_type;
111+ State m_state;
112+ const QImage m_img;
113+
114+ MirSurfaceItem* m_parentSurface;
115+ QList<MirSurfaceItem*> m_children;
116+ bool m_haveInputMethod;
117+};
118+
119+Q_DECLARE_METATYPE(MirSurfaceItem*)
120+
121+#endif // MIRSURFACEITEM_H
tests/mocks/Unity/Application/SurfaceManager.cppnew file+73
@@ -0,0 +1,73 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "SurfaceManager.h"
18+
19+#include "MirSurfaceItem.h"
20+#include "VirtualKeyboard.h"
21+
22+
23+SurfaceManager *SurfaceManager::the_surface_manager = nullptr;
24+
25+SurfaceManager *SurfaceManager::singleton()
26+{
27+ if (!the_surface_manager) {
28+ the_surface_manager = new SurfaceManager();
29+ }
30+ return the_surface_manager;
31+}
32+
33+SurfaceManager::SurfaceManager(QObject *parent) :
34+ QObject(parent)
35+ , m_virtualKeyboard(nullptr)
36+{
37+}
38+
39+void SurfaceManager::registerSurface(MirSurfaceItem *surface)
40+{
41+ connect(surface, &MirSurfaceItem::inputMethodRequested,
42+ this, &SurfaceManager::showInputMethod);
43+ connect(surface, &MirSurfaceItem::inputMethodDismissed,
44+ this, &SurfaceManager::hideInputMethod);
45+ Q_EMIT surfaceCreated(surface);
46+}
47+
48+void SurfaceManager::unregisterSurface(MirSurfaceItem *surface)
49+{
50+ disconnect(surface, 0, this, 0);
51+ Q_EMIT surfaceDestroyed(surface);
52+}
53+
54+void SurfaceManager::showInputMethod()
55+{
56+ inputMethodSurface()->setState(MirSurfaceItem::Restored);
57+}
58+
59+void SurfaceManager::hideInputMethod()
60+{
61+ if (m_virtualKeyboard) {
62+ m_virtualKeyboard->setState(MirSurfaceItem::Minimized);
63+ }
64+}
65+
66+MirSurfaceItem *SurfaceManager::inputMethodSurface()
67+{
68+ if (!m_virtualKeyboard) {
69+ m_virtualKeyboard = new VirtualKeyboard(MirSurfaceItem::Minimized);
70+ Q_EMIT surfaceCreated(m_virtualKeyboard);
71+ }
72+ return m_virtualKeyboard;
73+}
tests/mocks/Unity/Application/SurfaceManager.hnew file+54
@@ -0,0 +1,54 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef SURFACEMANAGER_H
18+#define SURFACEMANAGER_H
19+
20+#include <QObject>
21+
22+class MirSurfaceItem;
23+class VirtualKeyboard;
24+
25+class SurfaceManager : public QObject
26+{
27+ Q_OBJECT
28+public:
29+ explicit SurfaceManager(QObject *parent = 0);
30+
31+ static SurfaceManager *singleton();
32+
33+ // Internal mock use
34+ void registerSurface(MirSurfaceItem *surface);
35+ void unregisterSurface(MirSurfaceItem *surface);
36+
37+ // To be used in the tests
38+ Q_INVOKABLE MirSurfaceItem *inputMethodSurface();
39+
40+Q_SIGNALS:
41+ void countChanged();
42+ void surfaceCreated(MirSurfaceItem *surface);
43+ void surfaceDestroyed(MirSurfaceItem *surface);
44+
45+private Q_SLOTS:
46+ void showInputMethod();
47+ void hideInputMethod();
48+
49+private:
50+ static SurfaceManager *the_surface_manager;
51+ VirtualKeyboard *m_virtualKeyboard;
52+};
53+
54+#endif // SURFACEMANAGER_H
tests/mocks/Unity/Application/VirtualKeyboard.cppnew file+55
@@ -0,0 +1,55 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "VirtualKeyboard.h"
18+
19+#include <paths.h>
20+
21+VirtualKeyboard::VirtualKeyboard(State state, QQuickItem *parent)
22+ : MirSurfaceItem("input-method",
23+ MirSurfaceItem::InputMethod,
24+ state,
25+ QString("file://%1/Dash/graphics/phone/screenshots/vkb_portrait.png").arg(qmlDirectory()),
26+ parent)
27+{
28+}
29+
30+void VirtualKeyboard::touchEvent(QTouchEvent *event)
31+{
32+ if (event->type() == QEvent::TouchBegin && !hasTouchInsideKeyboard(event)) {
33+ event->ignore();
34+ }
35+}
36+
37+bool VirtualKeyboard::hasTouchInsideKeyboard(QTouchEvent *event)
38+{
39+ const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
40+ for (int i = 0; i < touchPoints.count(); ++i) {
41+ QPoint pos = touchPoints.at(i).pos().toPoint();
42+
43+ // Map to image coords
44+ int imageX = (int)( ( ((qreal)pos.x()) / width() ) * ((qreal)screenshotImage().size().width()) );
45+ int imageY = (int)( ( ((qreal)pos.y()) / height() ) * ((qreal)screenshotImage().size().height()) );
46+
47+ QRgb pixelHit = screenshotImage().pixel(imageX, imageY);
48+
49+ // The keyboard depicted in the image is in its opaque part.
50+ if (qAlpha(pixelHit) != 0) {
51+ return true;
52+ }
53+ }
54+ return false;
55+}
qml/Stages/SidestageHandle.qml → tests/mocks/Unity/Application/VirtualKeyboard.hrenamed+19−6
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2014 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -14,9 +14,22 @@
1414 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1515 */
1616
17−import QtQuick 2.0
18−import Ubuntu.Components 0.1
17+#ifndef VIRTUALKEYBOARD_H
18+#define VIRTUALKEYBOARD_H
1919
20−BorderImage {
21− source: "graphics/sidestage_handle.sci"
22−}
20+#include "MirSurfaceItem.h"
21+
22+class VirtualKeyboard : public MirSurfaceItem
23+{
24+ Q_OBJECT
25+public:
26+ VirtualKeyboard(State state,
27+ QQuickItem *parent = 0);
28+
29+ void touchEvent(QTouchEvent * event) override;
30+
31+private:
32+ bool hasTouchInsideKeyboard(QTouchEvent *event);
33+};
34+
35+#endif // VIRTUALKEYBOARD_H
tests/mocks/Unity/Application/plugin.cpp+16−9
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2014 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -19,29 +19,37 @@
1919#include "ApplicationImage.h"
2020#include "ApplicationManager.h"
2121#include "ApplicationScreenshotProvider.h"
22+#include "MirSurfaceItem.h"
23+#include "SurfaceManager.h"
2224
2325#include <qqml.h>
2426#include <QQmlEngine>
2527
26−ApplicationManager *s_appManager = 0;
27−
2828static QObject* applicationManagerSingleton(QQmlEngine* engine, QJSEngine* scriptEngine) {
2929 Q_UNUSED(engine);
3030 Q_UNUSED(scriptEngine);
31− if (!s_appManager) {
32− s_appManager = new ApplicationManager();
33− }
34− return s_appManager;
31+ return ApplicationManager::singleton();
32+}
33+
34+static QObject* surfaceManagerSingleton(QQmlEngine* engine, QJSEngine* scriptEngine) {
35+ Q_UNUSED(engine);
36+ Q_UNUSED(scriptEngine);
37+ return SurfaceManager::singleton();
3538}
3639
3740void FakeUnityApplicationQmlPlugin::registerTypes(const char *uri)
3841{
42+ qRegisterMetaType<MirSurfaceItem*>("MirSurfaceItem*");
43+
3944 qmlRegisterUncreatableType<unity::shell::application::ApplicationManagerInterface>(uri, 0, 1, "ApplicationManagerInterface", "Abstract interface. Cannot be created in QML");
4045 qmlRegisterSingletonType<ApplicationManager>(uri, 0, 1, "ApplicationManager", applicationManagerSingleton);
46+ qmlRegisterSingletonType<SurfaceManager>(uri, 0, 1, "SurfaceManager", surfaceManagerSingleton);
4147
4248 qmlRegisterUncreatableType<unity::shell::application::ApplicationInfoInterface>(uri, 0, 1, "ApplicationInfoInterface", "Abstract interface. Cannot be created in QML");
4349 qmlRegisterType<ApplicationInfo>(uri, 0, 1, "ApplicationInfo");
4450
51+ qmlRegisterUncreatableType<MirSurfaceItem>(uri, 0, 1, "MirSurfaceItem", "MirSurfaceItem can't be instantiated from QML");
52+
4553 qmlRegisterType<ApplicationImage>(uri, 0, 1, "ApplicationImage");
4654}
4755
@@ -49,6 +57,5 @@ void FakeUnityApplicationQmlPlugin::initializeEngine(QQmlEngine *engine, const c
4957{
5058 QQmlExtensionPlugin::initializeEngine(engine, uri);
5159
52− ApplicationManager* appManager = static_cast<ApplicationManager*>(applicationManagerSingleton(engine, NULL));
53− engine->addImageProvider(QLatin1String("application"), new ApplicationScreenshotProvider(appManager));
60+ engine->addImageProvider(QLatin1String("application"), new ApplicationScreenshotProvider(ApplicationManager::singleton()));
5461}
tests/mocks/Unity/CMakeLists.txt+1−1
@@ -6,7 +6,7 @@ add_subdirectory(Notifications)
66pkg_search_module(DEE dee-1.0 REQUIRED)
77pkg_search_module(GOBJECT gobject-2.0 REQUIRED)
88pkg_search_module(DEEQT libdee-qt5 REQUIRED)
9−pkg_check_modules(SCOPES_API REQUIRED unity-shell-scopes=2)
9+pkg_check_modules(SCOPES_API REQUIRED unity-shell-scopes=3)
1010
1111include_directories(
1212 ${CMAKE_CURRENT_BINARY_DIR}
tests/mocks/Unity/fake_scopes.cpp+5
@@ -125,6 +125,11 @@ bool Scopes::loaded() const
125125 return m_loaded;
126126}
127127
128+unity::shell::scopes::ScopeInterface* Scopes::overviewScope() const
129+{
130+ return nullptr;
131+}
132+
128133void Scopes::addScope(Scope* scope)
129134{
130135 int index = rowCount();
tests/mocks/Unity/fake_scopes.h+1
@@ -50,6 +50,7 @@ public:
5050 QModelIndex parent ( const QModelIndex & index ) const;
5151
5252 bool loaded() const override;
53+ unity::shell::scopes::ScopeInterface* overviewScope() const override;
5354
5455private Q_SLOTS:
5556 void updateScopes();
tests/plugins/Dash/CMakeLists.txt+1
@@ -79,3 +79,4 @@ set(qmltest_DEFAULT_NO_ADD_TEST TRUE)
7979set(qmltest_DEFAULT_PROPERTIES ENVIRONMENT "LC_ALL=C")
8080add_qml_test(. ScopeStyle IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
8181add_qml_test(. ListViewWithPageHeaderQML IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
82+add_qml_test(. CardAttributes IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
tests/plugins/Dash/cardcreator/1.res+63−61
@@ -5,8 +5,8 @@ AbstractButton {
55 property var cardData;
66 property var artShapeBorderSource: undefined;
77 property real fontScale: 1.0;
8− property var scopeStyle: null;
9− property int headerAlignment: Text.AlignLeft;
8+ property var scopeStyle: null;
9+ property int headerAlignment: Text.AlignLeft;
1010 property int fixedHeaderHeight: -1;
1111 property size fixedArtShapeSize: Qt.size(-1, -1);
1212 readonly property string title: cardData && cardData["title"] || "";
@@ -16,71 +16,73 @@ AbstractButton {
1616onArtShapeBorderSourceChanged: { if (artShapeBorderSource !== undefined && artShapeLoader.item) artShapeLoader.item.borderSource = artShapeBorderSource; }
1717readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
1818Item {
19− id: artShapeHolder;
20− height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
21− width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
22− anchors { horizontalCenter: parent.horizontalCenter; }
23− Loader {
24− id: artShapeLoader;
25− objectName: "artShapeLoader";
26− active: cardData && cardData["art"] || false;
27− asynchronous: root.asynchronous;
28− visible: status == Loader.Ready;
29− sourceComponent: UbuntuShape {
30− id: artShape;
31− objectName: "artShape";
32− radius: "medium";
33− visible: image.status == Image.Ready;
34− readonly property real fixedArtShapeSizeAspect: (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) ? root.fixedArtShapeSize.width / root.fixedArtShapeSize.height : -1;
35− readonly property real aspect: fixedArtShapeSizeAspect > 0 ? fixedArtShapeSizeAspect : components !== undefined ? components["art"]["aspect-ratio"] : 1;
36− readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect;
37− Component.onCompleted: { updateWidthHeightBindings(); if (artShapeBorderSource !== undefined) borderSource = artShapeBorderSource; }
38− onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings();
39− Connections { target: root; onFixedArtShapeSizeChanged: updateWidthHeightBindings(); }
40− function updateWidthHeightBindings() {
41− if (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) {
42− width = root.fixedArtShapeSize.width;
43− height = root.fixedArtShapeSize.height;
44− } else if (aspectSmallerThanImageAspect) {
45− width = Qt.binding(function() { return !visible ? 0 : image.width });
46− height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect });
47− } else {
48− width = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.width : height * image.aspect });
49− height = Qt.binding(function() { return !visible ? 0 : image.height });
50− }
51− }
52− image: Image {
53− objectName: "artImage";
54− source: cardData && cardData["art"] || "";
55− cache: true;
19+ id: artShapeHolder;
20+ height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
21+ width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
22+ anchors { horizontalCenter: parent.horizontalCenter; }
23+ Loader {
24+ id: artShapeLoader;
25+ objectName: "artShapeLoader";
26+ active: cardData && cardData["art"] || false;
5627 asynchronous: root.asynchronous;
57− fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop;
58− readonly property real aspect: implicitWidth / implicitHeight;
59− width: root.width;
60− height: width / artShape.aspect;
28+ visible: status == Loader.Ready;
29+ sourceComponent: UbuntuShape {
30+ id: artShape;
31+ objectName: "artShape";
32+ radius: "medium";
33+ visible: image.status == Image.Ready;
34+ readonly property real fixedArtShapeSizeAspect: (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) ? root.fixedArtShapeSize.width / root.fixedArtShapeSize.height : -1;
35+ readonly property real aspect: fixedArtShapeSizeAspect > 0 ? fixedArtShapeSizeAspect : components !== undefined ? components["art"]["aspect-ratio"] : 1;
36+ readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect;
37+ Component.onCompleted: { updateWidthHeightBindings(); if (artShapeBorderSource !== undefined) borderSource = artShapeBorderSource; }
38+ onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings();
39+ Connections { target: root; onFixedArtShapeSizeChanged: updateWidthHeightBindings(); }
40+ function updateWidthHeightBindings() {
41+ if (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) {
42+ width = root.fixedArtShapeSize.width;
43+ height = root.fixedArtShapeSize.height;
44+ } else if (aspectSmallerThanImageAspect) {
45+ width = Qt.binding(function() { return !visible ? 0 : image.width });
46+ height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect });
47+ } else {
48+ width = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.width : height * image.aspect });
49+ height = Qt.binding(function() { return !visible ? 0 : image.height });
50+ }
51+ }
52+ image: Image {
53+ objectName: "artImage";
54+ source: cardData && cardData["art"] || "";
55+ cache: true;
56+ asynchronous: root.asynchronous;
57+ fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop;
58+ readonly property real aspect: implicitWidth / implicitHeight;
59+ width: root.width;
60+ height: width / artShape.aspect;
61+ }
62+ }
6163 }
62− }
63− }
64− }
64+ }
6565readonly property int headerHeight: titleLabel.height;
6666Label {
67− id: titleLabel;
68− objectName: "titleLabel";
69− anchors { right: parent.right;left: parent.left;
67+ id: titleLabel;
68+ objectName: "titleLabel";
69+ anchors { right: parent.right;
70+rightMargin: units.gu(1);
71+left: parent.left;
7072top: artShapeHolder.bottom;
7173 topMargin: units.gu(1);
7274leftMargin: units.gu(1);
73− }
74− elide: Text.ElideRight;
75− fontSize: "small";
76− wrapMode: Text.Wrap;
77− maximumLineCount: 2;
78− font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
79− color: root.scopeStyle ? root.scopeStyle.foreground : "grey";
80− visible: showHeader ;
81− text: root.title;
82− font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
83− horizontalAlignment: root.headerAlignment;
84− }
75+ }
76+ elide: Text.ElideRight;
77+ fontSize: "small";
78+ wrapMode: Text.Wrap;
79+ maximumLineCount: 2;
80+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
81+ color: root.scopeStyle ? root.scopeStyle.foreground : "grey";
82+ visible: showHeader ;
83+ text: root.title;
84+ font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
85+ horizontalAlignment: root.headerAlignment;
86+ }
8587implicitHeight: titleLabel.y + titleLabel.height + units.gu(1);
8688}
tests/plugins/Dash/cardcreator/2.res+94−91
@@ -5,8 +5,8 @@ AbstractButton {
55 property var cardData;
66 property var artShapeBorderSource: undefined;
77 property real fontScale: 1.0;
8− property var scopeStyle: null;
9− property int headerAlignment: Text.AlignLeft;
8+ property var scopeStyle: null;
9+ property int headerAlignment: Text.AlignLeft;
1010 property int fixedHeaderHeight: -1;
1111 property size fixedArtShapeSize: Qt.size(-1, -1);
1212 readonly property string title: cardData && cardData["title"] || "";
@@ -14,104 +14,107 @@ AbstractButton {
1414 property bool showHeader: true;
1515 implicitWidth: childrenRect.width;
1616Loader {
17− id: backgroundLoader;
18− objectName: "backgroundLoader";
19− anchors.fill: parent;
20− asynchronous: root.asynchronous;
21− visible: status == Loader.Ready;
22− sourceComponent: UbuntuShape {
23− objectName: "background";
24− radius: "medium";
25− color: getColor(0) || "white";
26− gradientColor: getColor(1) || color;
27− anchors.fill: parent;
28− image: backgroundImage.source ? backgroundImage : null;
29− property real luminance: 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
30− property Image backgroundImage: Image {
31− objectName: "backgroundImage";
32− source: {
33− if (cardData && typeof cardData["background"] === "string") return cardData["background"];
34− else if (template && typeof template["card-background"] === "string") return template["card-background"];
35− else return "";
36− }
37− }
38− function getColor(index) {
39− if (cardData && typeof cardData["background"] === "object"
40− && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
41− return cardData["background"]["elements"][index];
42− } else if (template && typeof template["card-background"] === "object"
43− && (template["card-background"]["type"] === "color" || template["card-background"]["type"] === "gradient")) {
44− return template["card-background"]["elements"][index];
45− } else return undefined;
46− }
47− }
48− }
17+ id: backgroundLoader;
18+ objectName: "backgroundLoader";
19+ anchors.fill: parent;
20+ asynchronous: root.asynchronous;
21+ visible: status == Loader.Ready;
22+ sourceComponent: UbuntuShape {
23+ objectName: "background";
24+ radius: "medium";
25+ color: getColor(0) || "white";
26+ gradientColor: getColor(1) || color;
27+ anchors.fill: parent;
28+ image: backgroundImage.source ? backgroundImage : null;
29+ property real luminance: 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
30+ property Image backgroundImage: Image {
31+ objectName: "backgroundImage";
32+ source: {
33+ if (cardData && typeof cardData["background"] === "string") return cardData["background"];
34+ else if (template && typeof template["card-background"] === "string") return template["card-background"];
35+ else return "";
36+ }
37+ }
38+ function getColor(index) {
39+ if (cardData && typeof cardData["background"] === "object"
40+ && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
41+ return cardData["background"]["elements"][index];
42+ } else if (template && typeof template["card-background"] === "object"
43+ && (template["card-background"]["type"] === "color" || template["card-background"]["type"] === "gradient")) {
44+ return template["card-background"]["elements"][index];
45+ } else return undefined;
46+ }
47+ }
48+ }
4949readonly property size artShapeSize: Qt.size(-1, -1);
5050readonly property int headerHeight: row.height;
5151Row {
52− id: row;
53− objectName: "outerRow";
54− property real margins: units.gu(1);
55− spacing: margins;
56− height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight;
57− anchors { top: parent.top;
52+ id: row;
53+ objectName: "outerRow";
54+ property real margins: units.gu(1);
55+ spacing: margins;
56+ height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight;
57+ anchors { top: parent.top;
5858 topMargin: units.gu(1);
59− left: parent.left;
60−}
61− anchors.right: parent.right;
62− anchors.margins: margins;
63−data: [ Image {
64− id: mascotImage;
65− objectName: "mascotImage";
66− anchors { verticalCenter: parent.verticalCenter; }
67− readonly property int maxSize: Math.max(width, height) * 4;
68− source: cardData && cardData["mascot"];
69− width: units.gu(6);
70− height: units.gu(5.625);
71− sourceSize { width: maxSize; height: maxSize }
72− fillMode: Image.PreserveAspectCrop;
73− horizontalAlignment: Image.AlignHCenter;
74− verticalAlignment: Image.AlignVCenter;
75− visible: showHeader;
76− }
77−,
78−Column {
79− anchors.verticalCenter: parent.verticalCenter;
80− spacing: units.dp(2);
81− width: parent.width - x;
82− data: [ Label {
83− id: titleLabel;
84− objectName: "titleLabel";
85− anchors { left: parent.left; right: parent.right }
86− elide: Text.ElideRight;
87− fontSize: "small";
88− wrapMode: Text.Wrap;
89− maximumLineCount: 2;
90− font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
91− color: backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : (root.scopeStyle ? root.scopeStyle.foreground : "grey");
92− visible: showHeader ;
93− text: root.title;
94− font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
95− horizontalAlignment: root.headerAlignment;
96− }
97−,
98−Label {
99− id: subtitleLabel;
100− objectName: "subtitleLabel";
101− anchors { left: parent.left; right: parent.right }
59+left: parent.left;
60+ }
61+ anchors.right: parent.right;
62+ anchors.margins: margins;
63+ data: [ Image {
64+ id: mascotImage;
65+ objectName: "mascotImage";
66+ anchors { verticalCenter: parent.verticalCenter; }
67+ readonly property int maxSize: Math.max(width, height) * 4;
68+ source: cardData && cardData["mascot"];
69+ width: units.gu(6);
70+ height: units.gu(5.625);
71+ sourceSize { width: maxSize; height: maxSize }
72+ fillMode: Image.PreserveAspectCrop;
73+ horizontalAlignment: Image.AlignHCenter;
74+ verticalAlignment: Image.AlignVCenter;
75+ visible: showHeader;
76+ }
10277
78+ ,
79+ Column {
80+ anchors.verticalCenter: parent.verticalCenter;
81+ spacing: units.dp(2);
82+ width: parent.width - x;
83+ data: [ Label {
84+ id: titleLabel;
85+ objectName: "titleLabel";
86+ anchors { left: parent.left; right: parent.right }
10387 elide: Text.ElideRight;
10488 fontSize: "small";
89+ wrapMode: Text.Wrap;
90+ maximumLineCount: 2;
10591 font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
106− color: backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : (root.scopeStyle ? root.scopeStyle.foreground : "grey");
107− visible: titleLabel.visible && titleLabel.text;
108− text: cardData && cardData["subtitle"] || "";
109− font.weight: Font.Light;
92+ color: backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : (root.scopeStyle ? root.scopeStyle.foreground : "grey");
93+ visible: showHeader ;
94+ text: root.title;
95+ font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
11096 horizontalAlignment: root.headerAlignment;
11197 }
112−]
113−}
114−]
115−}
98+
99+ ,
100+ Label {
101+ id: subtitleLabel;
102+ objectName: "subtitleLabel";
103+ anchors { left: parent.left; right: parent.right }
104+ elide: Text.ElideRight;
105+ fontSize: "small";
106+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
107+ color: backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : (root.scopeStyle ? root.scopeStyle.foreground : "grey");
108+ visible: titleLabel.visible && titleLabel.text;
109+ text: cardData && cardData["subtitle"] || "";
110+ font.weight: Font.Light;
111+ horizontalAlignment: root.headerAlignment;
112+ }
113+
114+ ]
115+ }
116+
117+ ]
118+ }
116119implicitHeight: row.y + row.height + units.gu(1);
117120}
tests/plugins/Dash/cardcreator/3.res+77−74
@@ -5,8 +5,8 @@ AbstractButton {
55 property var cardData;
66 property var artShapeBorderSource: undefined;
77 property real fontScale: 1.0;
8− property var scopeStyle: null;
9− property int headerAlignment: Text.AlignLeft;
8+ property var scopeStyle: null;
9+ property int headerAlignment: Text.AlignLeft;
1010 property int fixedHeaderHeight: -1;
1111 property size fixedArtShapeSize: Qt.size(-1, -1);
1212 readonly property string title: cardData && cardData["title"] || "";
@@ -16,89 +16,92 @@ AbstractButton {
1616onArtShapeBorderSourceChanged: { if (artShapeBorderSource !== undefined && artShapeLoader.item) artShapeLoader.item.borderSource = artShapeBorderSource; }
1717readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
1818Item {
19− id: artShapeHolder;
20− height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
21− width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
22− anchors { horizontalCenter: parent.horizontalCenter; }
23− Loader {
24− id: artShapeLoader;
25− objectName: "artShapeLoader";
26− active: cardData && cardData["art"] || false;
27− asynchronous: root.asynchronous;
28− visible: status == Loader.Ready;
29− sourceComponent: UbuntuShape {
30− id: artShape;
31− objectName: "artShape";
32− radius: "medium";
33− visible: image.status == Image.Ready;
34− readonly property real fixedArtShapeSizeAspect: (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) ? root.fixedArtShapeSize.width / root.fixedArtShapeSize.height : -1;
35− readonly property real aspect: fixedArtShapeSizeAspect > 0 ? fixedArtShapeSizeAspect : components !== undefined ? components["art"]["aspect-ratio"] : 1;
36− readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect;
37− Component.onCompleted: { updateWidthHeightBindings(); if (artShapeBorderSource !== undefined) borderSource = artShapeBorderSource; }
38− onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings();
39− Connections { target: root; onFixedArtShapeSizeChanged: updateWidthHeightBindings(); }
40− function updateWidthHeightBindings() {
41− if (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) {
42− width = root.fixedArtShapeSize.width;
43− height = root.fixedArtShapeSize.height;
44− } else if (aspectSmallerThanImageAspect) {
45− width = Qt.binding(function() { return !visible ? 0 : image.width });
46− height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect });
47− } else {
48− width = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.width : height * image.aspect });
49− height = Qt.binding(function() { return !visible ? 0 : image.height });
50− }
51− }
52− image: Image {
53− objectName: "artImage";
54− source: cardData && cardData["art"] || "";
55− cache: true;
19+ id: artShapeHolder;
20+ height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
21+ width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
22+ anchors { horizontalCenter: parent.horizontalCenter; }
23+ Loader {
24+ id: artShapeLoader;
25+ objectName: "artShapeLoader";
26+ active: cardData && cardData["art"] || false;
5627 asynchronous: root.asynchronous;
57− fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop;
58− readonly property real aspect: implicitWidth / implicitHeight;
59− width: root.width;
60− height: width / artShape.aspect;
28+ visible: status == Loader.Ready;
29+ sourceComponent: UbuntuShape {
30+ id: artShape;
31+ objectName: "artShape";
32+ radius: "medium";
33+ visible: image.status == Image.Ready;
34+ readonly property real fixedArtShapeSizeAspect: (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) ? root.fixedArtShapeSize.width / root.fixedArtShapeSize.height : -1;
35+ readonly property real aspect: fixedArtShapeSizeAspect > 0 ? fixedArtShapeSizeAspect : components !== undefined ? components["art"]["aspect-ratio"] : 1;
36+ readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect;
37+ Component.onCompleted: { updateWidthHeightBindings(); if (artShapeBorderSource !== undefined) borderSource = artShapeBorderSource; }
38+ onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings();
39+ Connections { target: root; onFixedArtShapeSizeChanged: updateWidthHeightBindings(); }
40+ function updateWidthHeightBindings() {
41+ if (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) {
42+ width = root.fixedArtShapeSize.width;
43+ height = root.fixedArtShapeSize.height;
44+ } else if (aspectSmallerThanImageAspect) {
45+ width = Qt.binding(function() { return !visible ? 0 : image.width });
46+ height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect });
47+ } else {
48+ width = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.width : height * image.aspect });
49+ height = Qt.binding(function() { return !visible ? 0 : image.height });
50+ }
51+ }
52+ image: Image {
53+ objectName: "artImage";
54+ source: cardData && cardData["art"] || "";
55+ cache: true;
56+ asynchronous: root.asynchronous;
57+ fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop;
58+ readonly property real aspect: implicitWidth / implicitHeight;
59+ width: root.width;
60+ height: width / artShape.aspect;
61+ }
62+ }
6163 }
62− }
63− }
64− }
64+ }
6565readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;
6666Label {
67− id: titleLabel;
68− objectName: "titleLabel";
69− anchors { right: parent.right;left: parent.left;
67+ id: titleLabel;
68+ objectName: "titleLabel";
69+ anchors { right: parent.right;
70+rightMargin: units.gu(1);
71+left: parent.left;
7072top: artShapeHolder.bottom;
7173 topMargin: units.gu(1);
7274leftMargin: units.gu(1);
73−}
74− elide: Text.ElideRight;
75− fontSize: "small";
76− wrapMode: Text.Wrap;
77− maximumLineCount: 2;
78− font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
79− color: root.scopeStyle ? root.scopeStyle.foreground : "grey";
80− visible: showHeader ;
81− text: root.title;
82− font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
83− horizontalAlignment: root.headerAlignment;
84− }
85−Label {
86− id: subtitleLabel;
87− objectName: "subtitleLabel";
88− anchors { left: titleLabel.left;
89− leftMargin: titleLabel.leftMargin;
90− right: titleLabel.right;
91− top: titleLabel.bottom;
92− topMargin: units.dp(2);
93− }
75+ }
9476 elide: Text.ElideRight;
9577 fontSize: "small";
78+ wrapMode: Text.Wrap;
79+ maximumLineCount: 2;
9680 font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
97− color: root.scopeStyle ? root.scopeStyle.foreground : "grey";
98− visible: titleLabel.visible && titleLabel.text;
99− text: cardData && cardData["subtitle"] || "";
100− font.weight: Font.Light;
81+ color: root.scopeStyle ? root.scopeStyle.foreground : "grey";
82+ visible: showHeader ;
83+ text: root.title;
84+ font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
10185 horizontalAlignment: root.headerAlignment;
10286 }
87+Label {
88+ id: subtitleLabel;
89+ objectName: "subtitleLabel";
90+ anchors { left: titleLabel.left;
91+ leftMargin: titleLabel.leftMargin;
92+ right: titleLabel.right;
93+ rightMargin: titleLabel.rightMargin;
94+ top: titleLabel.bottom;
95+ topMargin: units.dp(2);
96+ }
97+ elide: Text.ElideRight;
98+ fontSize: "small";
99+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
100+ color: root.scopeStyle ? root.scopeStyle.foreground : "grey";
101+ visible: titleLabel.visible && titleLabel.text;
102+ text: cardData && cardData["subtitle"] || "";
103+ font.weight: Font.Light;
104+ horizontalAlignment: root.headerAlignment;
105+ }
103106implicitHeight: subtitleLabel.y + subtitleLabel.height + units.gu(1);
104107}
tests/plugins/Dash/cardcreator/5.res+119−117
@@ -4,9 +4,9 @@ AbstractButton {
44 property var components;
55 property var cardData;
66 property var artShapeBorderSource: undefined;
7− property real fontScale: 1.0;
8− property var scopeStyle: null;
9− property int headerAlignment: Text.AlignLeft;
7+ property real fontScale: 1.0;
8+ property var scopeStyle: null;
9+ property int headerAlignment: Text.AlignLeft;
1010 property int fixedHeaderHeight: -1;
1111 property size fixedArtShapeSize: Qt.size(-1, -1);
1212 readonly property string title: cardData && cardData["title"] || "";
@@ -16,130 +16,132 @@ AbstractButton {
1616onArtShapeBorderSourceChanged: { if (artShapeBorderSource !== undefined && artShapeLoader.item) artShapeLoader.item.borderSource = artShapeBorderSource; }
1717readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
1818Item {
19− id: artShapeHolder;
20− height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
21− width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
22− anchors { horizontalCenter: parent.horizontalCenter; }
23− Loader {
24− id: artShapeLoader;
25− objectName: "artShapeLoader";
26− active: cardData && cardData["art"] || false;
27− asynchronous: root.asynchronous;
28− visible: status == Loader.Ready;
29− sourceComponent: UbuntuShape {
30− id: artShape;
31− objectName: "artShape";
32− radius: "medium";
33− visible: image.status == Image.Ready;
34− readonly property real fixedArtShapeSizeAspect: (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) ? root.fixedArtShapeSize.width / root.fixedArtShapeSize.height : -1;
35− readonly property real aspect: fixedArtShapeSizeAspect > 0 ? fixedArtShapeSizeAspect : components !== undefined ? components["art"]["aspect-ratio"] : 1;
36− readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect;
37− Component.onCompleted: { updateWidthHeightBindings(); if (artShapeBorderSource !== undefined) borderSource = artShapeBorderSource; }
38− onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings();
39− Connections { target: root; onFixedArtShapeSizeChanged: updateWidthHeightBindings(); }
40− function updateWidthHeightBindings() {
41− if (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) {
42− width = root.fixedArtShapeSize.width;
43− height = root.fixedArtShapeSize.height;
44− } else if (aspectSmallerThanImageAspect) {
45− width = Qt.binding(function() { return !visible ? 0 : image.width });
46− height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect });
47− } else {
48− width = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.width : height * image.aspect });
49− height = Qt.binding(function() { return !visible ? 0 : image.height });
50− }
51− }
52− image: Image {
53− objectName: "artImage";
54− source: cardData && cardData["art"] || "";
55− cache: true;
19+ id: artShapeHolder;
20+ height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
21+ width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
22+ anchors { horizontalCenter: parent.horizontalCenter; }
23+ Loader {
24+ id: artShapeLoader;
25+ objectName: "artShapeLoader";
26+ active: cardData && cardData["art"] || false;
5627 asynchronous: root.asynchronous;
57− fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop;
58− readonly property real aspect: implicitWidth / implicitHeight;
59− width: root.width;
60− height: width / artShape.aspect;
28+ visible: status == Loader.Ready;
29+ sourceComponent: UbuntuShape {
30+ id: artShape;
31+ objectName: "artShape";
32+ radius: "medium";
33+ visible: image.status == Image.Ready;
34+ readonly property real fixedArtShapeSizeAspect: (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) ? root.fixedArtShapeSize.width / root.fixedArtShapeSize.height : -1;
35+ readonly property real aspect: fixedArtShapeSizeAspect > 0 ? fixedArtShapeSizeAspect : components !== undefined ? components["art"]["aspect-ratio"] : 1;
36+ readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect;
37+ Component.onCompleted: { updateWidthHeightBindings(); if (artShapeBorderSource !== undefined) borderSource = artShapeBorderSource; }
38+ onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings();
39+ Connections { target: root; onFixedArtShapeSizeChanged: updateWidthHeightBindings(); }
40+ function updateWidthHeightBindings() {
41+ if (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) {
42+ width = root.fixedArtShapeSize.width;
43+ height = root.fixedArtShapeSize.height;
44+ } else if (aspectSmallerThanImageAspect) {
45+ width = Qt.binding(function() { return !visible ? 0 : image.width });
46+ height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect });
47+ } else {
48+ width = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.width : height * image.aspect });
49+ height = Qt.binding(function() { return !visible ? 0 : image.height });
50+ }
51+ }
52+ image: Image {
53+ objectName: "artImage";
54+ source: cardData && cardData["art"] || "";
55+ cache: true;
56+ asynchronous: root.asynchronous;
57+ fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop;
58+ readonly property real aspect: implicitWidth / implicitHeight;
59+ width: root.width;
60+ height: width / artShape.aspect;
61+ }
62+ }
6163 }
62− }
63− }
64− }
64+ }
6565Loader {
66− id: overlayLoader;
67− anchors {
68− left: artShapeHolder.left;
69− right: artShapeHolder.right;
70− bottom: artShapeHolder.bottom;
71− }
72− active: artShapeLoader.active && artShapeLoader.item && artShapeLoader.item.image.status === Image.Ready || false;
73− asynchronous: root.asynchronous;
74− visible: showHeader && status == Loader.Ready;
75− sourceComponent: ShaderEffect {
76− id: overlay;
77− height: (fixedHeaderHeight > 0 ? fixedHeaderHeight : headerHeight) + units.gu(2);
78− opacity: 0.6;
79− property var source: ShaderEffectSource {
80− id: shaderSource;
81− sourceItem: artShapeLoader.item;
82− onVisibleChanged: if (visible) scheduleUpdate();
83− live: false;
84− sourceRect: Qt.rect(0, artShapeLoader.height - overlay.height, artShapeLoader.width, overlay.height);
85− }
86− vertexShader: "
87− uniform highp mat4 qt_Matrix;
88− attribute highp vec4 qt_Vertex;
89− attribute highp vec2 qt_MultiTexCoord0;
90− varying highp vec2 coord;
91− void main() {
92− coord = qt_MultiTexCoord0;
93− gl_Position = qt_Matrix * qt_Vertex;
94− }";
95− fragmentShader: "
96− varying highp vec2 coord;
97− uniform sampler2D source;
98− uniform lowp float qt_Opacity;
99− void main() {
100− lowp vec4 tex = texture2D(source, coord);
101− gl_FragColor = vec4(0, 0, 0, tex.a) * qt_Opacity;
102− }";
103− }
104− }
66+ id: overlayLoader;
67+ anchors {
68+ left: artShapeHolder.left;
69+ right: artShapeHolder.right;
70+ bottom: artShapeHolder.bottom;
71+ }
72+ active: artShapeLoader.active && artShapeLoader.item && artShapeLoader.item.image.status === Image.Ready || false;
73+ asynchronous: root.asynchronous;
74+ visible: showHeader && status == Loader.Ready;
75+ sourceComponent: ShaderEffect {
76+ id: overlay;
77+ height: (fixedHeaderHeight > 0 ? fixedHeaderHeight : headerHeight) + units.gu(2);
78+ opacity: 0.6;
79+ property var source: ShaderEffectSource {
80+ id: shaderSource;
81+ sourceItem: artShapeLoader.item;
82+ onVisibleChanged: if (visible) scheduleUpdate();
83+ live: false;
84+ sourceRect: Qt.rect(0, artShapeLoader.height - overlay.height, artShapeLoader.width, overlay.height);
85+ }
86+ vertexShader: "
87+ uniform highp mat4 qt_Matrix;
88+ attribute highp vec4 qt_Vertex;
89+ attribute highp vec2 qt_MultiTexCoord0;
90+ varying highp vec2 coord;
91+ void main() {
92+ coord = qt_MultiTexCoord0;
93+ gl_Position = qt_Matrix * qt_Vertex;
94+ }";
95+ fragmentShader: "
96+ varying highp vec2 coord;
97+ uniform sampler2D source;
98+ uniform lowp float qt_Opacity;
99+ void main() {
100+ lowp vec4 tex = texture2D(source, coord);
101+ gl_FragColor = vec4(0, 0, 0, tex.a) * qt_Opacity;
102+ }";
103+ }
104+ }
105105readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;
106106Label {
107− id: titleLabel;
108− objectName: "titleLabel";
109− anchors { left: parent.left;
110− leftMargin: units.gu(1);
111− right: parent.right;
112− top: overlayLoader.top;
113− topMargin: units.gu(1);
114−}
115− elide: Text.ElideRight;
116− fontSize: "small";
117− wrapMode: Text.Wrap;
118− maximumLineCount: 2;
119− font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
120− color: "white";
121− visible: showHeader && overlayLoader.active;
122− text: root.title;
123− font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
124− horizontalAlignment: root.headerAlignment;
125− }
126−Label {
127− id: subtitleLabel;
128− objectName: "subtitleLabel";
129− anchors { left: titleLabel.left;
130− leftMargin: titleLabel.leftMargin;
131− right: titleLabel.right;
132− top: titleLabel.bottom;
133− topMargin: units.dp(2);
134− }
107+ id: titleLabel;
108+ objectName: "titleLabel";
109+ anchors { left: parent.left;
110+ leftMargin: units.gu(1);
111+ right: parent.right;
112+ rightMargin: units.gu(1);
113+ top: overlayLoader.top;
114+ topMargin: units.gu(1);
115+ }
135116 elide: Text.ElideRight;
136117 fontSize: "small";
118+ wrapMode: Text.Wrap;
119+ maximumLineCount: 2;
137120 font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
138121 color: "white";
139− visible: titleLabel.visible && titleLabel.text;
140− text: cardData && cardData["subtitle"] || "";
141− font.weight: Font.Light;
122+ visible: showHeader && overlayLoader.active;
123+ text: root.title;
124+ font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
142125 horizontalAlignment: root.headerAlignment;
143126 }
127+Label {
128+ id: subtitleLabel;
129+ objectName: "subtitleLabel";
130+ anchors { left: titleLabel.left;
131+ leftMargin: titleLabel.leftMargin;
132+ right: titleLabel.right;
133+ rightMargin: titleLabel.rightMargin;
134+ top: titleLabel.bottom;
135+ topMargin: units.dp(2);
136+ }
137+ elide: Text.ElideRight;
138+ fontSize: "small";
139+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
140+ color: "white";
141+ visible: titleLabel.visible && titleLabel.text;
142+ text: cardData && cardData["subtitle"] || "";
143+ font.weight: Font.Light;
144+ horizontalAlignment: root.headerAlignment;
145+ }
144146implicitHeight: subtitleLabel.y + subtitleLabel.height + units.gu(1);
145147}
tests/plugins/Dash/cardcreator/7.resnew file+128
@@ -0,0 +1,128 @@
1+AbstractButton {
2+ id: root;
3+ property var template;
4+ property var components;
5+ property var cardData;
6+ property var artShapeBorderSource: undefined;
7+ property real fontScale: 1.0;
8+ property var scopeStyle: null;
9+ property int headerAlignment: Text.AlignLeft;
10+ property int fixedHeaderHeight: -1;
11+ property size fixedArtShapeSize: Qt.size(-1, -1);
12+ readonly property string title: cardData && cardData["title"] || "";
13+ property bool asynchronous: true;
14+ property bool showHeader: true;
15+ implicitWidth: childrenRect.width;
16+Loader {
17+ id: backgroundLoader;
18+ objectName: "backgroundLoader";
19+ anchors.fill: parent;
20+ asynchronous: root.asynchronous;
21+ visible: status == Loader.Ready;
22+ sourceComponent: UbuntuShape {
23+ objectName: "background";
24+ radius: "medium";
25+ color: getColor(0) || "white";
26+ gradientColor: getColor(1) || color;
27+ anchors.fill: parent;
28+ image: backgroundImage.source ? backgroundImage : null;
29+ property real luminance: 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
30+ property Image backgroundImage: Image {
31+ objectName: "backgroundImage";
32+ source: {
33+ if (cardData && typeof cardData["background"] === "string") return cardData["background"];
34+ else if (template && typeof template["card-background"] === "string") return template["card-background"];
35+ else return "";
36+ }
37+ }
38+ function getColor(index) {
39+ if (cardData && typeof cardData["background"] === "object"
40+ && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
41+ return cardData["background"]["elements"][index];
42+ } else if (template && typeof template["card-background"] === "object"
43+ && (template["card-background"]["type"] === "color" || template["card-background"]["type"] === "gradient")) {
44+ return template["card-background"]["elements"][index];
45+ } else return undefined;
46+ }
47+ }
48+ }
49+readonly property size artShapeSize: Qt.size(-1, -1);
50+readonly property int headerHeight: row.height;
51+Row {
52+ id: row;
53+ objectName: "outerRow";
54+ property real margins: units.gu(1);
55+ spacing: margins;
56+ height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight;
57+ anchors { top: parent.top;
58+ topMargin: units.gu(1);
59+left: parent.left;
60+ }
61+ anchors.right: parent.right;
62+ anchors.margins: margins;
63+ data: [ Image {
64+ id: mascotImage;
65+ objectName: "mascotImage";
66+ anchors { verticalCenter: parent.verticalCenter; }
67+ readonly property int maxSize: Math.max(width, height) * 4;
68+ source: cardData && cardData["mascot"];
69+ width: units.gu(6);
70+ height: units.gu(5.625);
71+ sourceSize { width: maxSize; height: maxSize }
72+ fillMode: Image.PreserveAspectCrop;
73+ horizontalAlignment: Image.AlignHCenter;
74+ verticalAlignment: Image.AlignVCenter;
75+ visible: showHeader;
76+ }
77+
78+ ,
79+ Column {
80+ anchors.verticalCenter: parent.verticalCenter;
81+ spacing: units.dp(2);
82+ width: parent.width - x;
83+ data: [
84+ Label {
85+ id: titleLabel;
86+ objectName: "titleLabel";
87+ anchors { left: parent.left; right: parent.right }
88+ elide: Text.ElideRight;
89+ fontSize: "small";
90+ wrapMode: Text.Wrap;
91+ maximumLineCount: 2;
92+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
93+ color: backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : (root.scopeStyle ? root.scopeStyle.foreground : "grey");
94+ visible: showHeader ;
95+ text: root.title;
96+ font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
97+ horizontalAlignment: root.headerAlignment;
98+ }
99+,
100+Label {
101+ id: subtitleLabel;
102+ objectName: "subtitleLabel";
103+ anchors { left: parent.left; right: parent.right }
104+ elide: Text.ElideRight;
105+ fontSize: "small";
106+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
107+ color: backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : (root.scopeStyle ? root.scopeStyle.foreground : "grey");
108+ visible: titleLabel.visible && titleLabel.text;
109+ text: cardData && cardData["subtitle"] || "";
110+ font.weight: Font.Light;
111+ horizontalAlignment: root.headerAlignment;
112+ }
113+,
114+CardAttributes {
115+ id: attributesRow;
116+ objectName: "attributesRow";
117+ anchors { left: parent.left; right: parent.right }
118+ color: backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : (root.scopeStyle ? root.scopeStyle.foreground : "grey");
119+ model: cardData["attributes"]
120+ }
121+
122+ ]
123+ }
124+
125+ ]
126+ }
127+implicitHeight: row.y + row.height + units.gu(1);
128+}
tests/plugins/Dash/cardcreator/7.tstnew file+3
@@ -0,0 +1,3 @@
1+template: {"card-background":{"elements":["#E9E9E9"],"type":"color"},"card-layout":"vertical","card-size":"medium","category-layout":"grid","collapsed-rows":2}
2+components: {"art":{"aspect-ratio":1,"fill-mode":"crop"},"background":{"field":"background"},"mascot":{"field":"icon"},"subtitle":{"field":"author"},"title":{"field":"title"},"attributes":{"field":"attributes","max-count":2}}
3+result: 7.res
tests/plugins/Dash/cardcreatortest.cpp+9−1
@@ -19,6 +19,7 @@
1919#include <QQuickItem>
2020#include <QQuickView>
2121#include <QtTestGui>
22+#include <QTemporaryFile>
2223
2324class CardCreatorTest : public QObject
2425{
@@ -74,7 +75,14 @@ private Q_SLOTS:
7475 QVERIFY(testResultFile.open(QIODevice::ReadOnly));
7576 QTextStream ts2(&testResultFile);
7677 const QString expectedResult = ts2.readAll();
77− QCOMPARE(cardStringResult.toString().simplified(), expectedResult.simplified());
78+ const QString executedResult = cardStringResult.toString();
79+ // Record failed results to /tmp
80+ QTemporaryFile tmpFile(QDir::tempPath() + QDir::separator() + "testCardCreatorFailedResultXXXXXX");
81+ tmpFile.open();
82+ tmpFile.setAutoRemove(false);
83+ tmpFile.write(executedResult.toUtf8().constData());
84+ QCOMPARE(executedResult.simplified(), expectedResult.simplified());
85+ tmpFile.setAutoRemove(true); // Remove the result if it passed
7886
7987 QVariant createCardComponentResult;
8088 QMetaObject::invokeMethod(view->rootObject(), "createCardComponent", Q_RETURN_ARG(QVariant, createCardComponentResult), Q_ARG(QVariant, templateJSON), Q_ARG(QVariant, componentsJSON));
tests/plugins/Dash/listviewwithpageheadersectiontest.cpp−1
@@ -40,7 +40,6 @@ private:
4040 QTRY_COMPARE(lvwph->m_visibleItems[visibleIndex]->height(), height);
4141 QCOMPARE(QQuickItemPrivate::get(lvwph->m_visibleItems[visibleIndex]->m_item)->culled, culled);
4242 QCOMPARE(section(lvwph->m_visibleItems[visibleIndex]->sectionItem()), sectionHeader);
43− QTRY_COMPARE(hasSectionHeaderProperty(lvwph->m_visibleItems[visibleIndex]->m_item), !sectionHeader.isNull());
4443 if (!sectionHeader.isNull()) {
4544 QCOMPARE(QQuickItemPrivate::get(lvwph->m_visibleItems[visibleIndex]->sectionItem())->culled, sectionHeaderCulled);
4645 QCOMPARE(sectionDelegateIndex(lvwph->m_visibleItems[visibleIndex]->sectionItem()), lvwph->m_firstVisibleIndex + visibleIndex);
tests/plugins/Dash/tst_CardAttributes.qmlnew file+54
@@ -0,0 +1,54 @@
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 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 Dash 0.1
20+
21+Item {
22+ width: units.gu(40)
23+ height: units.gu(4.5)
24+
25+ property var testData: [
26+ [{"value":"text1","icon":"image://theme/ok"},{"value":"text2","icon":"image://theme/cancel"}],
27+ [{"value":"text1","icon":"image://theme/ok"},{"value":"text2","icon":"image://theme/cancel"},{"value":"text3"}],
28+ [{"value":"text1","icon":"image://theme/ok"},{"value":"text2","icon":"image://theme/cancel"},{"value":"text3"},{"value":"text4"}],
29+ [{"value":"text1","icon":"image://theme/ok"},{"value":"text2","icon":"image://theme/cancel"},{"value":"text3","style":"highlighted"},{"value":"text4","icon":"image://theme/close","style":"highlighted"},{"value":"text5"}]
30+ ]
31+
32+ CardAttributes {
33+ id: cardAttributes
34+ model: testData[3]
35+ clip: true
36+ }
37+
38+ TestCase {
39+ name: "CardAttributesTest"
40+ when: windowShown
41+
42+ function init() {
43+ }
44+
45+ function test_columns_data() {
46+ return testData;
47+ }
48+
49+ function test_columns(data) {
50+ cardAttributes.model = data;
51+ compare(cardAttributes.columns, 2 + data.length % 2);
52+ }
53+ }
54+}
tests/qmltests/CMakeLists.txt−1
@@ -40,7 +40,6 @@ add_qml_test(Dash Card)
4040add_qml_benchmark(Dash CardBenchmark 1000)
4141add_qml_test(Dash CardTool)
4242add_qml_test(Dash GenericScopeView)
43−add_qml_test(Dash/Apps RunningApplicationsGrid)
4443add_qml_test(Dash/Previews Preview)
4544add_qml_test(Dash/Previews PreviewActions)
4645add_qml_test(Dash/Previews PreviewAudioPlayback)
tests/qmltests/Components/tst_PageHeader.qml+36−6
@@ -36,6 +36,16 @@ Item {
3636
3737 function init() {
3838 searchEnabled = true;
39+
40+ // Reset to initial state
41+ pageHeader.searchQuery = "";
42+ pageHeader.closePopup();
43+ pageHeader.searchHistory.clear();
44+
45+ // Check initial state
46+ var headerContainer = findChild(pageHeader, "headerContainer");
47+ tryCompareFunction(function() { return headerContainer.popover === null; }, true);
48+ compare(pageHeader.searchHistory.count, 0);
3949 }
4050
4151 function test_search_disabled() {
@@ -80,9 +90,6 @@ Item {
8090 }
8191
8292 function test_history() {
83− pageHeader.searchHistory.clear()
84− compare(pageHeader.searchHistory.count, 0)
85−
8693 pageHeader.triggerSearch()
8794 typeString("humppa1")
8895 pageHeader.resetSearch()
@@ -166,16 +173,26 @@ Item {
166173 tryCompareFunction(function() { return headerContainer.popover !== null; }, true);
167174
168175 tryCompare(headerContainer.popover, "visible", true);
176+
177+ var searchTextField = findChild(pageHeader, "searchTextField");
178+ compare(searchTextField.focus, true);
179+
180+ var recentSearches = findChild(headerContainer.popover, "recentSearches");
181+ mouseClick(recentSearches.itemAt(0), 0, 0);
182+
183+ compare(pageHeader.searchQuery, "Search2");
184+ tryCompareFunction(function() { return headerContainer.popover === null; }, true);
185+ compare(searchTextField.focus, false);
169186 }
170187
171− function test_tap_outside_closes_popup_data() {
188+ function test_popup_closing_data() {
172189 return [
173190 { tag: "with search text", searchText: "foobar", hideSearch: false },
174191 { tag: "without search text", searchText: "", hideSearch: true }
175192 ];
176193 }
177194
178− function test_tap_outside_closes_popup(data) {
195+ function test_popup_closing(data) {
179196 searchEnabled = true;
180197 pageHeader.searchHistory.clear();
181198
@@ -191,13 +208,26 @@ Item {
191208
192209 pageHeader.searchQuery = data.searchText;
193210
194− mouseClick(root, root.width / 2, root.height - 1);
211+ if (data.searchText == "") {
212+ // When the text is empty the user can also close the
213+ // popup by clicking outside the header instead of by starting a search
214+ mouseClick(root, root.width / 2, root.height - 1);
215+ }
195216
196217 tryCompare(headerContainer, "showSearch", !data.hideSearch);
197218 tryCompareFunction(function() { return headerContainer.popover === null; }, true);
198219
199220 pageHeader.resetSearch();
200221 }
222+
223+ function test_search_change_shows_search() {
224+ var headerContainer = findChild(pageHeader, "headerContainer");
225+ compare(headerContainer.showSearch, false);
226+ compare(searchQuery, "");
227+
228+ searchQuery = "H";
229+ compare(headerContainer.showSearch, true);
230+ }
201231 }
202232
203233 Column {
tests/qmltests/Dash/Apps/tst_RunningApplicationsGrid.qmldeleted−196
@@ -1,196 +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 General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import QtTest 1.0
19−import "../../../../qml/Dash/Apps"
20−import Unity.Test 0.1 as UT
21−import Unity.Application 0.1
22−
23−// Using Rectangle to have an opaque surface because AppManager paints app surfaces behind it.
24−Rectangle {
25− width: units.gu(50)
26− height: units.gu(40)
27−
28− function resetRunningApplications() {
29− while (ApplicationManager.count > 0) {
30− ApplicationManager.stopApplication(ApplicationManager.get(0).appId)
31− }
32−
33− ApplicationManager.startApplication("dialer-app");
34− ApplicationManager.startApplication("webbrowser-app");
35− }
36−
37− Component.onCompleted: {
38− resetRunningApplications()
39− }
40−
41− // The component under test
42− RunningApplicationsGrid {
43− id: runningApplicationsGrid
44− anchors.fill: parent
45− model: ApplicationManager
46− }
47−
48− UT.UnityTestCase {
49− name: "RunningApplicationsGrid"
50− when: windowShown
51−
52− function init() {
53− runningApplicationsGrid.terminationModeEnabled = false
54− resetRunningApplications()
55− }
56−
57− property var browserTile
58− property var phoneTile
59−
60− property var isBrowserLongPressed: false
61− function onBrowserLongPressed() {isBrowserLongPressed = true}
62−
63− property var isPhoneLongPressed: false
64− function onPhoneLongPressed() {isPhoneLongPressed = true}
65−
66− // Tiles should go to termination mode when any one of them is long-pressed.
67− // Long-pressing when they're in termination mode brings them back to activation mode
68− function test_enterTerminationMode() {
69− browserTile = findChild(runningApplicationsGrid, "runningAppTile Browser")
70− verify(browserTile != undefined)
71− browserTile.onPressAndHold.connect(onBrowserLongPressed)
72−
73− phoneTile = findChild(runningApplicationsGrid, "runningAppTile Dialer")
74− verify(phoneTile != undefined)
75− phoneTile.onPressAndHold.connect(onPhoneLongPressed)
76−
77− compare(browserTile.terminationModeEnabled, false)
78− compare(phoneTile.terminationModeEnabled, false)
79− compare(runningApplicationsGrid.terminationModeEnabled, false)
80−
81− isBrowserLongPressed = false
82− mousePress(browserTile, browserTile.width/2, browserTile.height/2)
83− tryCompareFunction(checkSwitchToTerminationModeAfterLongPress, true)
84−
85− mouseRelease(browserTile, browserTile.width/2, browserTile.height/2)
86−
87− compare(browserTile.terminationModeEnabled, true)
88− compare(phoneTile.terminationModeEnabled, true)
89− compare(runningApplicationsGrid.terminationModeEnabled, true)
90−
91− isPhoneLongPressed = false
92− mousePress(phoneTile, phoneTile.width/2, phoneTile.height/2)
93− tryCompareFunction(checkSwitchToActivationModeAfterLongPress, true)
94−
95− mouseRelease(phoneTile, phoneTile.width/2, phoneTile.height/2)
96−
97− compare(browserTile.terminationModeEnabled, false)
98− compare(phoneTile.terminationModeEnabled, false)
99− compare(runningApplicationsGrid.terminationModeEnabled, false)
100−
101− browserTile.onPressAndHold.disconnect(onBrowserLongPressed)
102− phoneTile.onPressAndHold.disconnect(onPhoneLongPressed)
103− }
104−
105− // Checks that components swicth to termination mode after (and only after) a long
106− // press happens on Browser tile.
107− function checkSwitchToTerminationModeAfterLongPress() {
108− compare(browserTile.terminationModeEnabled, isBrowserLongPressed)
109− compare(phoneTile.terminationModeEnabled, isBrowserLongPressed)
110− compare(runningApplicationsGrid.terminationModeEnabled, isBrowserLongPressed)
111−
112− return isBrowserLongPressed &&
113− browserTile.terminationModeEnabled &&
114− phoneTile.terminationModeEnabled &&
115− runningApplicationsGrid.terminationModeEnabled
116− }
117−
118− // Checks that components swicth to activation mode after (and only after) a long
119− // press happens on Phone tile.
120− function checkSwitchToActivationModeAfterLongPress() {
121− compare(browserTile.terminationModeEnabled, !isPhoneLongPressed)
122− compare(phoneTile.terminationModeEnabled, !isPhoneLongPressed)
123− compare(runningApplicationsGrid.terminationModeEnabled, !isPhoneLongPressed)
124−
125− return isPhoneLongPressed &&
126− !browserTile.terminationModeEnabled &&
127− !phoneTile.terminationModeEnabled &&
128− !runningApplicationsGrid.terminationModeEnabled
129− }
130−
131− // While on termination mode, clicking a running application tile, outside of
132− // the close icon should do nothing
133− function test_clickTileNotClose() {
134− runningApplicationsGrid.terminationModeEnabled = true
135−
136− var browserTile = findChild(runningApplicationsGrid, "runningAppTile Browser")
137− verify(browserTile != undefined)
138−
139− verify(ApplicationManager.findApplication("webbrowser-app") !== null)
140−
141− mouseClick(browserTile, browserTile.width/2, browserTile.height/2)
142−
143− verify(ApplicationManager.findApplication("webbrowser-app") !== null)
144−
145− // The tile for the Browser app should stay there
146− tryCompareFunction(checkBrowserTileExists, true)
147− }
148−
149− // While in termination mode, clicking on a running application tile's close icon
150− // causes the corresponding application to be terminated
151− function test_clickCloseIconToTerminateApp() {
152− runningApplicationsGrid.terminationModeEnabled = true
153−
154− var browserTile = findChild(runningApplicationsGrid, "runningAppTile Browser")
155− var browserTileCloseButton = findChild(runningApplicationsGrid, "closeIcon Browser")
156−
157− verify(browserTile != undefined)
158− verify(browserTileCloseButton != undefined)
159− verify(ApplicationManager.findApplication("webbrowser-app") !== 0)
160−
161− mouseClick(browserTileCloseButton, browserTileCloseButton.width/2, browserTileCloseButton.height/2)
162− wait(0) // spin event loop to start any pending animation
163−
164− verify(ApplicationManager.findApplication("webbrowser-app") === null)
165−
166− // The tile for the Browser app should eventually vanish since the
167− // application has been terminated.
168− tryCompareFunction(checkBrowserTileExists, false)
169− }
170−
171− function checkBrowserTileExists() {
172− return findChild(runningApplicationsGrid, "runningAppTile Browser")
173− != undefined
174− }
175−
176− // While in termination mode, if you click outside any of the tiles, the
177− // termination mode is disabled (i.e. we switch back to activation mode).
178− function test_clickOutsideTilesDisablesTerminationMode() {
179− runningApplicationsGrid.terminationModeEnabled = true
180−
181− var browserTile = findChild(runningApplicationsGrid, "runningAppTile Browser")
182− verify(browserTile != undefined)
183−
184− verify(runningApplicationsGrid.terminationModeEnabled);
185−
186− // Click on the bottom right corner of the grid, where there's no
187− // RunningApplicationTile lying around
188− mouseClick(runningApplicationsGrid,
189− runningApplicationsGrid.width - 1, runningApplicationsGrid.height - 1);
190−
191− wait(0) // spin event loop to ensure that any pending signal emission went through
192−
193− verify(!runningApplicationsGrid.terminationModeEnabled);
194− }
195− }
196−}
tests/qmltests/Dash/CardHelpers.js+5−3
@@ -16,7 +16,7 @@
1616
1717.pragma library
1818
19−var components = ["title", "art", "subtitle", "mascot", "emblem", "old-price", "price", "alt-price", "rating", "alt-rating", "summary"]
19+var components = ["title", "art", "subtitle", "mascot", "emblem", "old-price", "price", "alt-price", "rating", "alt-rating", "summary", "attributes"]
2020
2121var defaultLayout = ' \
2222{ \
@@ -48,7 +48,8 @@ var defaultLayout = ' \
4848 "empty": "image://theme/rating-star-empty" \
4949 }, \
5050 "alt-rating": null, \
51− "summary": null \
51+ "summary": null, \
52+ "attributes": { "max-count": 2 } \
5253 }, \
5354 "resources": {} \
5455}'
@@ -59,7 +60,8 @@ var fullMapping = ' \
5960 "art": "art", \
6061 "subtitle": "subtitle", \
6162 "mascot": "mascot", \
62− "summary": "summary" \
63+ "summary": "summary", \
64+ "attributes": "attributes" \
6365}'
6466
6567
tests/qmltests/Dash/tst_Card.qml+4−2
@@ -33,7 +33,8 @@ Rectangle {
3333 "mascot": "../../../tests/qmltests/Dash/artwork/avatar.png",
3434 "title": "foo",
3535 "subtitle": "bar",
36− "summary": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
36+ "summary": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
37+ "attributes": [{"value":"text1","icon":"image://theme/ok"},{"value":"text2","icon":"image://theme/cancel"}]
3738 }'
3839
3940 property var cardsModel: [
@@ -110,6 +111,7 @@ Rectangle {
110111 anchors { top: parent.top; left: parent.left; margins: units.gu(1) }
111112
112113 sourceComponent: cardTool.cardComponent
114+ clip: true
113115 onLoaded: {
114116 item.template = Qt.binding(function() { return cardTool.template; });
115117 item.components = Qt.binding(function() { return cardTool.components; });
@@ -359,7 +361,7 @@ Rectangle {
359361
360362 waitForRendering(card);
361363
362− tryCompareFunction(function() { return testCase.summary.y === data.top() }, true);
364+ tryCompareFunction(function() { return art.height > 0 && testCase.summary.y === data.top() }, true);
363365 }
364366
365367 function test_art_visibility() {
tests/qmltests/Greeter/tst_Lockscreen.qml+11−5
@@ -18,8 +18,9 @@ import QtQuick 2.0
1818import QtTest 1.0
1919import ".."
2020import "../../../qml/Components"
21−import Ubuntu.Components 0.1
21+import AccountsService 0.1
2222import LightDM 0.1 as LightDM
23+import Ubuntu.Components 0.1
2324import Unity.Test 0.1 as UT
2425
2526Rectangle {
@@ -53,11 +54,16 @@ Rectangle {
5354 }
5455 }
5556
57+ function setUser(username) {
58+ AccountsService.user = username
59+ LightDM.Greeter.authenticate(username)
60+ }
61+
5662 Connections {
5763 target: LightDM.Greeter
5864
5965 onShowPrompt: {
60− if (text === "PIN") {
66+ if (AccountsService.passwordDisplayHint === AccountsService.Numeric) {
6167 pinPadCheckBox.checked = false
6268 } else {
6369 pinPadCheckBox.checked = true
@@ -139,12 +145,12 @@ Rectangle {
139145 Button {
140146 text: "start auth (1234)"
141147 width: parent.width
142− onClicked: LightDM.Greeter.authenticate("has-pin")
148+ onClicked: setUser("has-pin")
143149 }
144150 Button {
145151 text: "start auth (password)"
146152 width: parent.width
147− onClicked: LightDM.Greeter.authenticate("has-password")
153+ onClicked: setUser("has-password")
148154 }
149155
150156 TextField {
@@ -255,7 +261,7 @@ Rectangle {
255261 enteredLabel.text = ""
256262 minPinLengthTextField.text = data.minPinLength
257263 maxPinLengthTextField.text = data.maxPinLength
258− LightDM.Greeter.authenticate(data.username)
264+ setUser(data.username)
259265 waitForLockscreenReady();
260266
261267 var inputField = findChild(lockscreen, "pinentryField")
tests/qmltests/Launcher/tst_Launcher.qml+11−2
@@ -94,9 +94,16 @@ Item {
9494 name: "Launcher"
9595 when: windowShown && initTestCase.completed
9696
97+ function cleanup() {
98+ mouseClick(root, root.width / 2, root.height / 2);
99+ revealer.waitUntilLauncherDisappears();
100+ }
101+
97102 // Drag from the left edge of the screen rightwards and check that the launcher
98103 // appears (as if being dragged by the finger/pointer)
99104 function test_dragLeftEdgeToRevealLauncherAndTapCenterToDismiss() {
105+ revealer.waitUntilLauncherDisappears();
106+
100107 var panel = findChild(launcher, "launcherPanel")
101108 verify(panel != undefined)
102109
@@ -117,10 +124,9 @@ Item {
117124 corresponding desktop file. E.g. clicking on phone icon should yield
118125 launcherApplicationSelected("[...]dialer-app.desktop") */
119126 function test_clickingOnAppIconCausesSignalEmission() {
127+ revealer.dragLauncherIntoView();
120128 launcher.lastSelectedApplication = ""
121129
122− revealer.dragLauncherIntoView()
123−
124130 var listView = findChild(launcher, "launcherListView");
125131 listView.positionViewAtEnd();
126132
@@ -184,6 +190,7 @@ Item {
184190 }
185191
186192 function test_countEmblems() {
193+ revealer.dragLauncherIntoView();
187194 var launcherListView = findChild(launcher, "launcherListView");
188195 for (var i = 0; i < launcherListView.count; ++i) {
189196 var delegate = findChild(launcherListView, "launcherDelegate" + i)
@@ -192,6 +199,7 @@ Item {
192199 }
193200
194201 function test_progressOverlays() {
202+ revealer.dragLauncherIntoView();
195203 var launcherListView = findChild(launcher, "launcherListView");
196204 for (var i = 0; i < launcherListView.count; ++i) {
197205 var delegate = findChild(launcherListView, "launcherDelegate" + i)
@@ -200,6 +208,7 @@ Item {
200208 }
201209
202210 function test_focusedHighlight() {
211+ revealer.dragLauncherIntoView();
203212 var launcherListView = findChild(launcher, "launcherListView");
204213 for (var i = 0; i < launcherListView.count; ++i) {
205214 var delegate = findChild(launcherListView, "launcherDelegate" + i)
tests/qmltests/Panel/tst_ActiveCallHint.qml+1
@@ -95,6 +95,7 @@ Item {
9595 function test_activeHint(data) {
9696 if (data.dialer) {
9797 var application = ApplicationManager.startApplication("dialer-app");
98+ ApplicationManager.focusApplication("dialer-app");
9899 tryCompare(ApplicationManager, "focusedApplicationId", "dialer-app");
99100 tryCompare(application, "state", ApplicationInfo.Running);
100101
tests/qmltests/Stages/tst_PhoneStage.qml+9−56
@@ -33,7 +33,6 @@ Item {
3333 PhoneStage {
3434 id: phoneStage
3535 anchors { fill: parent; rightMargin: units.gu(30) }
36− shown: true
3736 dragAreaWidth: units.gu(2)
3837 }
3938
@@ -45,7 +44,6 @@ Item {
4544
4645 Rectangle {
4746 anchors { fill: parent; leftMargin: phoneStage.width }
48−// color: "blue"
4947
5048 Column {
5149 anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
@@ -59,7 +57,10 @@ Item {
5957 }
6058 Button {
6159 anchors { left: parent.left; right: parent.right }
62− text: "Add App"
60+ text: "Remove App"
61+ onClicked: {
62+ ApplicationManager.stopApplication(ApplicationManager.get(0).appId)
63+ }
6364 }
6465 }
6566 }
@@ -74,11 +75,8 @@ Item {
7475 for (var i = 0; i < count; i++) {
7576 var app = ApplicationManager.startApplication(ApplicationManager.availableApplications()[ApplicationManager.count])
7677 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)
78+ var spreadView = findChild(phoneStage, "spreadView");
79+ tryCompare(spreadView, "contentX", -spreadView.shift);
8280 waitForRendering(phoneStage)
8381 }
8482 }
@@ -114,8 +112,6 @@ Item {
114112 true /* beginTouch */, true /* endTouch */, units.gu(10), 50);
115113
116114 tryCompare(ApplicationManager, "focusedApplicationId", activeApp.appId)
117−
118− tryCompare(phoneStage, "painting", false);
119115 }
120116
121117 function test_enterSpread_data() {
@@ -139,7 +135,7 @@ Item {
139135 var startX = phoneStage.width;
140136 var startY = phoneStage.height / 2;
141137 var endY = startY;
142− var endX = spreadView.width - (spreadView.width * spreadView[data.positionMarker]) - data.offset - phoneStage.dragAreaWidth;
138+ var endX = spreadView.width - (spreadView.width * spreadView[data.positionMarker]) - data.offset;
143139
144140 var oldFocusedApp = ApplicationManager.get(0);
145141 var newFocusedApp = ApplicationManager.get(data.newFocusedIndex);
@@ -165,7 +161,6 @@ Item {
165161 mouseClick(app2, units.gu(1), units.gu(1));
166162 }
167163
168− tryCompare(phoneStage, "painting", false);
169164 tryCompare(ApplicationManager, "focusedApplicationId", newFocusedApp.appId);
170165 }
171166
@@ -212,16 +207,6 @@ Item {
212207 tryCompare(spreadView, "phase", 0);
213208 }
214209
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−
225210 function test_select_data() {
226211 return [
227212 { tag: "0", index: 0 },
@@ -240,41 +225,9 @@ Item {
240225
241226 phoneStage.select(selectedApp.appId);
242227
243− tryCompare(phoneStage, "painting", false);
244− compare(ApplicationManager.focusedApplicationId, selectedApp.appId);
245− }
228+ tryCompare(spreadView, "contentX", -spreadView.shift);
246229
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);
230+ compare(ApplicationManager.focusedApplicationId, selectedApp.appId);
278231 }
279232
280233 function cleanup() {
tests/qmltests/tst_Shell.qml+109−12
@@ -236,6 +236,7 @@ Item {
236236
237237 // wait until the animation has finished
238238 tryCompare(greeter, "showProgress", 0);
239+ waitForRendering(greeter);
239240 }
240241
241242 /*
@@ -312,9 +313,9 @@ Item {
312313 - perform long left edge swipe to go minimize the app and go back to the dash.
313314
314315 Expected Results
315− - apps lens shown and Running Apps visible on screen
316+ - apps lens shown
316317 */
317− function test_minimizingAppTakesToRunningApps() {
318+ function test_minimizingAppTakesToDashApps() {
318319 var dashApps = findChild(shell, "clickscope");
319320 swipeUntilScopeViewIsReached(dashApps);
320321
@@ -352,10 +353,58 @@ Item {
352353 tryCompare(appsCategoryListView, "moving", false);
353354
354355 verify(itemIsOnScreen(dashApps));
356+ }
357+
358+ function test_showInputMethod() {
359+ var item = findChild(shell, "inputMethod");
360+ var surface = SurfaceManager.inputMethodSurface();
361+
362+ surface.setState(MirSurfaceItem.Minimized);
363+ tryCompare(item, "visible", false);
364+
365+ surface.setState(MirSurfaceItem.Restored);
366+ tryCompare(item, "visible", true);
367+
368+ surface.setState(MirSurfaceItem.Minimized);
369+ tryCompare(item, "visible", false);
370+
371+ surface.setState(MirSurfaceItem.Maximized);
372+ tryCompare(item, "visible", true);
373+
374+ surface.setState(MirSurfaceItem.Minimized);
375+ tryCompare(item, "visible", false);
376+ }
377+
378+ function test_surfaceLosesFocusWhilePanelIsOpen() {
379+ var app = ApplicationManager.startApplication("dialer-app");
380+ // wait until the app is fully loaded (ie, real surface replaces splash screen)
381+ tryCompareFunction(function() { return app.surface != null }, true);
382+
383+ tryCompare(app.surface, "focus", true);
384+
385+ // Drag the indicators panel half-open
386+ var touchX = shell.width / 2;
387+ var indicators = findChild(shell, "indicators");
388+ touchFlick(indicators,
389+ touchX /* fromX */, indicators.panelHeight * 0.5 /* fromY */,
390+ touchX /* toX */, shell.height * 0.5 /* toY */,
391+ true /* beginTouch */, false /* endTouch */);
392+ verify(indicators.partiallyOpened);
393+
394+ tryCompare(app.surface, "focus", false);
395+
396+ // And finish getting it open
397+ touchFlick(indicators,
398+ touchX /* fromX */, shell.height * 0.5 /* fromY */,
399+ touchX /* toX */, shell.height * 0.9 /* toY */,
400+ false /* beginTouch */, true /* endTouch */);
401+ tryCompare(indicators, "fullyOpened", true);
402+
403+ tryCompare(app.surface, "focus", false);
355404
356− var runningApplicationsGrid = findChild(appsCategoryListView, "running.apps.category");
357− verify(runningApplicationsGrid);
358− verify(itemIsOnScreen(runningApplicationsGrid));
405+ dragToCloseIndicatorsPanel();
406+
407+ tryCompare(app.surface, "focus", true);
359408 }
360409
361410 // Wait for the whole UI to settle down
@@ -365,6 +414,18 @@ Item {
365414 tryCompare(dashContentList, "moving", false);
366415 }
367416
417+ function dragToCloseIndicatorsPanel() {
418+ var indicators = findChild(shell, "indicators");
419+
420+ var touchStartX = shell.width / 2;
421+ var touchStartY = shell.height - (indicators.panelHeight * 0.5);
422+ touchFlick(shell,
423+ touchStartX, touchStartY,
424+ touchStartX, shell.height * 0.1);
425+
426+ tryCompare(indicators, "fullyClosed", true);
427+ }
428+
368429 function dragLauncherIntoView() {
369430 var launcherPanel = findChild(shell, "launcherPanel");
370431 verify(launcherPanel.x = - launcherPanel.width);
@@ -489,7 +550,7 @@ Item {
489550 {tag: "under greeter", greeter: true, app: false, launcher: false, indicators: false, expectedShown: false},
490551 {tag: "under app", greeter: false, app: true, launcher: false, indicators: false, expectedShown: false},
491552 {tag: "under launcher", greeter: false, app: false, launcher: true, indicators: false, expectedShown: true},
492− {tag: "under indicators", greeter: false, app: false, launcher: false, indicators: true, expectedShown: true},
553+ {tag: "under indicators", greeter: false, app: false, launcher: false, indicators: true, expectedShown: false},
493554 ]
494555 }
495556
@@ -519,16 +580,39 @@ Item {
519580 }
520581
521582 function test_focusRequestedHidesGreeter() {
522− var greeter = findChild(shell, "greeter")
583+ var greeter = findChild(shell, "greeter");
523584
524− greeter.show()
525− tryCompare(greeter, "showProgress", 1)
585+ var app = ApplicationManager.startApplication("dialer-app");
586+ // wait until the app is fully loaded (ie, real surface replaces splash screen)
587+ tryCompareFunction(function() { return app.surface != null }, true);
526588
527− ApplicationManager.focusRequested("notes-app")
528− tryCompare(greeter, "showProgress", 0)
529− waitUntilApplicationWindowIsFullyVisible()
589+ // Minimize the application we just launched
590+ swipeFromLeftEdge(units.gu(27));
591+
592+ // Wait for the whole UI to settle down
593+ waitUntilApplicationWindowIsFullyHidden();
594+
595+ greeter.show();
596+ tryCompare(greeter, "showProgress", 1);
597+
598+ // The main point of this test
599+ ApplicationManager.requestFocusApplication("dialer-app");
600+ tryCompare(greeter, "showProgress", 0);
601+ waitForRendering(greeter);
530602 }
531603
604+ function test_focusRequestedHidesIndicators() {
605+ var indicators = findChild(shell, "indicators");
606+
607+ showIndicators();
608+
609+ ApplicationManager.startApplication("camera-app");
610+ tryCompare(ApplicationManager, "count", 1);
611+
612+ tryCompare(indicators, "fullyClosed", true);
613+ }
614+
615+
532616 function test_showGreeterDBusCall() {
533617 var greeter = findChild(shell, "greeter")
534618 tryCompare(greeter, "showProgress", 0)
@@ -547,5 +631,18 @@ Item {
547631 swipeAwayGreeter()
548632 tryCompare(sessionSpy, "count", 1)
549633 }
634+
635+ function test_fullscreen() {
636+ var panel = findChild(shell, "panel");
637+ compare(panel.fullscreenMode, false);
638+ ApplicationManager.startApplication("camera-app");
639+ tryCompare(panel, "fullscreenMode", true);
640+ ApplicationManager.startApplication("gallery-app");
641+ tryCompare(panel, "fullscreenMode", false);
642+ ApplicationManager.requestFocusApplication("camera-app");
643+ tryCompare(panel, "fullscreenMode", true);
644+ ApplicationManager.requestFocusApplication("gallery-app");
645+ tryCompare(panel, "fullscreenMode", false);
646+ }
550647 }
551648}