on xenial help sign in
Merge with trunk
5970c90Mirco Müller
cmake/modules/QmlTest.cmake+1−1
@@ -135,7 +135,7 @@ macro(add_binary_qml_test CLASS_NAME LD_PATH DEPS)
135 set(testCommand
135 set(testCommand
136 LD_LIBRARY_PATH=${LD_PATH}
136 LD_LIBRARY_PATH=${LD_PATH}
137 ${CMAKE_CURRENT_BINARY_DIR}/${CLASS_NAME}TestExec
137 ${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
139 -o -,txt)
139 -o -,txt)
140
140
141 add_qmltest_target(test${CLASS_NAME} "${testCommand}" FALSE TRUE)
141 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+
1unity8 (7.90+14.10.20140725-0ubuntu1) utopic; urgency=low
63unity8 (7.90+14.10.20140725-0ubuntu1) utopic; urgency=low
2
64
3 [ CI bot ]
65 [ CI bot ]
debian/control+3−3
@@ -19,12 +19,12 @@ Build-Depends: cmake,
19 libgsettings-qt-dev,
19 libgsettings-qt-dev,
20 libhardware-dev,
20 libhardware-dev,
21 libhud-client2-dev,
21 libhud-client2-dev,
22+ libpam0g-dev,
22 libpay2-dev,
23 libpay2-dev,
23 libpulse-dev,
24 libpulse-dev,
24 libqmenumodel-dev (>= 0.2.8),
25 libqmenumodel-dev (>= 0.2.8),
25 libqt5xmlpatterns5-dev,
26 libqt5xmlpatterns5-dev,
26− libunity-api-dev (>= 7.85),
27+ libunity-api-dev (>= 7.87),
27− libunity-mir-dev,
28 libusermetricsoutput1-dev,
28 libusermetricsoutput1-dev,
29 libxcb1-dev,
29 libxcb1-dev,
30 pkg-config,
30 pkg-config,
@@ -80,10 +80,10 @@ Provides: indicator-renderer,
80Depends: gsettings-desktop-schemas,
80Depends: gsettings-desktop-schemas,
81 libcap2-bin,
81 libcap2-bin,
82 libglib2.0-bin,
82 libglib2.0-bin,
83− libunity-mir1 (>= 0.4),
84 qmenumodel-qml (>= 0.2.8),
83 qmenumodel-qml (>= 0.2.8),
85 qml-module-qtquick-xmllistmodel,
84 qml-module-qtquick-xmllistmodel,
86 qtdeclarative5-gsettings1.0,
85 qtdeclarative5-gsettings1.0,
86+ qtdeclarative5-qtmir-plugin (>= 0.4),
87 qtdeclarative5-ubuntu-settings-components (>= 0.3),
87 qtdeclarative5-ubuntu-settings-components (>= 0.3),
88 qtdeclarative5-ubuntu-telephony0.1,
88 qtdeclarative5-ubuntu-telephony0.1,
89 unity-launcher-impl-3,
89 unity-launcher-impl-3,
plugins/AccountsService/AccountsService.cpp+21−1
@@ -26,7 +26,8 @@ AccountsService::AccountsService(QObject* parent)
26 m_service(new AccountsServiceDBusAdaptor(this)),
26 m_service(new AccountsServiceDBusAdaptor(this)),
27 m_user(qgetenv("USER")),
27 m_user(qgetenv("USER")),
28 m_demoEdges(false),
28 m_demoEdges(false),
29− m_statsWelcomeScreen(false)
29+ m_statsWelcomeScreen(false),
30+ m_passwordDisplayHint(Keyboard)
30{
31{
31 connect(m_service, SIGNAL(propertiesChanged(const QString &, const QString &, const QStringList &)),
32 connect(m_service, SIGNAL(propertiesChanged(const QString &, const QString &, const QStringList &)),
32 this, SLOT(propertiesChanged(const QString &, const QString &, const QStringList &)));
33 this, SLOT(propertiesChanged(const QString &, const QString &, const QStringList &)));
@@ -47,6 +48,7 @@ void AccountsService::setUser(const QString &user)
47 updateDemoEdges();
48 updateDemoEdges();
48 updateBackgroundFile();
49 updateBackgroundFile();
49 updateStatsWelcomeScreen();
50 updateStatsWelcomeScreen();
51+ updatePasswordDisplayHint();
50}
52}
51
53
52bool AccountsService::demoEdges() const
54bool AccountsService::demoEdges() const
@@ -70,6 +72,11 @@ bool AccountsService::statsWelcomeScreen() const
70 return m_statsWelcomeScreen;
72 return m_statsWelcomeScreen;
71}
73}
72
74
75+AccountsService::PasswordDisplayHint AccountsService::passwordDisplayHint() const
76+{
77+ return m_passwordDisplayHint;
78+}
79+
73void AccountsService::updateDemoEdges()
80void AccountsService::updateDemoEdges()
74{
81{
75 auto demoEdges = m_service->getUserProperty(m_user, "com.canonical.unity.AccountsService", "demo-edges").toBool();
82 auto demoEdges = m_service->getUserProperty(m_user, "com.canonical.unity.AccountsService", "demo-edges").toBool();
@@ -97,6 +104,15 @@ void AccountsService::updateStatsWelcomeScreen()
97 }
104 }
98}
105}
99
106
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+
100void AccountsService::propertiesChanged(const QString &user, const QString &interface, const QStringList &changed)
116void AccountsService::propertiesChanged(const QString &user, const QString &interface, const QStringList &changed)
101{
117{
102 if (m_user != user) {
118 if (m_user != user) {
@@ -111,6 +127,10 @@ void AccountsService::propertiesChanged(const QString &user, const QString &inte
111 if (changed.contains("StatsWelcomeScreen")) {
127 if (changed.contains("StatsWelcomeScreen")) {
112 updateStatsWelcomeScreen();
128 updateStatsWelcomeScreen();
113 }
129 }
130+ } else if (interface == "com.ubuntu.AccountsService.SecurityPrivacy") {
131+ if (changed.contains("PasswordDisplayHint")) {
132+ updatePasswordDisplayHint();
133+ }
114 }
134 }
115}
135}
116
136
plugins/AccountsService/AccountsService.h+13
@@ -27,6 +27,7 @@ class AccountsServiceDBusAdaptor;
27class AccountsService: public QObject
27class AccountsService: public QObject
28{
28{
29 Q_OBJECT
29 Q_OBJECT
30+ Q_ENUMS(PasswordDisplayHint)
30 Q_PROPERTY (QString user
31 Q_PROPERTY (QString user
31 READ user
32 READ user
32 WRITE setUser
33 WRITE setUser
@@ -41,8 +42,16 @@ class AccountsService: public QObject
41 Q_PROPERTY (bool statsWelcomeScreen
42 Q_PROPERTY (bool statsWelcomeScreen
42 READ statsWelcomeScreen
43 READ statsWelcomeScreen
43 NOTIFY statsWelcomeScreenChanged)
44 NOTIFY statsWelcomeScreenChanged)
45+ Q_PROPERTY (PasswordDisplayHint passwordDisplayHint
46+ READ passwordDisplayHint
47+ NOTIFY passwordDisplayHintChanged)
44
48
45public:
49public:
50+ enum PasswordDisplayHint {
51+ Keyboard,
52+ Numeric,
53+ };
54+
46 explicit AccountsService(QObject *parent = 0);
55 explicit AccountsService(QObject *parent = 0);
47
56
48 QString user() const;
57 QString user() const;
@@ -51,12 +60,14 @@ public:
51 void setDemoEdges(bool demoEdges);
60 void setDemoEdges(bool demoEdges);
52 QString backgroundFile() const;
61 QString backgroundFile() const;
53 bool statsWelcomeScreen() const;
62 bool statsWelcomeScreen() const;
63+ PasswordDisplayHint passwordDisplayHint() const;
54
64
55Q_SIGNALS:
65Q_SIGNALS:
56 void userChanged();
66 void userChanged();
57 void demoEdgesChanged();
67 void demoEdgesChanged();
58 void backgroundFileChanged();
68 void backgroundFileChanged();
59 void statsWelcomeScreenChanged();
69 void statsWelcomeScreenChanged();
70+ void passwordDisplayHintChanged();
60
71
61private Q_SLOTS:
72private Q_SLOTS:
62 void propertiesChanged(const QString &user, const QString &interface, const QStringList &changed);
73 void propertiesChanged(const QString &user, const QString &interface, const QStringList &changed);
@@ -66,12 +77,14 @@ private:
66 void updateDemoEdges();
77 void updateDemoEdges();
67 void updateBackgroundFile();
78 void updateBackgroundFile();
68 void updateStatsWelcomeScreen();
79 void updateStatsWelcomeScreen();
80+ void updatePasswordDisplayHint();
69
81
70 AccountsServiceDBusAdaptor *m_service;
82 AccountsServiceDBusAdaptor *m_service;
71 QString m_user;
83 QString m_user;
72 bool m_demoEdges;
84 bool m_demoEdges;
73 QString m_backgroundFile;
85 QString m_backgroundFile;
74 bool m_statsWelcomeScreen;
86 bool m_statsWelcomeScreen;
87+ PasswordDisplayHint m_passwordDisplayHint;
75};
88};
76
89
77#endif
90#endif
plugins/AccountsService/plugin.cpp+1
@@ -33,5 +33,6 @@ void AccountsServicePlugin::registerTypes(const char *uri)
33{
33{
34 Q_ASSERT(uri == QLatin1String("AccountsService"));
34 Q_ASSERT(uri == QLatin1String("AccountsService"));
35 qDBusRegisterMetaType<QList<QVariantMap>>();
35 qDBusRegisterMetaType<QList<QVariantMap>>();
36+ qRegisterMetaType<AccountsService::PasswordDisplayHint>("AccountsService::PasswordDisplayHint");
36 qmlRegisterSingletonType<AccountsService>(uri, 0, 1, "AccountsService", service_provider);
37 qmlRegisterSingletonType<AccountsService>(uri, 0, 1, "AccountsService", service_provider);
37}
38}
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\
193 ] \n\
193 ] \n\
194 }\n';
194 }\n';
195
195
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+
196// %1 is used as anchors of mascotShapeLoader
211// %1 is used as anchors of mascotShapeLoader
197var kMascotShapeLoaderCode = 'Loader { \n\
212var kMascotShapeLoaderCode = 'Loader { \n\
198 id: mascotShapeLoader; \n\
213 id: mascotShapeLoader; \n\
@@ -224,7 +239,7 @@ var kMascotImageCode = 'Image { \n\
224 }\n';
239 }\n';
225
240
226// %1 is used as anchors of titleLabel
241// %1 is used as anchors of titleLabel
227−// %1 is used as color of titleLabel
242+// %2 is used as color of titleLabel
228// %3 is used as extra condition for visible of titleLabel
243// %3 is used as extra condition for visible of titleLabel
229var kTitleLabelCode = 'Label { \n\
244var kTitleLabelCode = 'Label { \n\
230 id: titleLabel; \n\
245 id: titleLabel; \n\
@@ -258,6 +273,15 @@ var kSubtitleLabelCode = 'Label { \n\
258 horizontalAlignment: root.headerAlignment; \n\
273 horizontalAlignment: root.headerAlignment; \n\
259 }\n';
274 }\n';
260
275
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+
261// %1 is used as top anchor of summary
285// %1 is used as top anchor of summary
262// %2 is used as topMargin anchor of summary
286// %2 is used as topMargin anchor of summary
263// %3 is used as color of summary
287// %3 is used as color of summary
@@ -308,6 +332,7 @@ function cardString(template, components) {
308 var headerAsOverlay = hasArt && template && template["overlay"] === true && (hasTitle || hasMascot);
332 var headerAsOverlay = hasArt && template && template["overlay"] === true && (hasTitle || hasMascot);
309 var hasSubtitle = hasTitle && components["subtitle"] || false;
333 var hasSubtitle = hasTitle && components["subtitle"] || false;
310 var hasHeaderRow = hasMascot && hasTitle;
334 var hasHeaderRow = hasMascot && hasTitle;
335+ var hasAttributes = hasTitle && components["attributes"] || false;
311
336
312 if (hasBackground) {
337 if (hasBackground) {
313 code += kBackgroundLoaderCode;
338 code += kBackgroundLoaderCode;
@@ -377,6 +402,14 @@ function cardString(template, components) {
377 code += 'readonly property int headerHeight: row.height;\n'
402 code += 'readonly property int headerHeight: row.height;\n'
378 } else if (hasMascot) {
403 } else if (hasMascot) {
379 code += 'readonly property int headerHeight: mascotImage.height;\n'
404 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+ }
380 } else if (hasSubtitle) {
413 } else if (hasSubtitle) {
381 code += 'readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;\n'
414 code += 'readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;\n'
382 } else if (hasTitle) {
415 } else if (hasTitle) {
@@ -425,10 +458,12 @@ function cardString(template, components) {
425
458
426 var titleAnchors;
459 var titleAnchors;
427 var subtitleAnchors;
460 var subtitleAnchors;
461+ var attributesAnchors;
428− if (hasMascot && hasSubtitle) {
462+ if (hasMascot && (hasSubtitle || hasAttributes)) {
429 // Using row + column
463 // Using row + column
430 titleAnchors = 'left: parent.left; right: parent.right';
464 titleAnchors = 'left: parent.left; right: parent.right';
431 subtitleAnchors = titleAnchors;
465 subtitleAnchors = titleAnchors;
466+ attributesAnchors = subtitleAnchors;
432 } else if (hasMascot) {
467 } else if (hasMascot) {
433 // Using row + label
468 // Using row + label
434 titleAnchors = 'verticalCenter: parent.verticalCenter;\n'
469 titleAnchors = 'verticalCenter: parent.verticalCenter;\n'
@@ -438,11 +473,13 @@ function cardString(template, components) {
438 titleAnchors = 'left: parent.left; \n\
473 titleAnchors = 'left: parent.left; \n\
439 leftMargin: units.gu(1); \n\
474 leftMargin: units.gu(1); \n\
440 right: parent.right; \n\
475 right: parent.right; \n\
476+ rightMargin: units.gu(1); \n\
441 top: overlayLoader.top; \n\
477 top: overlayLoader.top; \n\
442 topMargin: units.gu(1);\n';
478 topMargin: units.gu(1);\n';
443 } else {
479 } else {
444 // Using anchors to the mascot/parent
480 // Using anchors to the mascot/parent
445− titleAnchors = "right: parent.right;";
481+ titleAnchors = "right: parent.right;\n";
482+ titleAnchors += "rightMargin: units.gu(1);\n";
446 titleAnchors += headerLeftAnchor;
483 titleAnchors += headerLeftAnchor;
447 titleAnchors += headerVerticalAnchors;
484 titleAnchors += headerVerticalAnchors;
448 if (!headerLeftAnchorHasMargin) {
485 if (!headerLeftAnchorHasMargin) {
@@ -452,8 +489,19 @@ function cardString(template, components) {
452 subtitleAnchors = 'left: titleLabel.left; \n\
489 subtitleAnchors = 'left: titleLabel.left; \n\
453 leftMargin: titleLabel.leftMargin; \n\
490 leftMargin: titleLabel.leftMargin; \n\
454 right: titleLabel.right; \n\
491 right: titleLabel.right; \n\
492+ rightMargin: titleLabel.rightMargin; \n\
455 top: titleLabel.bottom; \n\
493 top: titleLabel.bottom; \n\
456 topMargin: units.dp(2);\n';
494 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+ }
457 }
505 }
458
506
459 var titleLabelVisibleExtra = (headerAsOverlay ? '&& overlayLoader.active': '');
507 var titleLabelVisibleExtra = (headerAsOverlay ? '&& overlayLoader.active': '');
@@ -463,11 +511,27 @@ function cardString(template, components) {
463 subtitleCode += kSubtitleLabelCode.arg(subtitleAnchors).arg(color);
511 subtitleCode += kSubtitleLabelCode.arg(subtitleAnchors).arg(color);
464 }
512 }
465
513
466− if (hasMascot && hasSubtitle) {
514+ if (hasMascot && (hasSubtitle || hasAttributes)) {
467 // If using row + column wrap the code in the column
515 // If using row + column wrap the code in the column
468 titleSubtitleCode = kHeaderColumnCode.arg(titleCode).arg(subtitleCode);
516 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+ }
469 } else {
526 } 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+ }
471 }
535 }
472 }
536 }
473
537
@@ -487,6 +551,7 @@ function cardString(template, components) {
487 else if (headerAsOverlay && hasArt) summaryTopAnchor = "artShapeHolder.bottom";
551 else if (headerAsOverlay && hasArt) summaryTopAnchor = "artShapeHolder.bottom";
488 else if (hasHeaderRow) summaryTopAnchor = "row.bottom";
552 else if (hasHeaderRow) summaryTopAnchor = "row.bottom";
489 else if (hasMascot) summaryTopAnchor = "mascotImage.bottom";
553 else if (hasMascot) summaryTopAnchor = "mascotImage.bottom";
554+ else if (hasAttributes) summaryTopAnchor = "attributesRow.bottom";
490 else if (hasSubtitle) summaryTopAnchor = "subtitleLabel.bottom";
555 else if (hasSubtitle) summaryTopAnchor = "subtitleLabel.bottom";
491 else if (hasTitle) summaryTopAnchor = "titleLabel.bottom";
556 else if (hasTitle) summaryTopAnchor = "titleLabel.bottom";
492 else if (hasArt) summaryTopAnchor = "artShapeHolder.bottom";
557 else if (hasArt) summaryTopAnchor = "artShapeHolder.bottom";
@@ -499,7 +564,7 @@ function cardString(template, components) {
499 color = 'root.scopeStyle ? root.scopeStyle.foreground : "grey"';
564 color = 'root.scopeStyle ? root.scopeStyle.foreground : "grey"';
500 }
565 }
501
566
502− var summaryTopMargin = (hasMascot || hasSubtitle ? 'anchors.margins' : '0');
567+ var summaryTopMargin = (hasMascot || hasSubtitle || hasAttributes ? 'anchors.margins' : '0');
503
568
504 code += kSummaryLabelCode.arg(summaryTopAnchor).arg(summaryTopMargin).arg(color);
569 code += kSummaryLabelCode.arg(summaryTopAnchor).arg(summaryTopMargin).arg(color);
505 }
570 }
@@ -510,6 +575,8 @@ function cardString(template, components) {
510 code += 'implicitHeight: row.y + row.height + units.gu(1);\n';
575 code += 'implicitHeight: row.y + row.height + units.gu(1);\n';
511 } else if (hasMascot) {
576 } else if (hasMascot) {
512 code += 'implicitHeight: mascotImage.y + mascotImage.height;\n';
577 code += 'implicitHeight: mascotImage.y + mascotImage.height;\n';
578+ } else if (hasAttributes) {
579+ code += 'implicitHeight: attributesRow.y + attributesRow.height + units.gu(1);\n';
513 } else if (hasSubtitle) {
580 } else if (hasSubtitle) {
514 code += 'implicitHeight: subtitleLabel.y + subtitleLabel.height + units.gu(1);\n';
581 code += 'implicitHeight: subtitleLabel.y + subtitleLabel.height + units.gu(1);\n';
515 } else if (hasTitle) {
582 } else if (hasTitle) {
@@ -526,8 +593,10 @@ function cardString(template, components) {
526function createCardComponent(parent, template, components) {
593function createCardComponent(parent, template, components) {
527 var imports = 'import QtQuick 2.2; \n\
594 var imports = 'import QtQuick 2.2; \n\
528 import Ubuntu.Components 0.1; \n\
595 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';
530 var card = cardString(template, components);
598 var card = cardString(template, components);
531 var code = imports + 'Component {\n' + card + '}\n';
599 var code = imports + 'Component {\n' + card + '}\n';
600+
532 return Qt.createQmlObject(code, parent, "createCardComponent");
601 return Qt.createQmlObject(code, parent, "createCardComponent");
533}
602}
plugins/Dash/qmldir+1
@@ -3,3 +3,4 @@ plugin Dash-qml
3typeinfo Dash.qmltypes
3typeinfo Dash.qmltypes
4singleton CardCreatorCache 0.1 CardCreatorCache.qml
4singleton CardCreatorCache 0.1 CardCreatorCache.qml
5ScopeStyle 0.1 ScopeStyle.qml
5ScopeStyle 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)
69 qmlRegisterType<UserMetricsOutput::ColorTheme>();
69 qmlRegisterType<UserMetricsOutput::ColorTheme>();
70
70
71 Q_ASSERT(uri == QLatin1String("LightDM"));
71 Q_ASSERT(uri == QLatin1String("LightDM"));
72+ qRegisterMetaType<QLightDM::Greeter::MessageType>("QLightDM::Greeter::MessageType");
73+ qRegisterMetaType<QLightDM::Greeter::PromptType>("QLightDM::Greeter::PromptType");
72 qmlRegisterSingletonType<Greeter>(uri, 0, 1, "Greeter", greeter_provider);
74 qmlRegisterSingletonType<Greeter>(uri, 0, 1, "Greeter", greeter_provider);
73 qmlRegisterSingletonType<UsersModel>(uri, 0, 1, "Users", users_provider);
75 qmlRegisterSingletonType<UsersModel>(uri, 0, 1, "Users", users_provider);
74 qmlRegisterUncreatableType<QLightDM::UsersModel>(uri, 0, 1, "UserRoles", "Type is not instantiable");
76 qmlRegisterUncreatableType<QLightDM::UsersModel>(uri, 0, 1, "UserRoles", "Type is not instantiable");
plugins/Utils/CMakeLists.txt+1
@@ -15,6 +15,7 @@ set(QMLPLUGIN_SRC
15 qsortfilterproxymodelqml.cpp
15 qsortfilterproxymodelqml.cpp
16 timeformatter.cpp
16 timeformatter.cpp
17 unitymenumodelpaths.cpp
17 unitymenumodelpaths.cpp
18+ windowkeysfilter.cpp
18 easingcurve.cpp
19 easingcurve.cpp
19 plugin.cpp
20 plugin.cpp
20 )
21 )
plugins/Utils/easingcurve.cpp+5−1
@@ -34,7 +34,11 @@ QEasingCurve::Type EasingCurve::type() const
34
34
35void EasingCurve::setType(const QEasingCurve::Type &type)
35void EasingCurve::setType(const QEasingCurve::Type &type)
36{
36{
37+ // FIXME: Working around bug https://bugreports.qt-project.org/browse/QTBUG-38686 here
38+ QEasingCurve newCurve;
37− m_easingCurve.setType(type);
39+ newCurve.setType(type);
40+ newCurve.setPeriod(m_easingCurve.period());
41+ m_easingCurve = newCurve;
38 Q_EMIT typeChanged();
42 Q_EMIT typeChanged();
39}
43}
40
44
plugins/Utils/plugin.cpp+2
@@ -30,6 +30,7 @@
30#include "qsortfilterproxymodelqml.h"
30#include "qsortfilterproxymodelqml.h"
31#include "timeformatter.h"
31#include "timeformatter.h"
32#include "unitymenumodelpaths.h"
32#include "unitymenumodelpaths.h"
33+#include "windowkeysfilter.h"
33#include "easingcurve.h"
34#include "easingcurve.h"
34
35
35void UtilsPlugin::registerTypes(const char *uri)
36void UtilsPlugin::registerTypes(const char *uri)
@@ -40,6 +41,7 @@ void UtilsPlugin::registerTypes(const char *uri)
40 qmlRegisterType<QSortFilterProxyModelQML>(uri, 0, 1, "SortFilterProxyModel");
41 qmlRegisterType<QSortFilterProxyModelQML>(uri, 0, 1, "SortFilterProxyModel");
41 qmlRegisterType<UnityMenuModelPaths>(uri, 0, 1, "UnityMenuModelPaths");
42 qmlRegisterType<UnityMenuModelPaths>(uri, 0, 1, "UnityMenuModelPaths");
42 qmlRegisterType<TimeFormatter>(uri, 0, 1, "TimeFormatter");
43 qmlRegisterType<TimeFormatter>(uri, 0, 1, "TimeFormatter");
44+ qmlRegisterType<WindowKeysFilter>(uri, 0, 1, "WindowKeysFilter");
43 qmlRegisterType<GDateTimeFormatter>(uri, 0, 1, "GDateTimeFormatter");
45 qmlRegisterType<GDateTimeFormatter>(uri, 0, 1, "GDateTimeFormatter");
44 qmlRegisterType<EasingCurve>(uri, 0, 1, "EasingCurve");
46 qmlRegisterType<EasingCurve>(uri, 0, 1, "EasingCurve");
45}
47}
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 ""
9"Project-Id-Version: unity8\n"
9"Project-Id-Version: unity8\n"
10"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
11"POT-Creation-Date: 2014-07-11 14:59+0200\n"
11"POT-Creation-Date: 2014-07-11 14:59+0200\n"
12−"PO-Revision-Date: 2014-07-22 15:46+0000\n"
12+"PO-Revision-Date: 2014-07-30 12:37+0000\n"
13−"Last-Translator: Kenneth Ramsussen <kentora@kentora.dk>\n"
13+"Last-Translator: Ask Hjorth Larsen <asklarsen@gmail.com>\n"
14"Language-Team: Danish <dansk@dansk-gruppen.dk>\n"
14"Language-Team: Danish <dansk@dansk-gruppen.dk>\n"
15"MIME-Version: 1.0\n"
15"MIME-Version: 1.0\n"
16"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Type: text/plain; charset=UTF-8\n"
17"Content-Transfer-Encoding: 8bit\n"
17"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"
19"X-Generator: Launchpad (build 17131)\n"
19"X-Generator: Launchpad (build 17131)\n"
20
20
21#: plugins/Unity/Launcher/launcheritem.cpp:43
21#: plugins/Unity/Launcher/launcheritem.cpp:43
@@ -72,7 +72,7 @@ msgid ""
72"You have now mastered the edge gestures and can start using the "
72"You have now mastered the edge gestures and can start using the "
73"phone<br><br>Tap on the screen to start"
73"phone<br><br>Tap on the screen to start"
74msgstr ""
74msgstr ""
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 "
76"på skærmen for at starte"
76"på skærmen for at starte"
77
77
78#: qml/Components/Lockscreen.qml:214
78#: qml/Components/Lockscreen.qml:214
@@ -114,7 +114,7 @@ msgstr "Seneste"
114
114
115#: qml/Dash/GenericScopeView.qml:330
115#: qml/Dash/GenericScopeView.qml:330
116msgid "See all"
116msgid "See all"
117−msgstr ""
117+msgstr "Se alle"
118
118
119#: qml/Dash/Previews/PreviewActionCombo.qml:34
119#: qml/Dash/Previews/PreviewActionCombo.qml:34
120msgid "More..."
120msgid "More..."
@@ -170,11 +170,11 @@ msgstr "Indtast venligst PIN for SIM"
170
170
171#: qml/Panel/ActiveCallHint.qml:77
171#: qml/Panel/ActiveCallHint.qml:77
172msgid "Tap to return to call..."
172msgid "Tap to return to call..."
173−msgstr ""
173+msgstr "Tryk for at besvare opkaldet..."
174
174
175#: qml/Panel/ActiveCallHint.qml:90
175#: qml/Panel/ActiveCallHint.qml:90
176msgid "Conference"
176msgid "Conference"
177−msgstr ""
177+msgstr "Konference"
178
178
179#: qml/Panel/Indicators/MenuItemFactory.qml:583
179#: qml/Panel/Indicators/MenuItemFactory.qml:583
180msgid "In queue…"
180msgid "In queue…"
po/fa.po+51−49
@@ -8,200 +8,202 @@ msgstr ""
8"Project-Id-Version: unity8\n"
8"Project-Id-Version: unity8\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2014-07-11 14:59+0200\n"
10"POT-Creation-Date: 2014-07-11 14:59+0200\n"
11−"PO-Revision-Date: 2013-10-31 19:02+0000\n"
11+"PO-Revision-Date: 2014-08-03 13:00+0000\n"
12−"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
12+"Last-Translator: Arash Badie Modiri <arashbm@gmail.com>\n"
13"Language-Team: Persian <fa@li.org>\n"
13"Language-Team: Persian <fa@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-07-26 07:17+0000\n"
17+"X-Launchpad-Export-Date: 2014-08-04 06:32+0000\n"
18−"X-Generator: Launchpad (build 17131)\n"
18+"X-Generator: Launchpad (build 17147)\n"
19
19
20#: plugins/Unity/Launcher/launcheritem.cpp:43
20#: plugins/Unity/Launcher/launcheritem.cpp:43
21#: plugins/Unity/Launcher/launcheritem.cpp:73
21#: plugins/Unity/Launcher/launcheritem.cpp:73
22msgid "Pin shortcut"
22msgid "Pin shortcut"
23−msgstr ""
23+msgstr "سنجاق کردن میانبر"
24
24
25#: plugins/Unity/Launcher/launcheritem.cpp:73
25#: plugins/Unity/Launcher/launcheritem.cpp:73
26msgid "Unpin shortcut"
26msgid "Unpin shortcut"
27−msgstr ""
27+msgstr "برداشتن سنجاق میانبر"
28
28
29#: qml/Components/EdgeDemoOverlay.qml:151
29#: qml/Components/EdgeDemoOverlay.qml:151
30msgid "Skip intro"
30msgid "Skip intro"
31−msgstr ""
31+msgstr "پرش از مقدّمه"
32
32
33#: qml/Components/EdgeDemo.qml:113
33#: qml/Components/EdgeDemo.qml:113
34msgid "Right edge"
34msgid "Right edge"
35−msgstr ""
35+msgstr "لبه‌ی راست"
36
36
37#: qml/Components/EdgeDemo.qml:114
37#: qml/Components/EdgeDemo.qml:114
38msgid "Try swiping from the right edge to unlock the phone"
38msgid "Try swiping from the right edge to unlock the phone"
39−msgstr ""
39+msgstr "برای باز کردن قفل تلفن، از لبه‌ی راست بکشید"
40
40
41#: qml/Components/EdgeDemo.qml:145
41#: qml/Components/EdgeDemo.qml:145
42msgid "Top edge"
42msgid "Top edge"
43−msgstr ""
43+msgstr "لبه‌ی بالا"
44
44
45#: qml/Components/EdgeDemo.qml:146
45#: qml/Components/EdgeDemo.qml:146
46msgid "Try swiping from the top edge to access the indicators"
46msgid "Try swiping from the top edge to access the indicators"
47−msgstr ""
47+msgstr "برای دست‌رسی به نشانگرها، از لبه‌ی بالا بکشید"
48
48
49#: qml/Components/EdgeDemo.qml:171
49#: qml/Components/EdgeDemo.qml:171
50msgid "Close"
50msgid "Close"
51−msgstr ""
51+msgstr "بستن"
52
52
53#: qml/Components/EdgeDemo.qml:172
53#: qml/Components/EdgeDemo.qml:172
54msgid "Swipe up again to close the settings screen"
54msgid "Swipe up again to close the settings screen"
55−msgstr ""
55+msgstr "برای بستن صفحه‌ی تنظیمات، دوباره به بالا بکشپد"
56
56
57#: qml/Components/EdgeDemo.qml:199
57#: qml/Components/EdgeDemo.qml:199
58msgid "Left edge"
58msgid "Left edge"
59−msgstr ""
59+msgstr "لبه‌ی چپ"
60
60
61#: qml/Components/EdgeDemo.qml:200
61#: qml/Components/EdgeDemo.qml:200
62msgid "Swipe from the left to reveal the launcher for quick access to apps"
62msgid "Swipe from the left to reveal the launcher for quick access to apps"
63−msgstr ""
63+msgstr "برای آشکار شدن اجراگر به منظور دست‌رسی سریع به اپ‌ها از سمت چپ بکشید"
64
64
65#: qml/Components/EdgeDemo.qml:227
65#: qml/Components/EdgeDemo.qml:227
66msgid "Well done"
66msgid "Well done"
67−msgstr ""
67+msgstr "عالی بود"
68
68
69#: qml/Components/EdgeDemo.qml:228
69#: qml/Components/EdgeDemo.qml:228
70msgid ""
70msgid ""
71"You have now mastered the edge gestures and can start using the "
71"You have now mastered the edge gestures and can start using the "
72"phone<br><br>Tap on the screen to start"
72"phone<br><br>Tap on the screen to start"
73msgstr ""
73msgstr ""
74+"هم اکنون در استفاده از ژست های حرکتی مهارت لازم را دارید و می‌توانید استفاده "
75+"از تلفن را آغاز کنید<br><br>برای شروع صفحه را لمس کنید"
74
76
75#: qml/Components/Lockscreen.qml:214
77#: qml/Components/Lockscreen.qml:214
76msgid "Emergency Call"
78msgid "Emergency Call"
77−msgstr ""
79+msgstr "تماس اضطراری"
78
80
79#: qml/Components/Lockscreen.qml:231
81#: qml/Components/Lockscreen.qml:231
80msgid "OK"
82msgid "OK"
81−msgstr ""
83+msgstr "قبول"
82
84
83#: qml/Components/PassphraseLockscreen.qml:60
85#: qml/Components/PassphraseLockscreen.qml:60
84#, qt-format
86#, qt-format
85msgid "Hello %1"
87msgid "Hello %1"
86−msgstr ""
88+msgstr "سلام %1"
87
89
88#: qml/Components/PassphraseLockscreen.qml:60
90#: qml/Components/PassphraseLockscreen.qml:60
89msgid "Hello"
91msgid "Hello"
90−msgstr ""
92+msgstr "سلام"
91
93
92#: qml/Components/PinLockscreen.qml:198
94#: qml/Components/PinLockscreen.qml:198
93msgid "CANCEL"
95msgid "CANCEL"
94−msgstr ""
96+msgstr "لغو"
95
97
96#: qml/Components/PinLockscreen.qml:216
98#: qml/Components/PinLockscreen.qml:216
97msgid "DONE"
99msgid "DONE"
98−msgstr ""
100+msgstr "انجام شد"
99
101
100#: qml/Components/SeeMore.qml:35
102#: qml/Components/SeeMore.qml:35
101msgid "See more"
103msgid "See more"
102−msgstr ""
104+msgstr "بیش‌تر ببینید"
103
105
104#: qml/Components/SeeMore.qml:58 qml/Dash/GenericScopeView.qml:330
106#: qml/Components/SeeMore.qml:58 qml/Dash/GenericScopeView.qml:330
105msgid "See less"
107msgid "See less"
106−msgstr ""
108+msgstr "کم‌تر ببینید"
107
109
108#: qml/Dash/DashApps.qml:37
110#: qml/Dash/DashApps.qml:37
109msgid "Recent"
111msgid "Recent"
110−msgstr ""
112+msgstr "موارد اخیر"
111
113
112#: qml/Dash/GenericScopeView.qml:330
114#: qml/Dash/GenericScopeView.qml:330
113msgid "See all"
115msgid "See all"
114−msgstr ""
116+msgstr "دیدن همه"
115
117
116#: qml/Dash/Previews/PreviewActionCombo.qml:34
118#: qml/Dash/Previews/PreviewActionCombo.qml:34
117msgid "More..."
119msgid "More..."
118−msgstr ""
120+msgstr "بیش‌تر…"
119
121
120#: qml/Dash/Previews/PreviewActionCombo.qml:34
122#: qml/Dash/Previews/PreviewActionCombo.qml:34
121msgid "Less..."
123msgid "Less..."
122−msgstr ""
124+msgstr "کم‌تر…"
123
125
124#: qml/Dash/Previews/PreviewRatingInput.qml:81
126#: qml/Dash/Previews/PreviewRatingInput.qml:81
125msgid "Rate this"
127msgid "Rate this"
126−msgstr ""
128+msgstr "امتیاز دادن"
127
129
128#: qml/Dash/Previews/PreviewRatingInput.qml:126
130#: qml/Dash/Previews/PreviewRatingInput.qml:126
129msgid "Add a review"
131msgid "Add a review"
130−msgstr ""
132+msgstr "افزودن یک بازبینی"
131
133
132#: qml/Dash/Previews/PreviewRatingInput.qml:166
134#: qml/Dash/Previews/PreviewRatingInput.qml:166
133msgid "Send"
135msgid "Send"
134−msgstr ""
136+msgstr "ارسال"
135
137
136#: qml/Greeter/Greeter.qml:157
138#: qml/Greeter/Greeter.qml:157
137msgid "Swipe to unlock"
139msgid "Swipe to unlock"
138−msgstr ""
140+msgstr "برای باز کردن قفل بکشد"
139
141
140#: qml/Hud/HudParametrizedActionsPage.qml:132
142#: qml/Hud/HudParametrizedActionsPage.qml:132
141msgid "Confirm"
143msgid "Confirm"
142−msgstr ""
144+msgstr "تأیید"
143
145
144#: qml/Hud/Hud.qml:61 qml/Hud/Hud.qml:99 qml/Hud/Hud.qml:372
146#: qml/Hud/Hud.qml:61 qml/Hud/Hud.qml:99 qml/Hud/Hud.qml:372
145msgid "Type or say a command"
147msgid "Type or say a command"
146−msgstr ""
148+msgstr "دستوری را بنویسید یا بگویید"
147
149
148#: qml/Hud/Hud.qml:84
150#: qml/Hud/Hud.qml:84
149msgid "Loading. Please Wait..."
151msgid "Loading. Please Wait..."
150−msgstr ""
152+msgstr "در حال بار کردن. لطفاً صبر کنید…"
151
153
152#: qml/Hud/Hud.qml:89
154#: qml/Hud/Hud.qml:89
153msgid "Speak Now..."
155msgid "Speak Now..."
154−msgstr ""
156+msgstr "اکنون صحبت کنید…"
155
157
156#: qml/Hud/Hud.qml:94
158#: qml/Hud/Hud.qml:94
157msgid "Speaking..."
159msgid "Speaking..."
158−msgstr ""
160+msgstr "در حال صحبت کردن…"
159
161
160#: qml/Notifications/NotificationMenuItemFactory.qml:91
162#: qml/Notifications/NotificationMenuItemFactory.qml:91
161msgid "Show password"
163msgid "Show password"
162−msgstr ""
164+msgstr "نمایش گذرواژه"
163
165
164#: qml/Notifications/NotificationMenuItemFactory.qml:103
166#: qml/Notifications/NotificationMenuItemFactory.qml:103
165msgid "Please enter SIM PIN"
167msgid "Please enter SIM PIN"
166−msgstr ""
168+msgstr "لطفاً PIN سیم‌کارت را وارد کنید"
167
169
168#: qml/Panel/ActiveCallHint.qml:77
170#: qml/Panel/ActiveCallHint.qml:77
169msgid "Tap to return to call..."
171msgid "Tap to return to call..."
170−msgstr ""
172+msgstr "برای بازگشت به تماس ضربه بزنید…"
171
173
172#: qml/Panel/ActiveCallHint.qml:90
174#: qml/Panel/ActiveCallHint.qml:90
173msgid "Conference"
175msgid "Conference"
174−msgstr ""
176+msgstr "کنفرانس"
175
177
176#: qml/Panel/Indicators/MenuItemFactory.qml:583
178#: qml/Panel/Indicators/MenuItemFactory.qml:583
177msgid "In queue…"
179msgid "In queue…"
178−msgstr ""
180+msgstr "در صف…"
179
181
180#: qml/Panel/Indicators/MenuItemFactory.qml:587
182#: qml/Panel/Indicators/MenuItemFactory.qml:587
181msgid "Downloading"
183msgid "Downloading"
182−msgstr ""
184+msgstr "در حال بارگیری"
183
185
184#: qml/Panel/Indicators/MenuItemFactory.qml:589
186#: qml/Panel/Indicators/MenuItemFactory.qml:589
185msgid "Paused, tap to resume"
187msgid "Paused, tap to resume"
186−msgstr ""
188+msgstr "مکث شد، برای از سر گیری ضربه بزنید"
187
189
188#: qml/Panel/Indicators/MenuItemFactory.qml:591
190#: qml/Panel/Indicators/MenuItemFactory.qml:591
189msgid "Canceled"
191msgid "Canceled"
190−msgstr ""
192+msgstr "لغو شد"
191
193
192#: qml/Panel/Indicators/MenuItemFactory.qml:593
194#: qml/Panel/Indicators/MenuItemFactory.qml:593
193msgid "Finished"
195msgid "Finished"
194−msgstr ""
196+msgstr "پایان یافت"
195
197
196#: qml/Panel/Indicators/MenuItemFactory.qml:595
198#: qml/Panel/Indicators/MenuItemFactory.qml:595
197msgid "Failed, tap to retry"
199msgid "Failed, tap to retry"
198−msgstr ""
200+msgstr "شکست خورد، برای تلاش دوباره ضربه بزنید"
199
201
200#: qml/Panel/SearchIndicator.qml:27
202#: qml/Panel/SearchIndicator.qml:27
201msgid "Search"
203msgid "Search"
202−msgstr ""
204+msgstr "جست‌وجو"
203
205
204#: qml/Shell.qml:359
206#: qml/Shell.qml:359
205#, qt-format
207#, qt-format
206msgid "Please enter %1"
208msgid "Please enter %1"
207−msgstr ""
209+msgstr "لطفا %1 را وارد کنید"
po/hu.po+8−8
@@ -8,14 +8,14 @@ msgstr ""
8"Project-Id-Version: unity\n"
8"Project-Id-Version: unity\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2014-07-11 14:59+0200\n"
10"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"
12"Last-Translator: Richard Somlói <ricsipontaz@gmail.com>\n"
12"Last-Translator: Richard Somlói <ricsipontaz@gmail.com>\n"
13"Language-Team: Hungarian <hu@li.org>\n"
13"Language-Team: Hungarian <hu@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-07-28 06:44+0000\n"
17+"X-Launchpad-Export-Date: 2014-08-04 06:32+0000\n"
18−"X-Generator: Launchpad (build 17131)\n"
18+"X-Generator: Launchpad (build 17147)\n"
19
19
20#: plugins/Unity/Launcher/launcheritem.cpp:43
20#: plugins/Unity/Launcher/launcheritem.cpp:43
21#: plugins/Unity/Launcher/launcheritem.cpp:73
21#: plugins/Unity/Launcher/launcheritem.cpp:73
@@ -44,7 +44,7 @@ msgstr "Felső szél"
44
44
45#: qml/Components/EdgeDemo.qml:146
45#: qml/Components/EdgeDemo.qml:146
46msgid "Try swiping from the top edge to access the indicators"
46msgid "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"
48
48
49#: qml/Components/EdgeDemo.qml:171
49#: qml/Components/EdgeDemo.qml:171
50msgid "Close"
50msgid "Close"
@@ -82,16 +82,16 @@ msgstr "Vészhívás"
82
82
83#: qml/Components/Lockscreen.qml:231
83#: qml/Components/Lockscreen.qml:231
84msgid "OK"
84msgid "OK"
85−msgstr "OK"
85+msgstr "Oké"
86
86
87#: qml/Components/PassphraseLockscreen.qml:60
87#: qml/Components/PassphraseLockscreen.qml:60
88#, qt-format
88#, qt-format
89msgid "Hello %1"
89msgid "Hello %1"
90−msgstr "Szia %1"
90+msgstr "Üdv %1"
91
91
92#: qml/Components/PassphraseLockscreen.qml:60
92#: qml/Components/PassphraseLockscreen.qml:60
93msgid "Hello"
93msgid "Hello"
94−msgstr "Szia"
94+msgstr "Üdv"
95
95
96#: qml/Components/PinLockscreen.qml:198
96#: qml/Components/PinLockscreen.qml:198
97msgid "CANCEL"
97msgid "CANCEL"
@@ -127,7 +127,7 @@ msgstr "Kevesebb…"
127
127
128#: qml/Dash/Previews/PreviewRatingInput.qml:81
128#: qml/Dash/Previews/PreviewRatingInput.qml:81
129msgid "Rate this"
129msgid "Rate this"
130−msgstr "Osztályzás"
130+msgstr "Értékelje"
131
131
132#: qml/Dash/Previews/PreviewRatingInput.qml:126
132#: qml/Dash/Previews/PreviewRatingInput.qml:126
133msgid "Add a review"
133msgid "Add a review"
po/is.po+6−6
@@ -10,14 +10,14 @@ msgstr ""
10"Project-Id-Version: po_unity8-is\n"
10"Project-Id-Version: po_unity8-is\n"
11"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
11"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
12"POT-Creation-Date: 2014-07-11 14:59+0200\n"
12"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"
14"Last-Translator: Sveinn í Felli <sv1@fellsnet.is>\n"
14"Last-Translator: Sveinn í Felli <sv1@fellsnet.is>\n"
15"Language-Team: Icelandic <translation-team-is@lists.sourceforge.net>\n"
15"Language-Team: Icelandic <translation-team-is@lists.sourceforge.net>\n"
16"MIME-Version: 1.0\n"
16"MIME-Version: 1.0\n"
17"Content-Type: text/plain; charset=UTF-8\n"
17"Content-Type: text/plain; charset=UTF-8\n"
18"Content-Transfer-Encoding: 8bit\n"
18"Content-Transfer-Encoding: 8bit\n"
19−"X-Launchpad-Export-Date: 2014-07-26 07:16+0000\n"
19+"X-Launchpad-Export-Date: 2014-08-01 07:14+0000\n"
20−"X-Generator: Launchpad (build 17131)\n"
20+"X-Generator: Launchpad (build 17141)\n"
21"Language: is\n"
21"Language: is\n"
22
22
23#: plugins/Unity/Launcher/launcheritem.cpp:43
23#: plugins/Unity/Launcher/launcheritem.cpp:43
@@ -117,7 +117,7 @@ msgstr "Nýlegt"
117
117
118#: qml/Dash/GenericScopeView.qml:330
118#: qml/Dash/GenericScopeView.qml:330
119msgid "See all"
119msgid "See all"
120−msgstr ""
120+msgstr "Sjá allt"
121
121
122#: qml/Dash/Previews/PreviewActionCombo.qml:34
122#: qml/Dash/Previews/PreviewActionCombo.qml:34
123msgid "More..."
123msgid "More..."
@@ -173,11 +173,11 @@ msgstr "Settu inn PIN fyrir SIM"
173
173
174#: qml/Panel/ActiveCallHint.qml:77
174#: qml/Panel/ActiveCallHint.qml:77
175msgid "Tap to return to call..."
175msgid "Tap to return to call..."
176−msgstr ""
176+msgstr "Slá til að svara símtali..."
177
177
178#: qml/Panel/ActiveCallHint.qml:90
178#: qml/Panel/ActiveCallHint.qml:90
179msgid "Conference"
179msgid "Conference"
180−msgstr ""
180+msgstr "Ráðstefna"
181
181
182#: qml/Panel/Indicators/MenuItemFactory.qml:583
182#: qml/Panel/Indicators/MenuItemFactory.qml:583
183msgid "In queue…"
183msgid "In queue…"
po/km.po+7−7
@@ -8,14 +8,14 @@ msgstr ""
8"Project-Id-Version: unity8\n"
8"Project-Id-Version: unity8\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2014-07-11 14:59+0200\n"
10"POT-Creation-Date: 2014-07-11 14:59+0200\n"
11−"PO-Revision-Date: 2014-07-16 01:37+0000\n"
11+"PO-Revision-Date: 2014-08-01 04:02+0000\n"
12−"Last-Translator: Khoem Sokhem <khoemsokhem@khmeros.info>\n"
12+"Last-Translator: Sok Sophea <sksophea@gmail.com>\n"
13"Language-Team: Khmer <km@li.org>\n"
13"Language-Team: Khmer <km@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-07-26 07:16+0000\n"
17+"X-Launchpad-Export-Date: 2014-08-02 06:39+0000\n"
18−"X-Generator: Launchpad (build 17131)\n"
18+"X-Generator: Launchpad (build 17147)\n"
19
19
20#: plugins/Unity/Launcher/launcheritem.cpp:43
20#: plugins/Unity/Launcher/launcheritem.cpp:43
21#: plugins/Unity/Launcher/launcheritem.cpp:73
21#: plugins/Unity/Launcher/launcheritem.cpp:73
@@ -115,7 +115,7 @@ msgstr "បច្ចុប្បន្ន"
115
115
116#: qml/Dash/GenericScopeView.qml:330
116#: qml/Dash/GenericScopeView.qml:330
117msgid "See all"
117msgid "See all"
118−msgstr ""
118+msgstr "មើល​ទាំងអស់"
119
119
120#: qml/Dash/Previews/PreviewActionCombo.qml:34
120#: qml/Dash/Previews/PreviewActionCombo.qml:34
121msgid "More..."
121msgid "More..."
@@ -171,11 +171,11 @@ msgstr "បញ្ចូល​កូដ PIN របស់​ស៊ីមកាត"
171
171
172#: qml/Panel/ActiveCallHint.qml:77
172#: qml/Panel/ActiveCallHint.qml:77
173msgid "Tap to return to call..."
173msgid "Tap to return to call..."
174−msgstr ""
174+msgstr "ប៉ះ​ដើម្បី​ត្រឡប់​ទៅ​ការ​ហៅ..."
175
175
176#: qml/Panel/ActiveCallHint.qml:90
176#: qml/Panel/ActiveCallHint.qml:90
177msgid "Conference"
177msgid "Conference"
178−msgstr ""
178+msgstr "សន្និសីទ"
179
179
180#: qml/Panel/Indicators/MenuItemFactory.qml:583
180#: qml/Panel/Indicators/MenuItemFactory.qml:583
181msgid "In queue…"
181msgid "In queue…"
po/pl.po+7−7
@@ -8,14 +8,14 @@ msgstr ""
8"Project-Id-Version: unity\n"
8"Project-Id-Version: unity\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2014-07-11 14:59+0200\n"
10"POT-Creation-Date: 2014-07-11 14:59+0200\n"
11−"PO-Revision-Date: 2014-07-19 14:10+0000\n"
11+"PO-Revision-Date: 2014-07-31 13:51+0000\n"
12−"Last-Translator: GTriderXC <gtriderxc@yahoo.com>\n"
12+"Last-Translator: Michał Radwański <radwanski.michal@outlook.com>\n"
13"Language-Team: Polish <pl@li.org>\n"
13"Language-Team: Polish <pl@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-07-26 07:17+0000\n"
17+"X-Launchpad-Export-Date: 2014-08-01 07:14+0000\n"
18−"X-Generator: Launchpad (build 17131)\n"
18+"X-Generator: Launchpad (build 17141)\n"
19
19
20#: plugins/Unity/Launcher/launcheritem.cpp:43
20#: plugins/Unity/Launcher/launcheritem.cpp:43
21#: plugins/Unity/Launcher/launcheritem.cpp:73
21#: plugins/Unity/Launcher/launcheritem.cpp:73
@@ -115,7 +115,7 @@ msgstr "Ostatnio używane"
115
115
116#: qml/Dash/GenericScopeView.qml:330
116#: qml/Dash/GenericScopeView.qml:330
117msgid "See all"
117msgid "See all"
118−msgstr ""
118+msgstr "Zobacz wszystko"
119
119
120#: qml/Dash/Previews/PreviewActionCombo.qml:34
120#: qml/Dash/Previews/PreviewActionCombo.qml:34
121msgid "More..."
121msgid "More..."
@@ -171,11 +171,11 @@ msgstr "Wprowadź kod PIN karty SIM"
171
171
172#: qml/Panel/ActiveCallHint.qml:77
172#: qml/Panel/ActiveCallHint.qml:77
173msgid "Tap to return to call..."
173msgid "Tap to return to call..."
174−msgstr ""
174+msgstr "Dotknij aby powrócić do połączenia..."
175
175
176#: qml/Panel/ActiveCallHint.qml:90
176#: qml/Panel/ActiveCallHint.qml:90
177msgid "Conference"
177msgid "Conference"
178−msgstr ""
178+msgstr "Konferencja"
179
179
180#: qml/Panel/Indicators/MenuItemFactory.qml:583
180#: qml/Panel/Indicators/MenuItemFactory.qml:583
181msgid "In queue…"
181msgid "In queue…"
po/ru.po+18−18
@@ -8,23 +8,23 @@ msgstr ""
8"Project-Id-Version: unity\n"
8"Project-Id-Version: unity\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2014-07-11 14:59+0200\n"
10"POT-Creation-Date: 2014-07-11 14:59+0200\n"
11−"PO-Revision-Date: 2014-05-15 12:57+0000\n"
11+"PO-Revision-Date: 2014-07-31 09:39+0000\n"
12−"Last-Translator: Zonov Roman <Unknown>\n"
12+"Last-Translator: Jay ZDLin <Unknown>\n"
13"Language-Team: Russian <ru@li.org>\n"
13"Language-Team: Russian <ru@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-07-26 07:17+0000\n"
17+"X-Launchpad-Export-Date: 2014-08-01 07:14+0000\n"
18−"X-Generator: Launchpad (build 17131)\n"
18+"X-Generator: Launchpad (build 17141)\n"
19
19
20#: plugins/Unity/Launcher/launcheritem.cpp:43
20#: plugins/Unity/Launcher/launcheritem.cpp:43
21#: plugins/Unity/Launcher/launcheritem.cpp:73
21#: plugins/Unity/Launcher/launcheritem.cpp:73
22msgid "Pin shortcut"
22msgid "Pin shortcut"
23−msgstr ""
23+msgstr "Закрепить ярлык"
24
24
25#: plugins/Unity/Launcher/launcheritem.cpp:73
25#: plugins/Unity/Launcher/launcheritem.cpp:73
26msgid "Unpin shortcut"
26msgid "Unpin shortcut"
27−msgstr ""
27+msgstr "Изъять ярлык"
28
28
29#: qml/Components/EdgeDemoOverlay.qml:151
29#: qml/Components/EdgeDemoOverlay.qml:151
30msgid "Skip intro"
30msgid "Skip intro"
@@ -85,7 +85,7 @@ msgstr "Экстренный вызов"
85
85
86#: qml/Components/Lockscreen.qml:231
86#: qml/Components/Lockscreen.qml:231
87msgid "OK"
87msgid "OK"
88−msgstr ""
88+msgstr "ОК"
89
89
90#: qml/Components/PassphraseLockscreen.qml:60
90#: qml/Components/PassphraseLockscreen.qml:60
91#, qt-format
91#, qt-format
@@ -98,11 +98,11 @@ msgstr "Привет"
98
98
99#: qml/Components/PinLockscreen.qml:198
99#: qml/Components/PinLockscreen.qml:198
100msgid "CANCEL"
100msgid "CANCEL"
101−msgstr ""
101+msgstr "ОТМЕНИТЬ"
102
102
103#: qml/Components/PinLockscreen.qml:216
103#: qml/Components/PinLockscreen.qml:216
104msgid "DONE"
104msgid "DONE"
105−msgstr ""
105+msgstr "ГОТОВО"
106
106
107#: qml/Components/SeeMore.qml:35
107#: qml/Components/SeeMore.qml:35
108msgid "See more"
108msgid "See more"
@@ -118,7 +118,7 @@ msgstr "Недавние"
118
118
119#: qml/Dash/GenericScopeView.qml:330
119#: qml/Dash/GenericScopeView.qml:330
120msgid "See all"
120msgid "See all"
121−msgstr ""
121+msgstr "Показать всё"
122
122
123#: qml/Dash/Previews/PreviewActionCombo.qml:34
123#: qml/Dash/Previews/PreviewActionCombo.qml:34
124msgid "More..."
124msgid "More..."
@@ -174,35 +174,35 @@ msgstr "Введите PIN код"
174
174
175#: qml/Panel/ActiveCallHint.qml:77
175#: qml/Panel/ActiveCallHint.qml:77
176msgid "Tap to return to call..."
176msgid "Tap to return to call..."
177−msgstr ""
177+msgstr "Коснитесь для возврата к вызову..."
178
178
179#: qml/Panel/ActiveCallHint.qml:90
179#: qml/Panel/ActiveCallHint.qml:90
180msgid "Conference"
180msgid "Conference"
181−msgstr ""
181+msgstr "Конференция"
182
182
183#: qml/Panel/Indicators/MenuItemFactory.qml:583
183#: qml/Panel/Indicators/MenuItemFactory.qml:583
184msgid "In queue…"
184msgid "In queue…"
185−msgstr ""
185+msgstr "В очереди..."
186
186
187#: qml/Panel/Indicators/MenuItemFactory.qml:587
187#: qml/Panel/Indicators/MenuItemFactory.qml:587
188msgid "Downloading"
188msgid "Downloading"
189−msgstr ""
189+msgstr "Загрузка"
190
190
191#: qml/Panel/Indicators/MenuItemFactory.qml:589
191#: qml/Panel/Indicators/MenuItemFactory.qml:589
192msgid "Paused, tap to resume"
192msgid "Paused, tap to resume"
193−msgstr ""
193+msgstr "Приостановлено, коснитесь для возобновления"
194
194
195#: qml/Panel/Indicators/MenuItemFactory.qml:591
195#: qml/Panel/Indicators/MenuItemFactory.qml:591
196msgid "Canceled"
196msgid "Canceled"
197−msgstr ""
197+msgstr "Отменено"
198
198
199#: qml/Panel/Indicators/MenuItemFactory.qml:593
199#: qml/Panel/Indicators/MenuItemFactory.qml:593
200msgid "Finished"
200msgid "Finished"
201−msgstr ""
201+msgstr "Завершено"
202
202
203#: qml/Panel/Indicators/MenuItemFactory.qml:595
203#: qml/Panel/Indicators/MenuItemFactory.qml:595
204msgid "Failed, tap to retry"
204msgid "Failed, tap to retry"
205−msgstr ""
205+msgstr "Сбой, коснитесь для повтора"
206
206
207#: qml/Panel/SearchIndicator.qml:27
207#: qml/Panel/SearchIndicator.qml:27
208msgid "Search"
208msgid "Search"
po/sv.po+6−6
@@ -8,14 +8,14 @@ msgstr ""
8"Project-Id-Version: unity\n"
8"Project-Id-Version: unity\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2014-07-11 14:59+0200\n"
10"POT-Creation-Date: 2014-07-11 14:59+0200\n"
11−"PO-Revision-Date: 2014-07-25 14:27+0000\n"
11+"PO-Revision-Date: 2014-07-31 08:10+0000\n"
12−"Last-Translator: Josef Andersson <Unknown>\n"
12+"Last-Translator: Peter Hultqvist <phq@silentorbit.com>\n"
13"Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n"
13"Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-07-26 07:17+0000\n"
17+"X-Launchpad-Export-Date: 2014-08-01 07:14+0000\n"
18−"X-Generator: Launchpad (build 17131)\n"
18+"X-Generator: Launchpad (build 17141)\n"
19"Language: sv\n"
19"Language: sv\n"
20
20
21#: plugins/Unity/Launcher/launcheritem.cpp:43
21#: plugins/Unity/Launcher/launcheritem.cpp:43
@@ -102,11 +102,11 @@ msgstr "KLAR"
102
102
103#: qml/Components/SeeMore.qml:35
103#: qml/Components/SeeMore.qml:35
104msgid "See more"
104msgid "See more"
105−msgstr "Se mer"
105+msgstr "Visa mer"
106
106
107#: qml/Components/SeeMore.qml:58 qml/Dash/GenericScopeView.qml:330
107#: qml/Components/SeeMore.qml:58 qml/Dash/GenericScopeView.qml:330
108msgid "See less"
108msgid "See less"
109−msgstr "Se mindre"
109+msgstr "Visa mindre"
110
110
111#: qml/Dash/DashApps.qml:37
111#: qml/Dash/DashApps.qml:37
112msgid "Recent"
112msgid "Recent"
po/ug.po+1−1
@@ -14,7 +14,7 @@ msgstr ""
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-07-29 06:54+0000\n"
17+"X-Launchpad-Export-Date: 2014-07-31 07:15+0000\n"
18"X-Generator: Launchpad (build 17131)\n"
18"X-Generator: Launchpad (build 17131)\n"
19
19
20#: plugins/Unity/Launcher/launcheritem.cpp:43
20#: plugins/Unity/Launcher/launcheritem.cpp:43
po/uk.po+4−4
@@ -8,13 +8,13 @@ msgstr ""
8"Project-Id-Version: unity\n"
8"Project-Id-Version: unity\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2014-07-11 14:59+0200\n"
10"POT-Creation-Date: 2014-07-11 14:59+0200\n"
11−"PO-Revision-Date: 2014-07-28 21:35+0000\n"
11+"PO-Revision-Date: 2014-07-29 07:53+0000\n"
12−"Last-Translator: Ярослав Смук <Smuk.Slavko@yandex.ua>\n"
12+"Last-Translator: Yuri Chornoivan <yurchor@gmail.com>\n"
13"Language-Team: Ukrainian <uk@li.org>\n"
13"Language-Team: Ukrainian <uk@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-07-29 06:54+0000\n"
17+"X-Launchpad-Export-Date: 2014-07-31 07:15+0000\n"
18"X-Generator: Launchpad (build 17131)\n"
18"X-Generator: Launchpad (build 17131)\n"
19
19
20#: plugins/Unity/Launcher/launcheritem.cpp:43
20#: plugins/Unity/Launcher/launcheritem.cpp:43
@@ -172,7 +172,7 @@ msgstr "Введіть PIN-код SIM-картки"
172
172
173#: qml/Panel/ActiveCallHint.qml:77
173#: qml/Panel/ActiveCallHint.qml:77
174msgid "Tap to return to call..."
174msgid "Tap to return to call..."
175−msgstr ""
175+msgstr "Торкніться, щоб повернутися до дзвінка…"
176
176
177#: qml/Panel/ActiveCallHint.qml:90
177#: qml/Panel/ActiveCallHint.qml:90
178msgid "Conference"
178msgid "Conference"
po/zh_CN.po+1−1
@@ -14,7 +14,7 @@ msgstr ""
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-07-29 06:54+0000\n"
17+"X-Launchpad-Export-Date: 2014-07-31 07:15+0000\n"
18"X-Generator: Launchpad (build 17131)\n"
18"X-Generator: Launchpad (build 17131)\n"
19
19
20#: plugins/Unity/Launcher/launcheritem.cpp:43
20#: 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 {
117 dragging = false
117 dragging = false
118 __pressedPosition = Qt.point(mouse.x, mouse.y)
118 __pressedPosition = Qt.point(mouse.x, mouse.y)
119 }
119 }
120+
121+ onCanceled: {
122+ dragging = false
123+ }
120}
124}
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 {
44 signal backClicked()
44 signal backClicked()
45
45
46 onScopeStyleChanged: refreshLogo()
46 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+ }
47
53
48 function triggerSearch() {
54 function triggerSearch() {
49 if (searchEntryEnabled) {
55 if (searchEntryEnabled) {
@@ -52,6 +58,12 @@ Item {
52 }
58 }
53 }
59 }
54
60
61+ function closePopup() {
62+ if (headerContainer.popover != null) {
63+ PopupUtils.close(headerContainer.popover);
64+ }
65+ }
66+
55 function resetSearch(keepFocus) {
67 function resetSearch(keepFocus) {
56 if (searchHistory) {
68 if (searchHistory) {
57 searchHistory.addQuery(searchTextField.text);
69 searchHistory.addQuery(searchTextField.text);
@@ -60,9 +72,7 @@ Item {
60 unfocus();
72 unfocus();
61 }
73 }
62 searchTextField.text = "";
74 searchTextField.text = "";
63− if (headerContainer.popover != null) {
75+ closePopup();
64− PopupUtils.close(headerContainer.popover);
65− }
66 }
76 }
67
77
68 function unfocus() {
78 function unfocus() {
@@ -103,9 +113,7 @@ Item {
103 anchors { fill: parent; margins: units.gu(1); bottomMargin: units.gu(3) + bottomContainer.height }
113 anchors { fill: parent; margins: units.gu(1); bottomMargin: units.gu(3) + bottomContainer.height }
104 visible: headerContainer.showSearch
114 visible: headerContainer.showSearch
105 onPressed: {
115 onPressed: {
106− if (headerContainer.popover) {
116+ closePopup();
107− PopupUtils.close(headerContainer.popover);
108− }
109 if (!searchTextField.text) {
117 if (!searchTextField.text) {
110 headerContainer.showSearch = false;
118 headerContainer.showSearch = false;
111 }
119 }
@@ -117,7 +125,7 @@ Item {
117 Flickable {
125 Flickable {
118 id: headerContainer
126 id: headerContainer
119 objectName: "headerContainer"
127 objectName: "headerContainer"
120− clip: true
128+ clip: openSearchAnimation.running
121 anchors { left: parent.left; top: parent.top; right: parent.right }
129 anchors { left: parent.left; top: parent.top; right: parent.right }
122 height: units.gu(6.5)
130 height: units.gu(6.5)
123 contentHeight: headersColumn.height
131 contentHeight: headersColumn.height
@@ -155,6 +163,7 @@ Item {
155 anchors { left: parent.left; right: parent.right }
163 anchors { left: parent.left; right: parent.right }
156 height: headerContainer.height
164 height: headerContainer.height
157 contentHeight: height
165 contentHeight: height
166+ opacity: headerContainer.clip || headerContainer.showSearch ? 1 : 0 // setting visible false cause column to relayout
158 separatorSource: ""
167 separatorSource: ""
159 // Required to keep PageHeadStyle noise down as it expects the Page's properties around.
168 // Required to keep PageHeadStyle noise down as it expects the Page's properties around.
160 property var styledItem: searchHeader
169 property var styledItem: searchHeader
@@ -171,6 +180,7 @@ Item {
171 property var contents: TextField {
180 property var contents: TextField {
172 id: searchTextField
181 id: searchTextField
173 objectName: "searchTextField"
182 objectName: "searchTextField"
183+ inputMethodHints: Qt.ImhNoPredictiveText
174 hasClearButton: false
184 hasClearButton: false
175 anchors {
185 anchors {
176 fill: parent
186 fill: parent
@@ -220,6 +230,12 @@ Item {
220 root.openSearchHistory();
230 root.openSearchHistory();
221 }
231 }
222 }
232 }
233+
234+ onTextChanged: {
235+ if (text != "") {
236+ closePopup();
237+ }
238+ }
223 }
239 }
224 }
240 }
225
241
@@ -229,6 +245,7 @@ Item {
229 anchors { left: parent.left; right: parent.right }
245 anchors { left: parent.left; right: parent.right }
230 height: headerContainer.height
246 height: headerContainer.height
231 contentHeight: height
247 contentHeight: height
248+ opacity: headerContainer.clip || !headerContainer.showSearch ? 1 : 0 // setting visible false cause column to relayout
232 separatorSource: ""
249 separatorSource: ""
233 textColor: root.scopeStyle ? root.scopeStyle.headerForeground : "grey"
250 textColor: root.scopeStyle ? root.scopeStyle.headerForeground : "grey"
234 property var styledItem: header
251 property var styledItem: header
@@ -296,6 +313,7 @@ Item {
296
313
297 Repeater {
314 Repeater {
298 id: recentSearches
315 id: recentSearches
316+ objectName: "recentSearches"
299 model: searchHistory
317 model: searchHistory
300
318
301 delegate: Standard {
319 delegate: Standard {
@@ -304,7 +322,8 @@ Item {
304 onClicked: {
322 onClicked: {
305 searchHistory.addQuery(text);
323 searchHistory.addQuery(text);
306 searchTextField.text = text;
324 searchTextField.text = text;
307− PopupUtils.close(popover);
325+ closePopup();
326+ unfocus();
308 }
327 }
309 }
328 }
310 }
329 }
qml/Components/PassphraseLockscreen.qml+3
@@ -31,8 +31,10 @@ Item {
31
31
32 function clear(playAnimation) {
32 function clear(playAnimation) {
33 pinentryField.text = "";
33 pinentryField.text = "";
34+ pinentryField.enabled = true
34 if (playAnimation) {
35 if (playAnimation) {
35 wrongPasswordAnimation.start();
36 wrongPasswordAnimation.start();
37+ pinentryField.forceActiveFocus();
36 } else {
38 } else {
37 pinentryField.focus = false
39 pinentryField.focus = false
38 }
40 }
@@ -78,6 +80,7 @@ Item {
78
80
79 onAccepted: {
81 onAccepted: {
80 if (pinentryField.text) {
82 if (pinentryField.text) {
83+ pinentryField.enabled = false;
81 root.entered(pinentryField.text);
84 root.entered(pinentryField.text);
82 }
85 }
83 }
86 }
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 {
165 }
165 }
166 }
166 }
167
167
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+
168 Loader {
192 Loader {
169 id: cardLoader
193 id: cardLoader
170− property var fields: ["art", "mascot", "title", "subtitle", "summary"]
194+ property var fields: ["art", "mascot", "title", "subtitle", "summary", "attributes"]
171 property var maxData: {
195 property var maxData: {
172 "art": Qt.resolvedUrl("graphics/checkers.png"),
196 "art": Qt.resolvedUrl("graphics/checkers.png"),
173 "mascot": Qt.resolvedUrl("graphics/checkers.png"),
197 "mascot": Qt.resolvedUrl("graphics/checkers.png"),
174 "title": "—\n—",
198 "title": "—\n—",
175 "subtitle": "—",
199 "subtitle": "—",
176− "summary": "—\n—\n—\n—\n—"
200+ "summary": "—\n—\n—\n—\n—",
201+ "attributes": attributesModel.model
177 }
202 }
178 sourceComponent: cardTool.cardComponent
203 sourceComponent: cardTool.cardComponent
179 onLoaded: {
204 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 {
121 Loader {
121 Loader {
122 width: ListView.view.width
122 width: ListView.view.width
123 height: ListView.view.height
123 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+ }
124 asynchronous: true
128 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"
128 objectName: scope.id + " loader"
130 objectName: scope.id + " loader"
129
131
130 readonly property bool moving: item ? item.moving : false
132 readonly property bool moving: item ? item.moving : false
qml/Dash/GenericScopeView.qml+1−10
@@ -18,7 +18,6 @@ import QtQuick 2.0
18import Ubuntu.Components 0.1
18import Ubuntu.Components 0.1
19import Utils 0.1
19import Utils 0.1
20import Unity 0.2
20import Unity 0.2
21−import Unity.Application 0.1
22import Dash 0.1
21import Dash 0.1
23import "../Components"
22import "../Components"
24import "../Components/ListItems" as ListItems
23import "../Components/ListItems" as ListItems
@@ -194,7 +193,6 @@ FocusScope {
194 switch (cardTool.categoryLayout) {
193 switch (cardTool.categoryLayout) {
195 case "carousel": return "CardCarousel.qml";
194 case "carousel": return "CardCarousel.qml";
196 case "vertical-journal": return "CardVerticalJournal.qml";
195 case "vertical-journal": return "CardVerticalJournal.qml";
197− case "running-apps": return "Apps/RunningApplicationsGrid.qml";
198 case "grid":
196 case "grid":
199 default: return "CardGrid.qml";
197 default: return "CardGrid.qml";
200 }
198 }
@@ -205,14 +203,7 @@ FocusScope {
205 item.enableHeightBehavior = scopeView.enableHeightBehaviorOnNextCreation;
203 item.enableHeightBehavior = scopeView.enableHeightBehaviorOnNextCreation;
206 scopeView.enableHeightBehaviorOnNextCreation = false;
204 scopeView.enableHeightBehaviorOnNextCreation = false;
207 }
205 }
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 })
206+ item.model = Qt.binding(function() { return results })
215− }
216 item.objectName = Qt.binding(function() { return categoryId })
207 item.objectName = Qt.binding(function() { return categoryId })
217 item.scopeStyle = scopeView.scopeStyle;
208 item.scopeStyle = scopeView.scopeStyle;
218 if (baseItem.expandable) {
209 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 {
181 bottom: parent.bottom
181 bottom: parent.bottom
182 }
182 }
183 x: -width
183 x: -width
184− opacity: (x == -width && dragArea.status === DirectionalDragArea.WaitingForTouch) ? 0 : 1
184+ visible: x > -width || dragArea.status === DirectionalDragArea.Undecided
185 model: LauncherModel
185 model: LauncherModel
186
186
187 property bool animate: true
187 property bool animate: true
qml/Launcher/LauncherDelegate.qml+9−6
@@ -70,28 +70,31 @@ Item {
70 height: root.itemHeight - units.gu(1)
70 height: root.itemHeight - units.gu(1)
71 }
71 }
72
72
73− BorderImage {
73+ UbuntuShape {
74 objectName: "countEmblem"
74 objectName: "countEmblem"
75 anchors {
75 anchors {
76 right: parent.right
76 right: parent.right
77 top: parent.top
77 top: parent.top
78 margins: units.dp(3)
78 margins: units.dp(3)
79 }
79 }
80− width: Math.min(root.itemWidth, Math.max(units.gu(3), countLabel.implicitWidth + units.gu(2.5)))
80+ width: Math.min(root.itemWidth, Math.max(units.gu(2), countLabel.implicitWidth + units.gu(1)))
81− height: units.gu(3)
81+ height: units.gu(2)
82− source: "graphics/notification.sci"
82+ color: UbuntuColors.orange
83 visible: root.count > 0
83 visible: root.count > 0
84+ borderSource: "none"
84
85
85 Label {
86 Label {
86 id: countLabel
87 id: countLabel
87 text: root.count
88 text: root.count
88 anchors.centerIn: parent
89 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)
89 width: root.itemWidth - units.gu(1)
93 width: root.itemWidth - units.gu(1)
90 horizontalAlignment: Text.AlignHCenter
94 horizontalAlignment: Text.AlignHCenter
91 elide: Text.ElideRight
95 elide: Text.ElideRight
92 color: "white"
96 color: "white"
93− fontSize: "small"
97+ fontSize: "x-small"
94− font.bold: true
95 }
98 }
96 }
99 }
97
100
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 {
37 readonly property int lockThreshold: openedHeight / 2
37 readonly property int lockThreshold: openedHeight / 2
38 property bool fullyOpened: height == openedHeight
38 property bool fullyOpened: height == openedHeight
39 property bool partiallyOpened: height > panelHeight && !fullyOpened
39 property bool partiallyOpened: height > panelHeight && !fullyOpened
40+ property bool fullyClosed: height <= panelHeight
40 property bool contentEnabled: true
41 property bool contentEnabled: true
41 property bool initalizeItem: true
42 property bool initalizeItem: true
42 readonly property alias content: menuContent
43 readonly property alias content: menuContent
@@ -187,6 +188,7 @@ Showable {
187 bottom: handle.top
188 bottom: handle.top
188 }
189 }
189 indicatorsModel: visibleIndicators.model
190 indicatorsModel: visibleIndicators.model
191+ visible: indicators.partiallyOpened || indicators.fullyOpened
190 clip: !indicators.fullyOpened
192 clip: !indicators.fullyOpened
191 activeHeader: indicators.state == "hint" || indicators.state == "reveal"
193 activeHeader: indicators.state == "hint" || indicators.state == "reveal"
192 enabled: contentEnabled
194 enabled: contentEnabled
@@ -219,6 +221,7 @@ Showable {
219 }
221 }
220 height: Math.max(Math.min(handleImage.height, indicators.height - handleImage.height), 0)
222 height: Math.max(Math.min(handleImage.height, indicators.height - handleImage.height), 0)
221 clip: height < handleImage.height
223 clip: height < handleImage.height
224+ visible: menuContent.visible
222
225
223 BorderImage {
226 BorderImage {
224 id: handleImage
227 id: handleImage
qml/Panel/Indicators/DefaultIndicatorPage.qml+1−1
@@ -126,7 +126,7 @@ IndicatorBase {
126 id: loader
126 id: loader
127 objectName: "menuItem" + index
127 objectName: "menuItem" + index
128 asynchronous: false
128 asynchronous: false
129− visible: height > 0
129+ visible: status == Loader.Ready
130
130
131 property int modelIndex: index
131 property int modelIndex: index
132
132
qml/Shell.qml+183−399
@@ -19,9 +19,9 @@ import AccountsService 0.1
19import GSettings 1.0
19import GSettings 1.0
20import Unity.Application 0.1
20import Unity.Application 0.1
21import Ubuntu.Components 0.1
21import Ubuntu.Components 0.1
22−import Ubuntu.Components.Popups 0.1
23import Ubuntu.Gestures 0.1
22import Ubuntu.Gestures 0.1
24import Unity.Launcher 0.1
23import Unity.Launcher 0.1
24+import Utils 0.1
25import LightDM 0.1 as LightDM
25import LightDM 0.1 as LightDM
26import Powerd 0.1
26import Powerd 0.1
27import SessionBroadcast 0.1
27import SessionBroadcast 0.1
@@ -31,10 +31,11 @@ import "Launcher"
31import "Panel"
31import "Panel"
32import "Components"
32import "Components"
33import "Notifications"
33import "Notifications"
34+import "Stages"
34import Unity.Notifications 1.0 as NotificationBackend
35import Unity.Notifications 1.0 as NotificationBackend
35import Unity.Session 0.1
36import Unity.Session 0.1
36
37
37−FocusScope {
38+Item {
38 id: shell
39 id: shell
39
40
40 // this is only here to select the width / height of the window if not running fullscreen
41 // this is only here to select the width / height of the window if not running fullscreen
@@ -47,7 +48,7 @@ FocusScope {
47 property url background
48 property url background
48 readonly property real panelHeight: panel.panelHeight
49 readonly property real panelHeight: panel.panelHeight
49
50
50− property bool dashShown: dash.shown
51+ property bool dashShown: dash.shown && dash.available && underlay.visible
51
52
52 property bool sideStageEnabled: shell.width >= units.gu(100)
53 property bool sideStageEnabled: shell.width >= units.gu(100)
53 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
54 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
@@ -55,14 +56,10 @@ FocusScope {
55 function activateApplication(appId) {
56 function activateApplication(appId) {
56 if (ApplicationManager.findApplication(appId)) {
57 if (ApplicationManager.findApplication(appId)) {
57 ApplicationManager.requestFocusApplication(appId);
58 ApplicationManager.requestFocusApplication(appId);
58− stages.show(true);
59− if (stages.locked && ApplicationManager.focusedApplicationId == appId) {
60− applicationsDisplayLoader.item.select(appId);
61− }
62 } else {
59 } else {
63 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
60 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
64 ApplicationManager.startApplication(appId, execFlags);
61 ApplicationManager.startApplication(appId, execFlags);
65− stages.show(false);
62+ stages.show();
66 }
63 }
67 }
64 }
68
65
@@ -89,82 +86,74 @@ FocusScope {
89 id: volumeControl
86 id: volumeControl
90 }
87 }
91
88
89+ WindowKeysFilter {
90+ // Handle but do not filter out volume keys
92− Keys.onVolumeUpPressed: volumeControl.volumeUp()
91+ Keys.onVolumeUpPressed: { volumeControl.volumeUp(); event.accepted = false; }
93− Keys.onVolumeDownPressed: volumeControl.volumeDown()
92+ Keys.onVolumeDownPressed: { volumeControl.volumeDown(); event.accepted = false; }
94
93
95− Item {
94+ Keys.onPressed: {
96− id: underlayClipper
95+ if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
97− anchors.fill: parent
96+ dialogs.onPowerKeyPressed();
98− anchors.rightMargin: stages.overlayWidth
97+ event.accepted = true;
99− clip: stages.overlayMode && !stages.painting
98+ } else {
99+ event.accepted = false;
100+ }
101+ }
100
102
101− InputFilterArea {
103+ Keys.onReleased: {
102− anchors.fill: parent
104+ if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
103− blockInput: parent.clip
105+ dialogs.onPowerKeyReleased();
106+ event.accepted = true;
107+ } else {
108+ event.accepted = false;
109+ }
104 }
110 }
111+ }
105
112
106− Item {
113+ Item {
107− id: underlay
114+ id: underlay
108− objectName: "underlay"
115+ objectName: "underlay"
109− anchors.fill: parent
116+ anchors.fill: parent
110− anchors.rightMargin: -parent.anchors.rightMargin
111
117
112− // Whether the underlay is fully covered by opaque UI elements.
118+ // Whether the underlay is fully covered by opaque UI elements.
113− property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicators.width
119+ property bool fullyCovered: (panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth)
120+ || stages.fullyShown || greeterWrapper.fullyShown
121+ visible: !fullyCovered
114
122
115− // Whether the user should see the topmost application surface (if there's one at all).
123+ Image {
116− readonly property bool applicationSurfaceShouldBeSeen: stages.shown && !stages.painting && !stages.overlayMode
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+ }
117
130
118− // NB! Application surfaces are stacked behind the shell one. So they can only be seen by the user
131+ Dash {
119− // through the translucent parts of the shell surface.
132+ id: dash
120− visible: !fullyCovered && !applicationSurfaceShouldBeSeen
133+ objectName: "dash"
121
134
122− Rectangle {
135+ available: !LightDM.Greeter.active
123− anchors.fill: parent
136+ hides: [stages, launcher, panel.indicators]
124− color: "black"
137+ shown: disappearingAnimationProgress !== 1.0 && greeterWrapper.showProgress !== 1.0 &&
125− opacity: dash.disappearingAnimationProgress
138+ !(panel.indicators.fullyOpened && !sideStageEnabled)
126− }
139+ enabled: disappearingAnimationProgress === 0.0 && greeterWrapper.showProgress === 0.0 && edgeDemo.dashEnabled
127
140
128− Image {
141+ anchors {
129− anchors.fill: dash
142+ fill: parent
130− source: shell.width > shell.height ? "Dash/graphics/paper_landscape.png" : "Dash/graphics/paper_portrait.png"
143+ topMargin: panel.panelHeight
131− fillMode: Image.PreserveAspectCrop
132− horizontalAlignment: Image.AlignRight
133− verticalAlignment: Image.AlignTop
134 }
144 }
135
145
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
146+ contentScale: 1.0 - 0.2 * disappearingAnimationProgress
151− opacity: 1.0 - disappearingAnimationProgress
147+ opacity: 1.0 - disappearingAnimationProgress
152− property real disappearingAnimationProgress: {
148+ property real disappearingAnimationProgress: stages.showProgress
153− if (stages.overlayMode) {
154− return 0;
155− } else {
156− return stages.showProgress
157− }
158− }
159
149
160− // FIXME: only necessary because stages.showProgress is not animated
150+ // FIXME: only necessary because stages.showProgress is not animated
161− Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
151+ Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
162− }
163 }
152 }
164 }
153 }
165
154
166 EdgeDragArea {
155 EdgeDragArea {
167− id: stagesDragHandle
156+ id: stagesDragArea
168 direction: Direction.Leftwards
157 direction: Direction.Leftwards
169
158
170 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
159 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
@@ -174,20 +163,20 @@ FocusScope {
174
163
175 onTouchXChanged: {
164 onTouchXChanged: {
176 if (status == DirectionalDragArea.Recognized) {
165 if (status == DirectionalDragArea.Recognized) {
177− if (ApplicationManager.count == 0) {
166+ if (ApplicationManager.empty) {
178− progress = Math.max(stages.width - stagesDragHandle.width + touchX, stages.width * .3)
167+ progress = Math.max(stages.width - stagesDragArea.width + touchX, stages.width * .3);
179 } else {
168 } else {
180− progress = stages.width - stagesDragHandle.width + touchX
169+ progress = stages.width - stagesDragArea.width + touchX;
181 }
170 }
182 }
171 }
183 }
172 }
184
173
185 onDraggingChanged: {
174 onDraggingChanged: {
186 if (!dragging) {
175 if (!dragging) {
187− if (ApplicationManager.count > 0 && progress < stages.width - units.gu(10)) {
176+ if (!ApplicationManager.empty && progress < stages.width - units.gu(10)) {
188− stages.show(true)
177+ stages.show();
189 }
178 }
190− stagesDragHandle.progress = stages.width;
179+ stagesDragArea.progress = Qt.binding(function () { return stages.width; });
191 }
180 }
192 }
181 }
193 }
182 }
@@ -198,253 +187,93 @@ FocusScope {
198 width: parent.width
187 width: parent.width
199 height: parent.height
188 height: parent.height
200
189
190+ visible: !fullyHidden && !ApplicationManager.empty
191+
201 x: {
192 x: {
202 if (shown) {
193 if (shown) {
203− if (overlayMode || locked || greeter.fakeActiveForApp !== "") {
194+ if (locked || greeter.fakeActiveForApp !== "") {
204 return 0;
195 return 0;
205 }
196 }
206− return launcher.progress
197+ return launcher.progress;
207 } else {
198 } else {
208− return stagesDragHandle.progress
199+ return stagesDragArea.progress
209 }
200 }
210 }
201 }
211−
212 Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.FastDuration } }
202 Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.FastDuration } }
213
203
214 property bool shown: false
204 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+ }
215
217
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.
216− property real showProgress: overlayMode ? 0 : MathUtils.clamp(1 - x / shell.width, 0, 1)
221+ property real showProgress: shell.width ? MathUtils.clamp(1 - x / shell.width, 0, 1) : 0
217
222
218 property bool fullyShown: x == 0
223 property bool fullyShown: x == 0
219 property bool fullyHidden: x == width
224 property bool fullyHidden: x == width
220
225
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
225 property bool locked: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.locked : false
226 property bool locked: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.locked : false
226
227
228+ // It might technically not be fullyShown but visually it just looks so.
229+ property bool roughlyFullyShown: x >= 0 && x <= units.gu(1)
230+
227− function show(focusApp) {
231+ function show() {
228 shown = true;
232 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− }
235 }
233 }
236
234
237 function hide() {
235 function hide() {
238 shown = false;
236 shown = false;
239− if (ApplicationManager.focusedApplicationId) {
240− ApplicationManager.unfocusCurrentApplication();
241− }
242 }
237 }
243
238
244 Connections {
239 Connections {
245 target: ApplicationManager
240 target: ApplicationManager
246−
247 onFocusRequested: {
241 onFocusRequested: {
248 if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== appId) {
242 if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== appId) {
249 lockscreen.show();
243 lockscreen.show();
250 }
244 }
251 greeter.hide();
245 greeter.hide();
252− stages.show(true);
246+ stages.show();
253 }
247 }
254
248
255 onFocusedApplicationIdChanged: {
249 onFocusedApplicationIdChanged: {
256 if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== ApplicationManager.focusedApplicationId) {
250 if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== ApplicationManager.focusedApplicationId) {
257 lockscreen.show();
251 lockscreen.show();
258 }
252 }
259− if (ApplicationManager.focusedApplicationId.length > 0) {
253+ panel.indicators.hide();
260− stages.show(false);
261− } else {
262− if (!stages.overlayMode) {
263− stages.hide();
264− }
265− }
266 }
254 }
267
255
268 onApplicationAdded: {
256 onApplicationAdded: {
269− stages.show(false);
257+ if (greeter.shown) {
270− }
258+ greeter.hide();
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);
418 }
259 }
419− }
420−
421− onShutdownRequested: {
422− // Display a dialog to ask the user to confirm.
423− if (!stages.dialogShown) {
260+ if (!stages.shown) {
424− stages.dialogShown = true;
261+ stages.show();
425− PopupUtils.open(shutdownDialog);
426 }
262 }
427 }
263 }
428
264
429− onRebootRequested: {
265+ onEmptyChanged: {
430− // Display a dialog to ask the user to confirm.
266+ if (ApplicationManager.empty) {
431− if (!stages.dialogShown) {
267+ stages.hide();
432− stages.dialogShown = true;
433− PopupUtils.open(rebootDialog);
434 }
268 }
435 }
269 }
436−
437− onLogoutReady: {
438− closeAllApps();
439− Qt.quit();
440− }
441 }
270 }
442
271
443 Loader {
272 Loader {
444 id: applicationsDisplayLoader
273 id: applicationsDisplayLoader
445 anchors.fill: parent
274 anchors.fill: parent
446
275
447− source: shell.sideStageEnabled ? "Stages/StageWithSideStage.qml" : "Stages/PhoneStage.qml"
276+ source: shell.sideStageEnabled ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
448
277
449 Binding {
278 Binding {
450 target: applicationsDisplayLoader.item
279 target: applicationsDisplayLoader.item
@@ -453,18 +282,20 @@ FocusScope {
453 }
282 }
454 Binding {
283 Binding {
455 target: applicationsDisplayLoader.item
284 target: applicationsDisplayLoader.item
456− property: "moving"
285+ property: "dragAreaWidth"
457− value: !stages.fullyShown
286+ value: shell.edgeSize
458 }
287 }
459 Binding {
288 Binding {
460 target: applicationsDisplayLoader.item
289 target: applicationsDisplayLoader.item
461− property: "shown"
290+ property: "maximizedAppTopMargin"
462− value: stages.shown
291+ // Not just using panel.panelHeight as that changes depending on the focused app.
292+ value: panel.indicators.panelHeight
463 }
293 }
464 Binding {
294 Binding {
465 target: applicationsDisplayLoader.item
295 target: applicationsDisplayLoader.item
466− property: "dragAreaWidth"
296+ property: "interactive"
467− value: shell.edgeSize
297+ value: stages.roughlyFullyShown && !greeter.shown && !lockscreen.shown
298+ && panel.indicators.fullyClosed
468 }
299 }
469 Binding {
300 Binding {
470 target: applicationsDisplayLoader.item
301 target: applicationsDisplayLoader.item
@@ -474,6 +305,33 @@ FocusScope {
474 }
305 }
475 }
306 }
476
307
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+
477 Lockscreen {
335 Lockscreen {
478 id: lockscreen
336 id: lockscreen
479 objectName: "lockscreen"
337 objectName: "lockscreen"
@@ -486,10 +344,11 @@ FocusScope {
486 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
344 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
487 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
345 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
488 y: panel.panelHeight
346 y: panel.panelHeight
489− x: required ? 0 : - width
347+ visible: required
490 width: parent.width
348 width: parent.width
491 height: parent.height - panel.panelHeight
349 height: parent.height - panel.panelHeight
492 background: shell.background
350 background: shell.background
351+ alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
493 minPinLength: 4
352 minPinLength: 4
494 maxPinLength: 4
353 maxPinLength: 4
495
354
@@ -504,7 +363,7 @@ FocusScope {
504 onShownChanged: if (shown) greeter.fakeActiveForApp = ""
363 onShownChanged: if (shown) greeter.fakeActiveForApp = ""
505
364
506 Component.onCompleted: {
365 Component.onCompleted: {
507− if (LightDM.Users.count == 1) {
366+ if (greeter.narrowMode) {
508 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
367 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
509 }
368 }
510 }
369 }
@@ -516,14 +375,17 @@ FocusScope {
516 onShowGreeter: greeter.show()
375 onShowGreeter: greeter.show()
517
376
518 onShowPrompt: {
377 onShowPrompt: {
519− if (LightDM.Users.count == 1) {
378+ if (greeter.narrowMode) {
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);
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();
527 lockscreen.show();
389 lockscreen.show();
528 }
390 }
529 }
391 }
@@ -537,6 +399,9 @@ FocusScope {
537 greeter.login();
399 greeter.login();
538 } else {
400 } else {
539 lockscreen.clear(true);
401 lockscreen.clear(true);
402+ if (greeter.narrowMode) {
403+ LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
404+ }
540 }
405 }
541 }
406 }
542 }
407 }
@@ -566,6 +431,7 @@ FocusScope {
566 StandardAnimation {}
431 StandardAnimation {}
567 }
432 }
568
433
434+ property bool fullyShown: showProgress === 1.0
569 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
435 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
570 onShowProgressChanged: if (LightDM.Greeter.promptless && showProgress === 0) greeter.login()
436 onShowProgressChanged: if (LightDM.Greeter.promptless && showProgress === 0) greeter.login()
571
437
@@ -590,25 +456,24 @@ FocusScope {
590
456
591 function login() {
457 function login() {
592 enabled = false;
458 enabled = false;
593− LightDM.Greeter.startSessionSync();
459+ if (LightDM.Greeter.startSessionSync()) {
594− sessionStarted();
460+ sessionStarted();
595− greeter.hide();
461+ greeter.hide();
596− lockscreen.hide();
462+ lockscreen.hide();
597− launcher.hide();
463+ launcher.hide();
464+ }
598 enabled = true;
465 enabled = true;
599 }
466 }
600
467
601 onShownChanged: {
468 onShownChanged: {
602 if (shown) {
469 if (shown) {
603− lockscreen.reset();
470+ if (greeter.narrowMode) {
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) {
610 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
471 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
611 }
472 }
473+ if (!LightDM.Greeter.promptless) {
474+ lockscreen.reset();
475+ lockscreen.show();
476+ }
612 greeter.fakeActiveForApp = "";
477 greeter.fakeActiveForApp = "";
613 greeter.forceActiveFocus();
478 greeter.forceActiveFocus();
614 }
479 }
@@ -632,12 +497,6 @@ FocusScope {
632 }
497 }
633 }
498 }
634
499
635− InputFilterArea {
636− anchors.fill: parent
637− blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
638− || panel.indicators.shown
639− }
640−
641 Connections {
500 Connections {
642 id: powerConnection
501 id: powerConnection
643 target: Powerd
502 target: Powerd
@@ -680,7 +539,7 @@ FocusScope {
680 return;
539 return;
681 }
540 }
682
541
683− if (stages.shown && !stages.overlayMode && !stages.locked) {
542+ if (!stages.locked) {
684 stages.hide();
543 stages.hide();
685 launcher.fadeOut();
544 launcher.fadeOut();
686 } else {
545 } else {
@@ -695,6 +554,7 @@ FocusScope {
695
554
696 Item {
555 Item {
697 id: overlay
556 id: overlay
557+ z: 10
698
558
699 anchors.fill: parent
559 anchors.fill: parent
700
560
@@ -709,29 +569,13 @@ FocusScope {
709 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
569 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
710 panelHeight: units.gu(3)
570 panelHeight: units.gu(3)
711 }
571 }
712− property string focusedAppId: ApplicationManager.focusedApplicationId
713− property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
714− fullscreenMode: (focusedApplication && stages.fullscreen && !LightDM.Greeter.active) || greeter.fakeActiveForApp !== ""
715
572
716− InputFilterArea {
573+ property bool topmostApplicationIsFullscreen:
717− anchors {
574+ ApplicationManager.focusedApplicationId &&
718− top: parent.top
575+ ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
719− left: parent.left
720− right: parent.right
721− }
722− height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
723− blockInput: true
724− }
725− }
726
576
727− InputFilterArea {
577+ fullscreenMode: (stages.roughlyFullyShown && topmostApplicationIsFullscreen
728− blockInput: launcher.shown
578+ && !LightDM.Greeter.active) || greeter.fakeActiveForApp !== ""
729− anchors {
730− top: parent.top
731− bottom: parent.bottom
732− left: parent.left
733− }
734− width: launcher.width
735 }
579 }
736
580
737 Launcher {
581 Launcher {
@@ -774,11 +618,6 @@ FocusScope {
774 MouseArea {
618 MouseArea {
775 anchors.fill: parent
619 anchors.fill: parent
776 }
620 }
777−
778− InputFilterArea {
779− anchors.fill: parent
780− blockInput: modalNotificationBackground.visible
781− }
782 }
621 }
783
622
784 Notifications {
623 Notifications {
@@ -804,36 +643,7 @@ FocusScope {
804 PropertyChanges { target: notifications; width: units.gu(38) }
643 PropertyChanges { target: notifications; width: units.gu(38) }
805 }
644 }
806 ]
645 ]
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
834 }
646 }
835− width: shell.edgeSize
836− blockInput: true
837 }
647 }
838
648
839 Binding {
649 Binding {
@@ -842,24 +652,22 @@ FocusScope {
842 value: "unity8"
652 value: "unity8"
843 }
653 }
844
654
845− OSKController {
655+ Dialogs {
846− anchors.topMargin: panel.panelHeight
656+ id: dialogs
847− anchors.fill: parent // as needs to know the geometry of the shell
657+ anchors.fill: parent
848− }
658+ z: overlay.z + 10
849−
659+ onPowerOffClicked: {
850− //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
660+ shutdownFadeOutRectangle.enabled = true;
851− MouseArea {
661+ shutdownFadeOutRectangle.visible = true;
852− anchors.bottom: parent.bottom
662+ shutdownFadeOut.start();
853− anchors.left: parent.left
663+ }
854− anchors.right: parent.right
855− height: ApplicationManager.keyboardVisible ? ApplicationManager.keyboardHeight : 0
856−
857− enabled: ApplicationManager.keyboardVisible
858 }
664 }
859
665
860 Label {
666 Label {
667+ id: alphaDisclaimerLabel
861 anchors.centerIn: parent
668 anchors.centerIn: parent
862 visible: ApplicationManager.fake ? ApplicationManager.fake : false
669 visible: ApplicationManager.fake ? ApplicationManager.fake : false
670+ z: dialogs.z + 10
863 text: "EARLY ALPHA\nNOT READY FOR USE"
671 text: "EARLY ALPHA\nNOT READY FOR USE"
864 color: "lightgrey"
672 color: "lightgrey"
865 opacity: 0.2
673 opacity: 0.2
@@ -873,6 +681,7 @@ FocusScope {
873
681
874 EdgeDemo {
682 EdgeDemo {
875 id: edgeDemo
683 id: edgeDemo
684+ z: alphaDisclaimerLabel.z + 10
876 greeter: greeter
685 greeter: greeter
877 launcher: launcher
686 launcher: launcher
878 dash: dash
687 dash: dash
@@ -885,35 +694,9 @@ FocusScope {
885 onShowHome: showHome()
694 onShowHome: showHome()
886 }
695 }
887
696
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−
915 Rectangle {
697 Rectangle {
916 id: shutdownFadeOutRectangle
698 id: shutdownFadeOutRectangle
699+ z: edgeDemo.z + 10
917 enabled: false
700 enabled: false
918 visible: false
701 visible: false
919 color: "black"
702 color: "black"
@@ -930,4 +713,5 @@ FocusScope {
930 }
713 }
931 }
714 }
932 }
715 }
716+
933}
717}
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
18import Ubuntu.Components 0.1
18import Ubuntu.Components 0.1
19import Ubuntu.Gestures 0.1
19import Ubuntu.Gestures 0.1
20import Unity.Application 0.1
20import Unity.Application 0.1
21−import LightDM 0.1 as LightDM
22import Utils 0.1
21import Utils 0.1
23import "../Components"
22import "../Components"
24
23
@@ -26,88 +25,51 @@ Item {
26 id: root
25 id: root
27
26
28 // Controls to be set from outside
27 // Controls to be set from outside
29− property bool shown: false
30− property bool moving: false
31 property int dragAreaWidth
28 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
32
32
33 // State information propagated to the outside
33 // 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
34+ readonly property bool locked: spreadView.phase == 2
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
42
35
43 function select(appId) {
36 function select(appId) {
44− spreadView.snapTo(priv.indexOf(appId))
37+ spreadView.snapTo(priv.indexOf(appId));
45 }
38 }
46
39
47− onMovingChanged: {
40+ onWidthChanged: {
48− if (moving) {
41+ spreadView.selectedIndex = -1;
49− if (ApplicationManager.focusedApplicationId) {
42+ spreadView.phase = 0;
50− priv.requestNewScreenshot();
43+ spreadView.contentX = -spreadView.shift;
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− }
59 }
44 }
60
45
61 Connections {
46 Connections {
62 target: ApplicationManager
47 target: ApplicationManager
63
48
64 onFocusRequested: {
49 onFocusRequested: {
65− if (spreadView.visible) {
50+ if (spreadView.phase > 0) {
66 spreadView.snapTo(priv.indexOf(appId));
51 spreadView.snapTo(priv.indexOf(appId));
67 } else {
52 } else {
68 priv.switchToApp(appId);
53 priv.switchToApp(appId);
69 }
54 }
70 }
55 }
71
56
72− onFocusedApplicationIdChanged: {
57+ onApplicationAdded: {
58+ if (spreadView.phase == 2) {
73− if (ApplicationManager.focusedApplicationId.length > 0) {
59+ spreadView.snapTo(ApplicationManager.count - 1);
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− }
81 } else {
60 } else {
82− spreadView.selectedIndex = -1;
83 spreadView.phase = 0;
61 spreadView.phase = 0;
84 spreadView.contentX = -spreadView.shift;
62 spreadView.contentX = -spreadView.shift;
85− }
63+ priv.switchToApp(appId);
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);
102 }
64 }
103 }
65 }
104
66
105 onApplicationRemoved: {
67 onApplicationRemoved: {
106− if (ApplicationManager.count == 0) {
68+ // Unless we're closing the app ourselves in the spread,
107− mainScreenshotImage.source = ""
69+ // lets make sure the spread doesn't mess up by the changing app list.
108− mainScreenshotImage.visible = false;
70+ if (spreadView.closingIndex == -1) {
109− } else {
71+ spreadView.phase = 0;
110− mainScreenshotImage.source = ApplicationManager.get(0).screenshot;
72+ spreadView.contentX = -spreadView.shift;
111 }
73 }
112 }
74 }
113 }
75 }
@@ -117,46 +79,11 @@ Item {
117
79
118 property string focusedAppId: ApplicationManager.focusedApplicationId
80 property string focusedAppId: ApplicationManager.focusedApplicationId
119 property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
81 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− }
149
82
150 function switchToApp(appId) {
83 function switchToApp(appId) {
151 if (priv.focusedAppId) {
84 if (priv.focusedAppId) {
152− priv.newFocusedAppId = appId;
85+ spreadView.focusChanging = true;
153− root.fullscreen = ApplicationManager.findApplication(appId).fullscreen;
86+ ApplicationManager.focusApplication(appId);
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− }
160 } else {
87 } else {
161 ApplicationManager.focusApplication(appId);
88 ApplicationManager.focusApplication(appId);
162 }
89 }
@@ -173,158 +100,6 @@ Item {
173
100
174 }
101 }
175
102
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−
328 Rectangle {
103 Rectangle {
329 id: coverFlipBackground
104 id: coverFlipBackground
330 anchors.fill: parent
105 anchors.fill: parent
@@ -332,16 +107,12 @@ Item {
332 visible: spreadView.visible
107 visible: spreadView.visible
333 }
108 }
334
109
335− InputFilterArea {
336− anchors.fill: root
337− blockInput: spreadView.visible
338− }
339
110
340 Flickable {
111 Flickable {
341 id: spreadView
112 id: spreadView
342 objectName: "spreadView"
113 objectName: "spreadView"
343 anchors.fill: parent
114 anchors.fill: parent
344− visible: spreadDragArea.status == DirectionalDragArea.Recognized || phase > 1 || snapAnimation.running
115+ interactive: (spreadDragArea.status == DirectionalDragArea.Recognized || phase > 1) && draggedIndex == -1
345 contentWidth: spreadRow.width - shift
116 contentWidth: spreadRow.width - shift
346 contentX: -shift
117 contentX: -shift
347
118
@@ -350,8 +121,8 @@ Item {
350 // that, the beginning of the gesture starts with a negative value for contentX
121 // that, the beginning of the gesture starts with a negative value for contentX
351 // so the flickable wants to pull it into the view already. "shift" tunes the
122 // so the flickable wants to pull it into the view already. "shift" tunes the
352 // distance where to "lock" the content.
123 // distance where to "lock" the content.
353− property real shift: width / 2
124+ readonly property real shift: width / 2
354− property real shiftedContentX: contentX + shift
125+ readonly property real shiftedContentX: contentX + shift
355
126
356 property int tileDistance: width / 4
127 property int tileDistance: width / 4
357
128
@@ -375,6 +146,10 @@ Item {
375 property int phase: 0
146 property int phase: 0
376
147
377 property int selectedIndex: -1
148 property int selectedIndex: -1
149+ property int draggedIndex: -1
150+ property int closingIndex: -1
151+
152+ property bool focusChanging: false
378
153
379 onShiftedContentXChanged: {
154 onShiftedContentXChanged: {
380 switch (phase) {
155 switch (phase) {
@@ -398,9 +173,9 @@ Item {
398 snapAnimation.targetContentX = -shift;
173 snapAnimation.targetContentX = -shift;
399 snapAnimation.start();
174 snapAnimation.start();
400 } else if (shiftedContentX < positionMarker2 * width) {
175 } else if (shiftedContentX < positionMarker2 * width) {
401− snapTo(1)
176+ snapTo(1);
402 } else if (shiftedContentX < positionMarker3 * width) {
177 } else if (shiftedContentX < positionMarker3 * width) {
403− snapTo(1)
178+ snapTo(1);
404 } else if (phase < 2){
179 } else if (phase < 2){
405 // Add 1 pixel to make sure we definitely hit positionMarker4 even with rounding errors of the animation.
180 // Add 1 pixel to make sure we definitely hit positionMarker4 even with rounding errors of the animation.
406 snapAnimation.targetContentX = width * positionMarker4 + 1 - shift;
181 snapAnimation.targetContentX = width * positionMarker4 + 1 - shift;
@@ -408,9 +183,18 @@ Item {
408 }
183 }
409 }
184 }
410 function snapTo(index) {
185 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+ }
411 spreadView.selectedIndex = index;
190 spreadView.selectedIndex = index;
412− root.fullscreen = ApplicationManager.get(index).fullscreen;
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 {
413− snapAnimation.targetContentX = -shift;
196+ snapAnimation.targetContentX = -shift + index * spreadView.tileDistance;
197+ }
414 snapAnimation.start();
198 snapAnimation.start();
415 }
199 }
416
200
@@ -429,7 +213,8 @@ Item {
429 script: {
213 script: {
430 if (spreadView.selectedIndex >= 0) {
214 if (spreadView.selectedIndex >= 0) {
431 ApplicationManager.focusApplication(ApplicationManager.get(spreadView.selectedIndex).appId);
215 ApplicationManager.focusApplication(ApplicationManager.get(spreadView.selectedIndex).appId);
216+
432− spreadView.selectedIndex = -1
217+ spreadView.selectedIndex = -1;
433 spreadView.phase = 0;
218 spreadView.phase = 0;
434 spreadView.contentX = -spreadView.shift;
219 spreadView.contentX = -spreadView.shift;
435 }
220 }
@@ -443,6 +228,15 @@ Item {
443 // tileDistance * app count (with a minimum of 3 apps, in order to also allow moving 1 and 2 apps a bit)
228 // tileDistance * app count (with a minimum of 3 apps, in order to also allow moving 1 and 2 apps a bit)
444 // + some constant value (still scales with the screen width) which looks good and somewhat fills the screen
229 // + some constant value (still scales with the screen width) which looks good and somewhat fills the screen
445 width: Math.max(3, ApplicationManager.count) * spreadView.tileDistance + (spreadView.width - spreadView.tileDistance) * 1.5
230 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+ }
446
240
447 x: spreadView.contentX
241 x: spreadView.contentX
448
242
@@ -462,18 +256,55 @@ Item {
462 height: spreadView.height
256 height: spreadView.height
463 selected: spreadView.selectedIndex == index
257 selected: spreadView.selectedIndex == index
464 otherSelected: spreadView.selectedIndex >= 0 && !selected
258 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
465
264
466− z: index
265+ z: behavioredIndex
467 x: index == 0 ? 0 : spreadView.width + (index - 1) * spreadView.tileDistance
266 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+ }
468
290
469 // Each tile has a different progress value running from 0 to 1.
291 // 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
471 progress: {
295 progress: {
472− var tileProgress = (spreadView.shiftedContentX - index * spreadView.tileDistance) / spreadView.width;
296+ var tileProgress = (spreadView.shiftedContentX - behavioredIndex * spreadView.tileDistance) / spreadView.width;
473 // Tile 1 needs to move directly from the beginning...
297 // Tile 1 needs to move directly from the beginning...
474− if (index == 1 && spreadView.phase < 2) {
298+ if (behavioredIndex == 1 && spreadView.phase < 2) {
475 tileProgress += spreadView.tileDistance / spreadView.width;
299 tileProgress += spreadView.tileDistance / spreadView.width;
476 }
300 }
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));
477 return tileProgress;
308 return tileProgress;
478 }
309 }
479
310
@@ -493,7 +324,7 @@ Item {
493
324
494 EasingCurve {
325 EasingCurve {
495 id: snappingCurve
326 id: snappingCurve
496− type: EasingCurve.OutQuad
327+ type: EasingCurve.Linear
497 period: 0.05
328 period: 0.05
498 progress: appDelegate.progress - spreadView.positionMarker1
329 progress: appDelegate.progress - spreadView.positionMarker1
499 }
330 }
@@ -507,7 +338,84 @@ Item {
507 }
338 }
508 }
339 }
509 }
340 }
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;
510 }
405 }
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();
511 }
419 }
512 }
420 }
513 }
421 }
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 @@
17*/
17*/
18
18
19import QtQuick 2.0
19import QtQuick 2.0
20+import Unity.Application 0.1
21+import Ubuntu.Components 1.1
22+import "../Components"
20
23
21Item {
24Item {
22 id: root
25 id: root
23
26
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+
24 signal clicked()
35 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+ }
25
65
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+ }
27
111
28− QtObject {
112+ transform: Translate {
29− id: priv
113+ y: dragArea.distance
30− property real heightDifference: root.height - appImage.implicitHeight
114+ }
31 }
115 }
32
116
33− Image {
117+
34− id: dropShadow
118+ StateGroup {
35− anchors.fill: appImage
119+ id: appSurfaceState
36− anchors.margins: -units.gu(2)
120+ states: [
37− source: "graphics/dropshadow.png"
121+ State {
38− opacity: .4
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"
39 }
140 }
40− Image {
141+
41− id: appImage
142+ Loader {
143+ id: splashLoader
42− anchors {
144+ anchors.fill: surfaceContainer
43− left: parent.left;
44− bottom: parent.bottom;
45− top: parent.top;
46− topMargin: priv.heightDifference * Math.max(0, 1 - root.topMarginProgress)
47− }
48− source: model.screenshot
49− antialiasing: true
50 }
145 }
51− MouseArea {
146+
147+ DraggingArea {
148+ id: dragArea
52− anchors.fill: appImage
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) {
53− onClicked: root.clicked()
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+ }
54 }
218 }
55}
219}
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
23SpreadDelegate {
23SpreadDelegate {
24 id: root
24 id: root
25
25
26+ // Set this to true when this tile is selected in the spread. The animation will change to bring the tile to front.
26 property bool selected: false
27 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.
27 property bool otherSelected: false
29 property bool otherSelected: false
28
30
29 // The progress animates the tiles. A value > 0 makes it appear from the right edge. At 1 it reaches the end position.
31 // 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 {
44 property real startDistance: units.gu(5)
46 property real startDistance: units.gu(5)
45 property real endDistance: units.gu(.5)
47 property real endDistance: units.gu(.5)
46
48
49+ // Hiding tiles when their progress is negative or reached the maximum
50+ visible: progress >= 0 && progress < 1.7
51+
47 onSelectedChanged: {
52 onSelectedChanged: {
48 if (selected) {
53 if (selected) {
49 priv.snapshot();
54 priv.snapshot();
@@ -106,16 +111,6 @@ SpreadDelegate {
106 selectedTopMarginProgress = topMarginProgress;
111 selectedTopMarginProgress = topMarginProgress;
107 }
112 }
108
113
109− // This calculates how much negative progress there can be if unwinding the spread completely
110− // the progress for each tile starts at 0 when it crosses the right edge, so the later a tile comes in,
111− // the bigger its negativeProgress can be.
112− property real negativeProgress: {
113− if (index == 1 && spreadView.phase < 2) {
114− return 0;
115− }
116− return -index * spreadView.tileDistance / spreadView.width;
117− }
118−
119 function linearAnimation(startProgress, endProgress, startValue, endValue, progress) {
114 function linearAnimation(startProgress, endProgress, startValue, endValue, progress) {
120 // progress : progressDiff = value : valueDiff => value = progress * valueDiff / progressDiff
115 // progress : progressDiff = value : valueDiff => value = progress * valueDiff / progressDiff
121 return (progress - startProgress) * (endValue - startValue) / (endProgress - startProgress) + startValue;
116 return (progress - startProgress) * (endValue - startValue) / (endProgress - startProgress) + startValue;
@@ -127,22 +122,20 @@ SpreadDelegate {
127 return helperEasingCurve.value * (endValue - startValue) + startValue;
122 return helperEasingCurve.value * (endValue - startValue) + startValue;
128 }
123 }
129
124
130− property real animatedEndDistance: linearAnimation(0, 2, root.endDistance, 0, root.progress)
131−
132 // The following blocks handle the animation of the tile in the spread.
125 // The following blocks handle the animation of the tile in the spread.
133 // At the beginning, each tile is attached at the right edge, outside the screen.
126 // At the beginning, each tile is attached at the right edge, outside the screen.
134 // The progress for each tile starts at 0 and it reaches its end position at a progress of 1.
127 // 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
128+ // The first phases are handled special for the first 2 tiles. as we do the alt-tab and snapping
136− // Once we reached phase 3, the animation is the same for all tiles.
129+ // in there. 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
130+ // When a tile is selected, the animation state is snapshotted, and the spreadView is unwound.
138− // back to negativeProgress). All tiles are kept in place and faded out to 0 opacity except
131+ // All tiles are kept in place and faded out to 0 opacity except
139 // the selected tile, which is animated from the snapshotted position to be fullscreen.
132 // the selected tile, which is animated from the snapshotted position to be fullscreen.
140
133
141− property real xTranslate: {
134+ readonly property real xTranslate: {
142 if (otherSelected) {
135 if (otherSelected) {
143 if (spreadView.phase < 2 && index == 0) {
136 if (spreadView.phase < 2 && index == 0) {
144− return linearAnimation(selectedProgress, negativeProgress,
137+ return linearAnimation(selectedProgress, 0, selectedXTranslate,
145− selectedXTranslate, selectedXTranslate - spreadView.tileDistance, root.progress);
138+ selectedXTranslate - spreadView.tileDistance, root.progress);
146 }
139 }
147
140
148 return selectedXTranslate;
141 return selectedXTranslate;
@@ -160,7 +153,7 @@ SpreadDelegate {
160 // Apply the same animation as with the rest but add spreadView.width to align it with the others.
153 // Apply the same animation as with the rest but add spreadView.width to align it with the others.
161 return -easingCurve.value * spreadView.width + spreadView.width;
154 return -easingCurve.value * spreadView.width + spreadView.width;
162 } else if (priv.isSelected) {
155 } else if (priv.isSelected) {
163− return linearAnimation(selectedProgress, negativeProgress, selectedXTranslate, 0, root.progress);
156+ return linearAnimation(selectedProgress, 0, selectedXTranslate, 0, root.progress);
164 }
157 }
165
158
166 case 1:
159 case 1:
@@ -177,23 +170,28 @@ SpreadDelegate {
177 if (priv.isSelected) {
170 if (priv.isSelected) {
178 // Distance to left edge
171 // Distance to left edge
179 var targetTranslate = -spreadView.width - ((index - 1) * root.startDistance);
172 var targetTranslate = -spreadView.width - ((index - 1) * root.startDistance);
180− return linearAnimation(selectedProgress, negativeProgress,
173+ return linearAnimation(selectedProgress, 0,
181 selectedXTranslate, targetTranslate, root.progress);
174 selectedXTranslate, targetTranslate, root.progress);
182 }
175 }
183
176
184 // Fix it at the right edge...
177 // Fix it at the right edge...
185 var rightEdgeOffset = -((index - 1) * root.startDistance);
178 var rightEdgeOffset = -((index - 1) * root.startDistance);
186 // ...and use our easing to move them to the left. Stop a bit earlier for each tile
179 // ...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);
187 return -easingCurve.value * spreadView.width + (index * animatedEndDistance) + rightEdgeOffset;
181 return -easingCurve.value * spreadView.width + (index * animatedEndDistance) + rightEdgeOffset;
188
182
189 }
183 }
190
184
191− property real angle: {
185+ readonly property real angle: {
186+ if (spreadView.focusChanging) {
187+ return 0;
188+ }
189+
192 if (priv.otherSelected) {
190 if (priv.otherSelected) {
193 return priv.selectedAngle;
191 return priv.selectedAngle;
194 }
192 }
195 if (priv.isSelected) {
193 if (priv.isSelected) {
196− return linearAnimation(selectedProgress, negativeProgress, selectedAngle, 0, root.progress);
194+ return linearAnimation(selectedProgress, 0, selectedAngle, 0, root.progress);
197 }
195 }
198 switch (index) {
196 switch (index) {
199 case 0:
197 case 0:
@@ -217,12 +215,15 @@ SpreadDelegate {
217 return root.startAngle - easingCurve.value * (root.startAngle - root.endAngle);
215 return root.startAngle - easingCurve.value * (root.startAngle - root.endAngle);
218 }
216 }
219
217
220− property real scale: {
218+ readonly property real scale: {
219+ if (spreadView.focusChanging) {
220+ return 1;
221+ }
221 if (priv.otherSelected) {
222 if (priv.otherSelected) {
222 return priv.selectedScale;
223 return priv.selectedScale;
223 }
224 }
224 if (priv.isSelected) {
225 if (priv.isSelected) {
225− return linearAnimation(selectedProgress, negativeProgress, selectedScale, 1, root.progress);
226+ return linearAnimation(selectedProgress, 0, selectedScale, 1, root.progress);
226 }
227 }
227
228
228 switch (index) {
229 switch (index) {
@@ -247,7 +248,7 @@ SpreadDelegate {
247 return root.startScale - easingCurve.value * (root.startScale - root.endScale);
248 return root.startScale - easingCurve.value * (root.startScale - root.endScale);
248 }
249 }
249
250
250− property real opacity: {
251+ readonly property real opacity: {
251 if (priv.otherSelected) {
252 if (priv.otherSelected) {
252 return linearAnimation (selectedProgress, Math.max(0, selectedProgress - .5),
253 return linearAnimation (selectedProgress, Math.max(0, selectedProgress - .5),
253 selectedOpacity, 0, root.progress);
254 selectedOpacity, 0, root.progress);
@@ -265,9 +266,9 @@ SpreadDelegate {
265 return 1;
266 return 1;
266 }
267 }
267
268
268− property real topMarginProgress: {
269+ readonly property real topMarginProgress: {
269 if (priv.isSelected) {
270 if (priv.isSelected) {
270− return linearAnimation(selectedProgress, negativeProgress, selectedTopMarginProgress, 0, root.progress);
271+ return linearAnimation(selectedProgress, 0, selectedTopMarginProgress, 0, root.progress);
271 }
272 }
272
273
273 switch (index) {
274 switch (index) {
@@ -290,6 +291,7 @@ SpreadDelegate {
290
291
291 return easingCurve.value;
292 return easingCurve.value;
292 }
293 }
294+
293 }
295 }
294
296
295 transform: [
297 transform: [
@@ -303,18 +305,22 @@ SpreadDelegate {
303 xScale: priv.scale
305 xScale: priv.scale
304 yScale: xScale
306 yScale: xScale
305 },
307 },
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+ },
306 Translate {
313 Translate {
307 x: priv.xTranslate
314 x: priv.xTranslate
308 }
315 }
309 ]
316 ]
310 opacity: priv.opacity
317 opacity: priv.opacity
311− topMarginProgress: priv.topMarginProgress
312
318
313 EasingCurve {
319 EasingCurve {
314 id: easingCurve
320 id: easingCurve
315 type: EasingCurve.OutSine
321 type: EasingCurve.OutSine
316 period: 1 - spreadView.positionMarker2
322 period: 1 - spreadView.positionMarker2
317− progress: root.animatedProgress
323+ progress: root.progress
318 }
324 }
319
325
320 // This is used as a calculation helper to figure values for progress other than the current one
326 // 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
8FAKE=false
8FAKE=false
9PINLOCK=false
9PINLOCK=false
10KEYLOCK=false
10KEYLOCK=false
11+USE_MOCKS=false
11MOUSE_TOUCH=true
12MOUSE_TOUCH=true
12
13
13usage() {
14usage() {
@@ -31,9 +32,9 @@ eval set -- "$ARGS"
31while [ $# -gt 0 ]
32while [ $# -gt 0 ]
32do
33do
33 case "$1" in
34 case "$1" in
34− -f|--fake) FAKE=true;;
35+ -f|--fake) FAKE=true; USE_MOCKS=true;;
35− -p|--pinlock) PINLOCK=true;;
36+ -p|--pinlock) PINLOCK=true; USE_MOCKS=true;;
36− -k|--keylock) KEYLOCK=true;;
37+ -k|--keylock) KEYLOCK=true; USE_MOCKS=true;;
37 -g|--gdb) GDB=true;;
38 -g|--gdb) GDB=true;;
38 -h|--help) usage;;
39 -h|--help) usage;;
39 -m|--nomousetouch) MOUSE_TOUCH=false;;
40 -m|--nomousetouch) MOUSE_TOUCH=false;;
@@ -43,20 +44,28 @@ do
43done
44done
44
45
45if $FAKE; then
46if $FAKE; then
46− export QML2_IMPORT_PATH=$QML2_IMPORT_PATH:$PWD/builddir/tests/mocks:$PWD/builddir/plugins:$PWD/builddir/modules
47 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/builddir/tests/mocks/libusermetrics:$PWD/builddir/tests/mocks/LightDM/single
47 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/builddir/tests/mocks/libusermetrics:$PWD/builddir/tests/mocks/LightDM/single
48fi
48fi
49
49
50if $PINLOCK; then
50if $PINLOCK; then
51− export QML2_IMPORT_PATH=$QML2_IMPORT_PATH:$PWD/builddir/tests/mocks:$PWD/builddir/plugins:$PWD/builddir/modules
52 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/builddir/tests/mocks/libusermetrics:$PWD/builddir/tests/mocks/LightDM/single-pin
51 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/builddir/tests/mocks/libusermetrics:$PWD/builddir/tests/mocks/LightDM/single-pin
53fi
52fi
54
53
55if $KEYLOCK; then
54if $KEYLOCK; then
56− export QML2_IMPORT_PATH=$QML2_IMPORT_PATH:$PWD/builddir/tests/mocks:$PWD/builddir/plugins:$PWD/builddir/modules
57 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/builddir/tests/mocks/libusermetrics:$PWD/builddir/tests/mocks/LightDM/single-passphrase
55 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/builddir/tests/mocks/libusermetrics:$PWD/builddir/tests/mocks/LightDM/single-passphrase
58fi
56fi
59
57
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+
60QML_PHONE_SHELL_ARGS=""
69QML_PHONE_SHELL_ARGS=""
61if $MOUSE_TOUCH; then
70if $MOUSE_TOUCH; then
62 QML_PHONE_SHELL_ARGS="$QML_PHONE_SHELL_ARGS -mousetouch"
71 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−
3include_directories(
1include_directories(
4 ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
2 ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
5− ${UNITY-MIR_INCLUDE_DIRS}
6)
3)
7
4
8file(GLOB_RECURSE QML_FILES
5file(GLOB_RECURSE QML_FILES
src/main.cpp+21−89
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2012 Canonical, Ltd.
2+ * Copyright (C) 2012-2014 Canonical, Ltd.
3 *
3 *
4 * Authors:
4 * Authors:
5 * Gerry Boland <gerry.boland@canonical.com>
5 * Gerry Boland <gerry.boland@canonical.com>
@@ -23,12 +23,10 @@
23#include <QtGui/QGuiApplication>
23#include <QtGui/QGuiApplication>
24#include <QtQml/QQmlEngine>
24#include <QtQml/QQmlEngine>
25#include <QtQml/QQmlContext>
25#include <QtQml/QQmlContext>
26−#include <qpa/qplatformnativeinterface.h>
27#include <QLibrary>
26#include <QLibrary>
28#include <QDebug>
27#include <QDebug>
29−#include <libintl.h>
30−#include <dlfcn.h>
31#include <csignal>
28#include <csignal>
29+#include <libintl.h>
32
30
33// local
31// local
34#include <paths.h>
32#include <paths.h>
@@ -36,11 +34,13 @@
36#include "ApplicationArguments.h"
34#include "ApplicationArguments.h"
37#include "CachingNetworkManagerFactory.h"
35#include "CachingNetworkManagerFactory.h"
38
36
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[])
42{
38{
39+ bool isMirServer = false;
43− const bool isUbuntuMirServer = qgetenv("QT_QPA_PLATFORM") == "ubuntumirserver";
40+ if (qgetenv("QT_QPA_PLATFORM") == "ubuntumirclient") {
41+ setenv("QT_QPA_PLATFORM", "mirserver", 1 /* overwrite */);
42+ isMirServer = true;
43+ }
44
44
45 QGuiApplication::setApplicationName("unity8");
45 QGuiApplication::setApplicationName("unity8");
46 QGuiApplication *application;
46 QGuiApplication *application;
@@ -70,21 +70,7 @@ int startShell(int argc, const char** argv, void* server)
70Load the testability driver");
70Load the testability driver");
71 parser.addOption(testabilityOption);
71 parser.addOption(testabilityOption);
72
72
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);
73+ application = new QGuiApplication(argc, (char**)argv);
87− }
88
74
89 // Treat args with single dashes the same as arguments with two dashes
75 // Treat args with single dashes the same as arguments with two dashes
90 // Ex: -fullscreen == --fullscreen
76 // Ex: -fullscreen == --fullscreen
@@ -100,8 +86,8 @@ Load the testability driver");
100 if (parser.isSet(windowGeometryOption) &&
86 if (parser.isSet(windowGeometryOption) &&
101 parser.value(windowGeometryOption).split('x').size() == 2)
87 parser.value(windowGeometryOption).split('x').size() == 2)
102 {
88 {
103− QStringList geom = parser.value(windowGeometryOption).split('x');
89+ QStringList geom = parser.value(windowGeometryOption).split('x');
104− qmlArgs.setSize(geom.at(0).toInt(), geom.at(1).toInt());
90+ qmlArgs.setSize(geom.at(0).toInt(), geom.at(1).toInt());
105 }
91 }
106
92
107 // The testability driver is only loaded by QApplication but not by QGuiApplication.
93 // The testability driver is only loaded by QApplication but not by QGuiApplication.
@@ -125,7 +111,8 @@ Load the testability driver");
125
111
126 QQuickView* view = new QQuickView();
112 QQuickView* view = new QQuickView();
127 view->setResizeMode(QQuickView::SizeRootObjectToView);
113 view->setResizeMode(QQuickView::SizeRootObjectToView);
114+ view->setColor("black");
128− view->setTitle("Qml Phone Shell");
115+ view->setTitle("Unity8 Shell");
129 view->engine()->setBaseUrl(QUrl::fromLocalFile(::qmlDirectory()));
116 view->engine()->setBaseUrl(QUrl::fromLocalFile(::qmlDirectory()));
130 view->rootContext()->setContextProperty("applicationArguments", &qmlArgs);
117 view->rootContext()->setContextProperty("applicationArguments", &qmlArgs);
131 view->rootContext()->setContextProperty("indicatorProfile", indicatorProfile);
118 view->rootContext()->setContextProperty("indicatorProfile", indicatorProfile);
@@ -141,18 +128,9 @@ Load the testability driver");
141 application->installNativeEventFilter(mouseTouchAdaptor);
128 application->installNativeEventFilter(mouseTouchAdaptor);
142 }
129 }
143
130
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−
153 QUrl source(::qmlDirectory()+"Shell.qml");
131 QUrl source(::qmlDirectory()+"Shell.qml");
154 prependImportPaths(view->engine(), ::overrideImportPaths());
132 prependImportPaths(view->engine(), ::overrideImportPaths());
155− if (!isUbuntuMirServer) {
133+ if (!isMirServer) {
156 prependImportPaths(view->engine(), ::nonMirImportPaths());
134 prependImportPaths(view->engine(), ::nonMirImportPaths());
157 }
135 }
158 appendImportPaths(view->engine(), ::fallbackImportPaths());
136 appendImportPaths(view->engine(), ::fallbackImportPaths());
@@ -161,10 +139,15 @@ Load the testability driver");
161 view->engine()->setNetworkAccessManagerFactory(managerFactory);
139 view->engine()->setNetworkAccessManagerFactory(managerFactory);
162
140
163 view->setSource(source);
141 view->setSource(source);
164− view->setColor("transparent");
165 QObject::connect(view->engine(), SIGNAL(quit()), application, SLOT(quit()));
142 QObject::connect(view->engine(), SIGNAL(quit()), application, SLOT(quit()));
166
143
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+
167− if (qgetenv("QT_QPA_PLATFORM") == "ubuntu" || isUbuntuMirServer || parser.isSet(fullscreenOption)) {
150+ if (isMirServer || parser.isSet(fullscreenOption)) {
168 view->showFullScreen();
151 view->showFullScreen();
169 } else {
152 } else {
170 view->show();
153 view->show();
@@ -178,54 +161,3 @@ Load the testability driver");
178
161
179 return result;
162 return result;
180}
163}
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):
188 raise emulators.UnityEmulatorException(
188 raise emulators.UnityEmulatorException(
189 'No category found with name {}'.format(category))
189 'No category found with name {}'.format(category))
190
190
191−
192−class DashApps(GenericScopeView):
193− """Autopilot emulator for the applications scope."""
194−
195 def get_applications(self, category):
191 def get_applications(self, category):
196 """Return the list of applications on a category.
192 """Return the list of applications on a category.
197
193
tests/autopilot/unity8/shell/tests/test_emulators.py+1−1
@@ -142,7 +142,7 @@ class DashEmulatorTestCase(DashBaseTestCase):
142 scope_id = 'clickscope'
142 scope_id = 'clickscope'
143 scope = self.dash.open_scope(scope_id)
143 scope = self.dash.open_scope(scope_id)
144 self._assert_scope_is_opened(scope, scope_id)
144 self._assert_scope_is_opened(scope, scope_id)
145− self.assertIsInstance(scope, dash_emulators.DashApps)
145+ self.assertIsInstance(scope, dash_emulators.GenericScopeView)
146
146
147
147
148class GenericScopeViewEmulatorTestCase(DashBaseTestCase):
148class GenericScopeViewEmulatorTestCase(DashBaseTestCase):
tests/autopilot/unity8/shell/tests/test_upstart.py+18−3
@@ -25,7 +25,8 @@ import signal
25import subprocess
25import subprocess
26import time
26import time
27
27
28+import fixtures
28−from testtools.matchers._basic import Equals
29+from testtools.matchers import Equals, MismatchError
29from autopilot.matchers import Eventually
30from autopilot.matchers import Eventually
30from autopilot.introspection import get_proxy_object_for_existing_process
31from autopilot.introspection import get_proxy_object_for_existing_process
31
32
@@ -100,8 +101,21 @@ class UpstartIntegrationTests(UnityTestCase):
100 emulator_base=UnityEmulatorBase,))
101 emulator_base=UnityEmulatorBase,))
101
102
102 def test_no_sigstop(self):
103 def test_no_sigstop(self):
104+ self.useFixture(
105+ fixtures.EnvironmentVariable(
103− self.patch_environment("UNITY_MIR_EMITS_SIGSTOP", "")
106+ 'UNITY_MIR_EMITS_SIGSTOP', newvalue=None))
104 self._launch_unity()
107 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+
105 self._set_proxy()
119 self._set_proxy()
106
120
107 logger.debug("Unity started, waiting for it to be ready.")
121 logger.debug("Unity started, waiting for it to be ready.")
@@ -109,7 +123,8 @@ class UpstartIntegrationTests(UnityTestCase):
109 logger.debug("Unity loaded and ready.")
123 logger.debug("Unity loaded and ready.")
110
124
111 def test_expect_sigstop(self):
125 def test_expect_sigstop(self):
126+ self.useFixture(
112− self.patch_environment("UNITY_MIR_EMITS_SIGSTOP", "1")
127+ fixtures.EnvironmentVariable('UNITY_MIR_EMITS_SIGSTOP', '1'))
113 self._launch_unity()
128 self._launch_unity()
114 self.assertThat(
129 self.assertThat(
115 lambda: os.WIFSTOPPED(self._get_status()),
130 lambda: os.WIFSTOPPED(self._get_status()),
tests/mocks/AccountsService/AccountsService.cpp+12−2
@@ -29,12 +29,14 @@ AccountsService::AccountsService(QObject* parent)
29
29
30QString AccountsService::user() const
30QString AccountsService::user() const
31{
31{
32− return "testuser";
32+ return m_user;
33}
33}
34
34
35void AccountsService::setUser(const QString &user)
35void AccountsService::setUser(const QString &user)
36{
36{
37− Q_UNUSED(user)
37+ m_user = user;
38+ Q_EMIT userChanged();
39+ Q_EMIT passwordDisplayHintChanged();
38}
40}
39
41
40bool AccountsService::demoEdges() const
42bool AccountsService::demoEdges() const
@@ -68,3 +70,11 @@ void AccountsService::setStatsWelcomeScreen(bool statsWelcomeScreen)
68 m_statsWelcomeScreen = statsWelcomeScreen;
70 m_statsWelcomeScreen = statsWelcomeScreen;
69 statsWelcomeScreenChanged();
71 statsWelcomeScreenChanged();
70}
72}
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 @@
27class AccountsService: public QObject
27class AccountsService: public QObject
28{
28{
29 Q_OBJECT
29 Q_OBJECT
30+ Q_ENUMS(PasswordDisplayHint)
30 Q_PROPERTY (QString user
31 Q_PROPERTY (QString user
31 READ user
32 READ user
32 WRITE setUser
33 WRITE setUser
@@ -43,8 +44,16 @@ class AccountsService: public QObject
43 READ statsWelcomeScreen
44 READ statsWelcomeScreen
44 WRITE setStatsWelcomeScreen // only available in mock
45 WRITE setStatsWelcomeScreen // only available in mock
45 NOTIFY statsWelcomeScreenChanged)
46 NOTIFY statsWelcomeScreenChanged)
47+ Q_PROPERTY (PasswordDisplayHint passwordDisplayHint
48+ READ passwordDisplayHint
49+ NOTIFY passwordDisplayHintChanged)
46
50
47public:
51public:
52+ enum PasswordDisplayHint {
53+ Keyboard,
54+ Numeric,
55+ };
56+
48 explicit AccountsService(QObject *parent = 0);
57 explicit AccountsService(QObject *parent = 0);
49
58
50 QString user() const;
59 QString user() const;
@@ -55,15 +64,18 @@ public:
55 void setBackgroundFile(const QString &backgroundFile);
64 void setBackgroundFile(const QString &backgroundFile);
56 bool statsWelcomeScreen() const;
65 bool statsWelcomeScreen() const;
57 void setStatsWelcomeScreen(bool statsWelcomeScreen);
66 void setStatsWelcomeScreen(bool statsWelcomeScreen);
67+ PasswordDisplayHint passwordDisplayHint() const;
58
68
59Q_SIGNALS:
69Q_SIGNALS:
60 void userChanged();
70 void userChanged();
61 void demoEdgesChanged();
71 void demoEdgesChanged();
62 void backgroundFileChanged();
72 void backgroundFileChanged();
63 void statsWelcomeScreenChanged();
73 void statsWelcomeScreenChanged();
74+ void passwordDisplayHintChanged();
64
75
65private:
76private:
66 QString m_backgroundFile;
77 QString m_backgroundFile;
78+ QString m_user;
67 bool m_statsWelcomeScreen;
79 bool m_statsWelcomeScreen;
68};
80};
69
81
tests/mocks/AccountsService/CMakeLists.txt+4−1
@@ -5,4 +5,7 @@ add_library(MockAccountsService-qml MODULE
5
5
6qt5_use_modules(MockAccountsService-qml DBus Qml)
6qt5_use_modules(MockAccountsService-qml DBus Qml)
7
7
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)
32void AccountsServicePlugin::registerTypes(const char *uri)
32void AccountsServicePlugin::registerTypes(const char *uri)
33{
33{
34 Q_ASSERT(uri == QLatin1String("AccountsService"));
34 Q_ASSERT(uri == QLatin1String("AccountsService"));
35+ qRegisterMetaType<AccountsService::PasswordDisplayHint>("AccountsService::PasswordDisplayHint");
35 qmlRegisterSingletonType<AccountsService>(uri, 0, 1, "AccountsService", service_provider);
36 qmlRegisterSingletonType<AccountsService>(uri, 0, 1, "AccountsService", service_provider);
36}
37}
tests/mocks/LightDM/GreeterPrivate.h+2
@@ -24,6 +24,7 @@
24namespace QLightDM
24namespace QLightDM
25{
25{
26class Greeter;
26class Greeter;
27+class GreeterImpl;
27
28
28class GreeterPrivate
29class GreeterPrivate
29{
30{
@@ -40,6 +41,7 @@ public:
40 void handleRespond(const QString &response);
41 void handleRespond(const QString &response);
41
42
42protected:
43protected:
44+ GreeterImpl *m_impl; // if the backend needs more private data
43 Greeter * const q_ptr;
45 Greeter * const q_ptr;
44
46
45private:
47private:
tests/mocks/LightDM/demo/CMakeLists.txt+4−2
@@ -11,11 +11,13 @@ set(LibLightDM_SOURCES
11add_library(MockLightDM-demo STATIC ${LibLightDM_SOURCES})
11add_library(MockLightDM-demo STATIC ${LibLightDM_SOURCES})
12
12
13include_directories(
13include_directories(
14+ ${CMAKE_CURRENT_BINARY_DIR}
14 ${LIBUSERMETRICSOUTPUT_INCLUDE_DIRS}
15 ${LIBUSERMETRICSOUTPUT_INCLUDE_DIRS}
15)
16)
16
17
17target_link_libraries(MockLightDM-demo
18target_link_libraries(MockLightDM-demo
18 ${LIBUSERMETRICSOUTPUT_LDFLAGS}
19 ${LIBUSERMETRICSOUTPUT_LDFLAGS}
20+ -lpam
19− )
21+)
20
22
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 @@
18
18
19#include "../Greeter.h"
19#include "../Greeter.h"
20#include "../GreeterPrivate.h"
20#include "../GreeterPrivate.h"
21−#include <QtCore/QDir>
21+#include <QFuture>
22−#include <QtCore/QSettings>
22+#include <QFutureInterface>
23+#include <QFutureWatcher>
24+#include <QQueue>
25+#include <QtConcurrent>
26+#include <QVector>
27+#include <security/pam_appl.h>
23
28
24namespace QLightDM
29namespace QLightDM
25{
30{
26
31
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+
27GreeterPrivate::GreeterPrivate(Greeter* parent)
222GreeterPrivate::GreeterPrivate(Greeter* parent)
28 : authenticated(false),
223 : authenticated(false),
29 authenticationUser(),
224 authenticationUser(),
225+ m_impl(new GreeterImpl(parent, this)),
30 q_ptr(parent)
226 q_ptr(parent)
31{
227{
32}
228}
33
229
34void GreeterPrivate::handleAuthenticate()
230void GreeterPrivate::handleAuthenticate()
35{
231{
36− Q_Q(Greeter);
37−
38− QSettings settings(QDir::homePath() + "/.unity8-greeter-demo", QSettings::NativeFormat);
39− settings.beginGroup(authenticationUser);
232+ m_impl->start(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− }
50}
233}
51
234
52void GreeterPrivate::handleRespond(const QString &response)
235void GreeterPrivate::handleRespond(const QString &response)
53{
236{
54− Q_Q(Greeter);
237+ m_impl->respond(response);
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();
68}
238}
69
239
70}
240}
241+
242+#include "GreeterPrivate.moc"
tests/mocks/LightDM/demo/UsersModelPrivate.cpp+1−1
@@ -29,7 +29,7 @@ UsersModelPrivate::UsersModelPrivate(UsersModel* parent)
29 : q_ptr(parent)
29 : q_ptr(parent)
30{
30{
31 QSettings settings(QDir::homePath() + "/.unity8-greeter-demo", QSettings::NativeFormat);
31 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();
33
33
34 Q_FOREACH(const QString &user, users)
34 Q_FOREACH(const QString &user, users)
35 {
35 {
tests/mocks/LightDM/full/GreeterPrivate.cpp+1−1
@@ -54,7 +54,7 @@ void GreeterPrivate::handleAuthenticate()
54 authenticated = true;
54 authenticated = true;
55 Q_EMIT q->authenticationComplete();
55 Q_EMIT q->authenticationComplete();
56 } else if (authenticationUser == "has-pin"){
56 } else if (authenticationUser == "has-pin"){
57− Q_EMIT q->showPrompt("PIN", Greeter::PromptTypeSecret);
57+ Q_EMIT q->showPrompt("Password:", Greeter::PromptTypeSecret);
58 } else if (authenticationUser == "auth-error") {
58 } else if (authenticationUser == "auth-error") {
59 authenticated = false;
59 authenticated = false;
60 Q_EMIT q->authenticationComplete();
60 Q_EMIT q->authenticationComplete();
tests/mocks/LightDM/single-pin/GreeterPrivate.cpp+1−1
@@ -32,7 +32,7 @@ GreeterPrivate::GreeterPrivate(Greeter* parent)
32void GreeterPrivate::handleAuthenticate()
32void GreeterPrivate::handleAuthenticate()
33{
33{
34 Q_Q(Greeter);
34 Q_Q(Greeter);
35− Q_EMIT q->showPrompt("PIN", Greeter::PromptTypeSecret);
35+ Q_EMIT q->showPrompt("Password:", Greeter::PromptTypeSecret);
36}
36}
37
37
38void GreeterPrivate::handleRespond(const QString &response)
38void GreeterPrivate::handleRespond(const QString &response)
tests/mocks/LightDM/single-pin/UsersModelPrivate.cpp+1−1
@@ -26,7 +26,7 @@ UsersModelPrivate::UsersModelPrivate(UsersModel* parent)
26{
26{
27 entries =
27 entries =
28 {
28 {
29− { "single", "Has PIN", 0, 0, false, false, 0, 0 },
29+ { "has-pin", "Has PIN", 0, 0, false, false, 0, 0 },
30 };
30 };
31}
31}
32
32
tests/mocks/Unity/Application/Application.qmltypes−2
@@ -102,8 +102,6 @@ Module {
102 "ForceMainStage": 1
102 "ForceMainStage": 1
103 }
103 }
104 }
104 }
105− Property { name: "keyboardHeight"; type: "int"; isReadonly: true }
106− Property { name: "keyboardVisible"; type: "bool"; isReadonly: true }
107 Property { name: "sideStageWidth"; type: "int"; isReadonly: true }
105 Property { name: "sideStageWidth"; type: "int"; isReadonly: true }
108 Property { name: "stageHint"; type: "StageHint"; isReadonly: true }
106 Property { name: "stageHint"; type: "StageHint"; isReadonly: true }
109 Property { name: "formFactorHint"; type: "FormFactorHint"; isReadonly: true }
107 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 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2014 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
5 * it under the terms of the GNU General Public License as published by
@@ -15,6 +15,8 @@
15 */
15 */
16
16
17#include "ApplicationInfo.h"
17#include "ApplicationInfo.h"
18+#include "MirSurfaceItem.h"
19+#include "SurfaceManager.h"
18
20
19#include <QGuiApplication>
21#include <QGuiApplication>
20#include <QQuickItem>
22#include <QQuickItem>
@@ -24,29 +26,39 @@
24
26
25ApplicationInfo::ApplicationInfo(const QString &appId, QObject *parent)
27ApplicationInfo::ApplicationInfo(const QString &appId, QObject *parent)
26 : ApplicationInfoInterface(appId, parent)
28 : ApplicationInfoInterface(appId, parent)
27− ,m_appId(appId)
29+ , m_appId(appId)
28− ,m_stage(MainStage)
30+ , m_stage(MainStage)
29− ,m_state(Starting)
31+ , m_state(Starting)
30− ,m_focused(false)
32+ , m_focused(false)
31− ,m_fullscreen(false)
33+ , m_fullscreen(false)
32− ,m_windowItem(0)
34+ , m_windowItem(0)
33− ,m_windowComponent(0)
35+ , m_windowComponent(0)
34− ,m_parentItem(0)
36+ , m_parentItem(0)
37+ , m_surface(0)
35{
38{
36− QTimer::singleShot(300, this, SLOT(setRunning()));
39+ connect(this, &ApplicationInfo::stateChanged, this, &ApplicationInfo::onStateChanged);
37}
40}
38
41
39ApplicationInfo::ApplicationInfo(QObject *parent)
42ApplicationInfo::ApplicationInfo(QObject *parent)
40 : ApplicationInfoInterface(QString(), parent)
43 : ApplicationInfoInterface(QString(), parent)
41− ,m_stage(MainStage)
44+ , m_stage(MainStage)
42− ,m_state(Starting)
45+ , m_state(Starting)
43− ,m_focused(false)
46+ , m_focused(false)
44− ,m_fullscreen(false)
47+ , m_fullscreen(false)
45− ,m_windowItem(0)
48+ , m_windowItem(0)
46− ,m_windowComponent(0)
49+ , m_windowComponent(0)
47− ,m_parentItem(0)
50+ , m_parentItem(0)
51+ , m_surface(0)
48{
52{
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+ }
50}
62}
51
63
52void ApplicationInfo::onWindowComponentStatusChanged(QQmlComponent::Status status)
64void ApplicationInfo::onWindowComponentStatusChanged(QQmlComponent::Status status)
@@ -55,10 +67,46 @@ void ApplicationInfo::onWindowComponentStatusChanged(QQmlComponent::Status statu
55 doCreateWindowItem();
67 doCreateWindowItem();
56}
68}
57
69
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()
59{
80{
60− m_state = Running;
81+ if (m_surface || state() == ApplicationInfo::Stopped) return;
61− Q_EMIT stateChanged();
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);
62}
110}
63
111
64void ApplicationInfo::createWindowComponent()
112void ApplicationInfo::createWindowComponent()
tests/mocks/Unity/Application/ApplicationInfo.h+21−8
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2014 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
5 * it under the terms of the GNU General Public License as published by
@@ -21,6 +21,7 @@
21#include <QQmlComponent>
21#include <QQmlComponent>
22
22
23class QQuickItem;
23class QQuickItem;
24+class MirSurfaceItem;
24
25
25// unity-api
26// unity-api
26#include <unity/shell/application/ApplicationInfoInterface.h>
27#include <unity/shell/application/ApplicationInfoInterface.h>
@@ -36,6 +37,7 @@ class ApplicationInfo : public ApplicationInfoInterface {
36
37
37 Q_PROPERTY(bool fullscreen READ fullscreen WRITE setFullscreen NOTIFY fullscreenChanged)
38 Q_PROPERTY(bool fullscreen READ fullscreen WRITE setFullscreen NOTIFY fullscreenChanged)
38 Q_PROPERTY(Stage stage READ stage WRITE setStage NOTIFY stageChanged)
39 Q_PROPERTY(Stage stage READ stage WRITE setStage NOTIFY stageChanged)
40+ Q_PROPERTY(MirSurfaceItem* surface READ surface NOTIFY surfaceChanged)
39
41
40 // Only exists in this fake implementation
42 // Only exists in this fake implementation
41
43
@@ -45,9 +47,10 @@ class ApplicationInfo : public ApplicationInfoInterface {
45 // QML component used to represent the application window
47 // QML component used to represent the application window
46 Q_PROPERTY(QString windowQml READ windowQml WRITE setWindowQml NOTIFY windowQmlChanged)
48 Q_PROPERTY(QString windowQml READ windowQml WRITE setWindowQml NOTIFY windowQmlChanged)
47
49
48− public:
50+public:
49 ApplicationInfo(QObject *parent = NULL);
51 ApplicationInfo(QObject *parent = NULL);
50 ApplicationInfo(const QString &appId, QObject *parent = NULL);
52 ApplicationInfo(const QString &appId, QObject *parent = NULL);
53+ ~ApplicationInfo();
51
54
52 #define IMPLEMENT_PROPERTY(name, Name, type) \
55 #define IMPLEMENT_PROPERTY(name, Name, type) \
53 public: \
56 public: \
@@ -56,11 +59,11 @@ class ApplicationInfo : public ApplicationInfoInterface {
56 { \
59 { \
57 if (m_##name != value) { \
60 if (m_##name != value) { \
58 m_##name = value; \
61 m_##name = value; \
59− Q_EMIT name##Changed(); \
62+ Q_EMIT name##Changed(value); \
60 } \
63 } \
61 } \
64 } \
62 Q_SIGNALS: \
65 Q_SIGNALS: \
63− void name##Changed(); \
66+ void name##Changed(const type&); \
64 private: \
67 private: \
65 type m_##name;
68 type m_##name;
66
69
@@ -78,21 +81,31 @@ class ApplicationInfo : public ApplicationInfoInterface {
78
81
79 #undef IMPLEMENT_PROPERTY
82 #undef IMPLEMENT_PROPERTY
80
83
84+public:
85+ void setSurface(MirSurfaceItem* surface);
86+ MirSurfaceItem* surface() const { return m_surface; }
87+
88+Q_SIGNALS:
89+ void surfaceChanged(MirSurfaceItem*);
90+
81− public:
91+public:
82 void showWindow(QQuickItem *parent);
92 void showWindow(QQuickItem *parent);
83 void hideWindow();
93 void hideWindow();
84
94
85− private Q_SLOTS:
95+private Q_SLOTS:
86 void onWindowComponentStatusChanged(QQmlComponent::Status status);
96 void onWindowComponentStatusChanged(QQmlComponent::Status status);
87− void setRunning();
97+ void onStateChanged(State state);
98+
99+ void createSurface();
88
100
89− private:
101+private:
90 void createWindowItem();
102 void createWindowItem();
91 void doCreateWindowItem();
103 void doCreateWindowItem();
92 void createWindowComponent();
104 void createWindowComponent();
93 QQuickItem *m_windowItem;
105 QQuickItem *m_windowItem;
94 QQmlComponent *m_windowComponent;
106 QQmlComponent *m_windowComponent;
95 QQuickItem *m_parentItem;
107 QQuickItem *m_parentItem;
108+ MirSurfaceItem* m_surface;
96};
109};
97
110
98Q_DECLARE_METATYPE(ApplicationInfo*)
111Q_DECLARE_METATYPE(ApplicationInfo*)
tests/mocks/Unity/Application/ApplicationManager.cpp+60−31
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2014 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
5 * it under the terms of the GNU General Public License as published by
@@ -15,7 +15,9 @@
15 */
15 */
16
16
17#include "ApplicationManager.h"
17#include "ApplicationManager.h"
18+#include "ApplicationDBusAdaptor.h"
18#include "ApplicationInfo.h"
19#include "ApplicationInfo.h"
20+#include "MirSurfaceItem.h"
19
21
20#include <paths.h>
22#include <paths.h>
21
23
@@ -26,6 +28,22 @@
26#include <QQmlComponent>
28#include <QQmlComponent>
27#include <QTimer>
29#include <QTimer>
28#include <QDateTime>
30#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+}
29
47
30ApplicationManager::ApplicationManager(QObject *parent)
48ApplicationManager::ApplicationManager(QObject *parent)
31 : ApplicationManagerInterface(parent)
49 : ApplicationManagerInterface(parent)
@@ -36,6 +54,9 @@ ApplicationManager::ApplicationManager(QObject *parent)
36 , m_sideStage(0)
54 , m_sideStage(0)
37 , m_rightMargin(0)
55 , m_rightMargin(0)
38{
56{
57+ m_roleNames.insert(RoleSurface, "surface");
58+ m_roleNames.insert(RoleFullscreen, "fullscreen");
59+
39 buildListOfAvailableApplications();
60 buildListOfAvailableApplications();
40}
61}
41
62
@@ -69,6 +90,10 @@ QVariant ApplicationManager::data(const QModelIndex& index, int role) const {
69 return app->focused();
90 return app->focused();
70 case RoleScreenshot:
91 case RoleScreenshot:
71 return app->screenshot();
92 return app->screenshot();
93+ case RoleSurface:
94+ return QVariant::fromValue(app->surface());
95+ case RoleFullscreen:
96+ return app->fullscreen();
72 default:
97 default:
73 return QVariant();
98 return QVariant();
74 }
99 }
@@ -89,6 +114,17 @@ ApplicationInfo *ApplicationManager::findApplication(const QString &appId) const
89 return nullptr;
114 return nullptr;
90}
115}
91
116
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+
92void ApplicationManager::add(ApplicationInfo *application) {
128void ApplicationManager::add(ApplicationInfo *application) {
93 if (!application) {
129 if (!application) {
94 return;
130 return;
@@ -99,7 +135,14 @@ void ApplicationManager::add(ApplicationInfo *application) {
99 endInsertRows();
135 endInsertRows();
100 Q_EMIT applicationAdded(application->appId());
136 Q_EMIT applicationAdded(application->appId());
101 Q_EMIT countChanged();
137 Q_EMIT countChanged();
138+ if (count() == 1) Q_EMIT emptyChanged(isEmpty()); // was empty but not anymore
102 Q_EMIT focusRequested(application->appId());
139 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+ });
103}
146}
104
147
105void ApplicationManager::remove(ApplicationInfo *application) {
148void ApplicationManager::remove(ApplicationInfo *application) {
@@ -110,7 +153,9 @@ void ApplicationManager::remove(ApplicationInfo *application) {
110 endRemoveRows();
153 endRemoveRows();
111 Q_EMIT applicationRemoved(application->appId());
154 Q_EMIT applicationRemoved(application->appId());
112 Q_EMIT countChanged();
155 Q_EMIT countChanged();
156+ if (isEmpty()) Q_EMIT emptyChanged(isEmpty());
113 }
157 }
158+ disconnect(application, &ApplicationInfo::surfaceChanged, this, 0);
114}
159}
115
160
116void ApplicationManager::move(int from, int to) {
161void ApplicationManager::move(int from, int to) {
@@ -127,16 +172,6 @@ void ApplicationManager::move(int from, int to) {
127 }
172 }
128}
173}
129
174
130−int ApplicationManager::keyboardHeight() const
131−{
132− return 0;
133−}
134−
135−bool ApplicationManager::keyboardVisible() const
136−{
137− return false;
138−}
139−
140int ApplicationManager::sideStageWidth() const
175int ApplicationManager::sideStageWidth() const
141{
176{
142 return 0;
177 return 0;
@@ -180,8 +215,7 @@ ApplicationInfo* ApplicationManager::startApplication(const QString &appId,
180 application->setStage(ApplicationInfo::MainStage);
215 application->setStage(ApplicationInfo::MainStage);
181 }
216 }
182 add(application);
217 add(application);
183−
218+ application->setState(ApplicationInfo::Running);
184− QMetaObject::invokeMethod(this, "focusApplication", Qt::QueuedConnection, Q_ARG(QString, appId));
185
219
186 return application;
220 return application;
187}
221}
@@ -195,6 +229,7 @@ bool ApplicationManager::stopApplication(const QString &appId)
195 if (application->appId() == focusedApplicationId()) {
229 if (application->appId() == focusedApplicationId()) {
196 unfocusCurrentApplication();
230 unfocusCurrentApplication();
197 }
231 }
232+ application->setState(ApplicationInfo::Stopped);
198 remove(application);
233 remove(application);
199 return true;
234 return true;
200}
235}
@@ -215,7 +250,6 @@ bool ApplicationManager::updateScreenshot(const QString &appId)
215 return false;
250 return false;
216 }
251 }
217
252
218− application->setScreenshot(QString("image://application/%1/%2").arg(appId).arg(QDateTime::currentMSecsSinceEpoch()));
219 QModelIndex appIndex = index(idx);
253 QModelIndex appIndex = index(idx);
220 Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << RoleScreenshot);
254 Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << RoleScreenshot);
221 return true;
255 return true;
@@ -261,6 +295,7 @@ bool ApplicationManager::focusApplication(const QString &appId)
261 if (app->focused() && app->stage() == ApplicationInfo::MainStage) {
295 if (app->focused() && app->stage() == ApplicationInfo::MainStage) {
262 app->setFocused(false);
296 app->setFocused(false);
263 app->hideWindow();
297 app->hideWindow();
298+ app->setState(ApplicationInfo::Suspended);
264 }
299 }
265 }
300 }
266
301
@@ -268,6 +303,7 @@ bool ApplicationManager::focusApplication(const QString &appId)
268 application->setFocused(true);
303 application->setFocused(true);
269 if (!m_mainStage)
304 if (!m_mainStage)
270 createMainStage();
305 createMainStage();
306+ application->setState(ApplicationInfo::Running);
271 application->showWindow(m_mainStage);
307 application->showWindow(m_mainStage);
272 m_mainStage->setZ(-1000);
308 m_mainStage->setZ(-1000);
273 if (m_sideStage)
309 if (m_sideStage)
@@ -278,6 +314,7 @@ bool ApplicationManager::focusApplication(const QString &appId)
278 if (app->focused() && app->stage() == ApplicationInfo::SideStage) {
314 if (app->focused() && app->stage() == ApplicationInfo::SideStage) {
279 app->setFocused(false);
315 app->setFocused(false);
280 app->hideWindow();
316 app->hideWindow();
317+ app->setState(ApplicationInfo::Suspended);
281 }
318 }
282 }
319 }
283
320
@@ -285,6 +322,7 @@ bool ApplicationManager::focusApplication(const QString &appId)
285 application->setFocused(true);
322 application->setFocused(true);
286 if (!m_sideStage)
323 if (!m_sideStage)
287 createSideStage();
324 createSideStage();
325+ application->setState(ApplicationInfo::Running);
288 application->showWindow(m_sideStage);
326 application->showWindow(m_sideStage);
289 m_sideStage->setZ(-1000);
327 m_sideStage->setZ(-1000);
290 if (m_mainStage)
328 if (m_mainStage)
@@ -354,6 +392,9 @@ void ApplicationManager::generateQmlStrings(ApplicationInfo *application)
354 "}").arg(qmlDirectory())
392 "}").arg(qmlDirectory())
355 .arg(application->icon().toString());
393 .arg(application->icon().toString());
356 application->setImageQml(imageQml);
394 application->setImageQml(imageQml);
395+
396+ application->setScreenshot(QString("file://%1/Dash/graphics/phone/screenshots/%2@12.png").arg(qmlDirectory())
397+ .arg(application->icon().toString()));
357}
398}
358
399
359void ApplicationManager::buildListOfAvailableApplications()
400void ApplicationManager::buildListOfAvailableApplications()
@@ -365,7 +406,6 @@ void ApplicationManager::buildListOfAvailableApplications()
365 application->setName("Dialer");
406 application->setName("Dialer");
366 application->setIcon(QUrl("dialer"));
407 application->setIcon(QUrl("dialer"));
367 application->setStage(ApplicationInfo::SideStage);
408 application->setStage(ApplicationInfo::SideStage);
368− application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
369 generateQmlStrings(application);
409 generateQmlStrings(application);
370 m_availableApplications.append(application);
410 m_availableApplications.append(application);
371
411
@@ -374,7 +414,6 @@ void ApplicationManager::buildListOfAvailableApplications()
374 application->setName("Camera");
414 application->setName("Camera");
375 application->setIcon(QUrl("camera"));
415 application->setIcon(QUrl("camera"));
376 application->setFullscreen(true);
416 application->setFullscreen(true);
377− application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
378 generateQmlStrings(application);
417 generateQmlStrings(application);
379 m_availableApplications.append(application);
418 m_availableApplications.append(application);
380
419
@@ -382,7 +421,6 @@ void ApplicationManager::buildListOfAvailableApplications()
382 application->setAppId("gallery-app");
421 application->setAppId("gallery-app");
383 application->setName("Gallery");
422 application->setName("Gallery");
384 application->setIcon(QUrl("gallery"));
423 application->setIcon(QUrl("gallery"));
385− application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
386 generateQmlStrings(application);
424 generateQmlStrings(application);
387 m_availableApplications.append(application);
425 m_availableApplications.append(application);
388
426
@@ -391,7 +429,6 @@ void ApplicationManager::buildListOfAvailableApplications()
391 application->setName("Facebook");
429 application->setName("Facebook");
392 application->setIcon(QUrl("facebook"));
430 application->setIcon(QUrl("facebook"));
393 application->setStage(ApplicationInfo::SideStage);
431 application->setStage(ApplicationInfo::SideStage);
394− application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
395 generateQmlStrings(application);
432 generateQmlStrings(application);
396 m_availableApplications.append(application);
433 m_availableApplications.append(application);
397
434
@@ -400,7 +437,6 @@ void ApplicationManager::buildListOfAvailableApplications()
400 application->setFullscreen(true);
437 application->setFullscreen(true);
401 application->setName("Browser");
438 application->setName("Browser");
402 application->setIcon(QUrl("browser"));
439 application->setIcon(QUrl("browser"));
403− application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
404 generateQmlStrings(application);
440 generateQmlStrings(application);
405 m_availableApplications.append(application);
441 m_availableApplications.append(application);
406
442
@@ -409,7 +445,6 @@ void ApplicationManager::buildListOfAvailableApplications()
409 application->setName("Twitter");
445 application->setName("Twitter");
410 application->setIcon(QUrl("twitter"));
446 application->setIcon(QUrl("twitter"));
411 application->setStage(ApplicationInfo::SideStage);
447 application->setStage(ApplicationInfo::SideStage);
412− application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
413 generateQmlStrings(application);
448 generateQmlStrings(application);
414 m_availableApplications.append(application);
449 m_availableApplications.append(application);
415
450
@@ -417,7 +452,6 @@ void ApplicationManager::buildListOfAvailableApplications()
417 application->setAppId("gmail-webapp");
452 application->setAppId("gmail-webapp");
418 application->setName("GMail");
453 application->setName("GMail");
419 application->setIcon(QUrl("gmail"));
454 application->setIcon(QUrl("gmail"));
420− application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
421 m_availableApplications.append(application);
455 m_availableApplications.append(application);
422
456
423 application = new ApplicationInfo(this);
457 application = new ApplicationInfo(this);
@@ -425,7 +459,6 @@ void ApplicationManager::buildListOfAvailableApplications()
425 application->setName("Weather");
459 application->setName("Weather");
426 application->setIcon(QUrl("weather"));
460 application->setIcon(QUrl("weather"));
427 application->setStage(ApplicationInfo::SideStage);
461 application->setStage(ApplicationInfo::SideStage);
428− application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
429 generateQmlStrings(application);
462 generateQmlStrings(application);
430 m_availableApplications.append(application);
463 m_availableApplications.append(application);
431
464
@@ -434,7 +467,6 @@ void ApplicationManager::buildListOfAvailableApplications()
434 application->setName("Notepad");
467 application->setName("Notepad");
435 application->setIcon(QUrl("notepad"));
468 application->setIcon(QUrl("notepad"));
436 application->setStage(ApplicationInfo::SideStage);
469 application->setStage(ApplicationInfo::SideStage);
437− application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
438 m_availableApplications.append(application);
470 m_availableApplications.append(application);
439
471
440 application = new ApplicationInfo(this);
472 application = new ApplicationInfo(this);
@@ -442,7 +474,6 @@ void ApplicationManager::buildListOfAvailableApplications()
442 application->setName("Calendar");
474 application->setName("Calendar");
443 application->setIcon(QUrl("calendar"));
475 application->setIcon(QUrl("calendar"));
444 application->setStage(ApplicationInfo::SideStage);
476 application->setStage(ApplicationInfo::SideStage);
445− application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
446 m_availableApplications.append(application);
477 m_availableApplications.append(application);
447
478
448 application = new ApplicationInfo(this);
479 application = new ApplicationInfo(this);
@@ -450,21 +481,18 @@ void ApplicationManager::buildListOfAvailableApplications()
450 application->setName("Media Player");
481 application->setName("Media Player");
451 application->setIcon(QUrl("mediaplayer-app"));
482 application->setIcon(QUrl("mediaplayer-app"));
452 application->setFullscreen(true);
483 application->setFullscreen(true);
453− application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
454 m_availableApplications.append(application);
484 m_availableApplications.append(application);
455
485
456 application = new ApplicationInfo(this);
486 application = new ApplicationInfo(this);
457 application->setAppId("evernote");
487 application->setAppId("evernote");
458 application->setName("Evernote");
488 application->setName("Evernote");
459 application->setIcon(QUrl("evernote"));
489 application->setIcon(QUrl("evernote"));
460− application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
461 m_availableApplications.append(application);
490 m_availableApplications.append(application);
462
491
463 application = new ApplicationInfo(this);
492 application = new ApplicationInfo(this);
464 application->setAppId("map");
493 application->setAppId("map");
465 application->setName("Map");
494 application->setName("Map");
466 application->setIcon(QUrl("map"));
495 application->setIcon(QUrl("map"));
467− application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
468 generateQmlStrings(application);
496 generateQmlStrings(application);
469 m_availableApplications.append(application);
497 m_availableApplications.append(application);
470
498
@@ -472,28 +500,24 @@ void ApplicationManager::buildListOfAvailableApplications()
472 application->setAppId("pinterest");
500 application->setAppId("pinterest");
473 application->setName("Pinterest");
501 application->setName("Pinterest");
474 application->setIcon(QUrl("pinterest"));
502 application->setIcon(QUrl("pinterest"));
475− application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
476 m_availableApplications.append(application);
503 m_availableApplications.append(application);
477
504
478 application = new ApplicationInfo(this);
505 application = new ApplicationInfo(this);
479 application->setAppId("soundcloud");
506 application->setAppId("soundcloud");
480 application->setName("SoundCloud");
507 application->setName("SoundCloud");
481 application->setIcon(QUrl("soundcloud"));
508 application->setIcon(QUrl("soundcloud"));
482− application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
483 m_availableApplications.append(application);
509 m_availableApplications.append(application);
484
510
485 application = new ApplicationInfo(this);
511 application = new ApplicationInfo(this);
486 application->setAppId("wikipedia");
512 application->setAppId("wikipedia");
487 application->setName("Wikipedia");
513 application->setName("Wikipedia");
488 application->setIcon(QUrl("wikipedia"));
514 application->setIcon(QUrl("wikipedia"));
489− application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
490 m_availableApplications.append(application);
515 m_availableApplications.append(application);
491
516
492 application = new ApplicationInfo(this);
517 application = new ApplicationInfo(this);
493 application->setAppId("youtube");
518 application->setAppId("youtube");
494 application->setName("YouTube");
519 application->setName("YouTube");
495 application->setIcon(QUrl("youtube"));
520 application->setIcon(QUrl("youtube"));
496− application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
497 m_availableApplications.append(application);
521 m_availableApplications.append(application);
498}
522}
499
523
@@ -590,3 +614,8 @@ void ApplicationManager::setRightMargin(int rightMargin)
590 generateQmlStrings(app);
614 generateQmlStrings(app);
591 }
615 }
592}
616}
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 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2014 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
5 * it under the terms of the GNU General Public License as published by
@@ -36,8 +36,7 @@ class ApplicationManager : public ApplicationManagerInterface {
36 Q_ENUMS(FavoriteApplication)
36 Q_ENUMS(FavoriteApplication)
37 Q_FLAGS(ExecFlags)
37 Q_FLAGS(ExecFlags)
38
38
39− Q_PROPERTY(int keyboardHeight READ keyboardHeight NOTIFY keyboardHeightChanged)
39+ Q_PROPERTY(bool empty READ isEmpty NOTIFY emptyChanged)
40− Q_PROPERTY(bool keyboardVisible READ keyboardVisible NOTIFY keyboardVisibleChanged)
41
40
42 Q_PROPERTY(int sideStageWidth READ sideStageWidth)
41 Q_PROPERTY(int sideStageWidth READ sideStageWidth)
43 Q_PROPERTY(StageHint stageHint READ stageHint)
42 Q_PROPERTY(StageHint stageHint READ stageHint)
@@ -53,6 +52,12 @@ class ApplicationManager : public ApplicationManagerInterface {
53 ApplicationManager(QObject *parent = NULL);
52 ApplicationManager(QObject *parent = NULL);
54 virtual ~ApplicationManager();
53 virtual ~ApplicationManager();
55
54
55+ static ApplicationManager *singleton();
56+
57+ enum MoreRoles {
58+ RoleSurface = RoleScreenshot+1,
59+ RoleFullscreen,
60+ };
56 enum Role {
61 enum Role {
57 Dash, Default, Indicators, Notifications, Greeter, Launcher, OnScreenKeyboard,
62 Dash, Default, Indicators, Notifications, Greeter, Launcher, OnScreenKeyboard,
58 ShutdownDialog
63 ShutdownDialog
@@ -74,8 +79,6 @@ class ApplicationManager : public ApplicationManagerInterface {
74 };
79 };
75 Q_DECLARE_FLAGS(ExecFlags, Flag)
80 Q_DECLARE_FLAGS(ExecFlags, Flag)
76
81
77− int keyboardHeight() const;
78− bool keyboardVisible() const;
79 int sideStageWidth() const;
82 int sideStageWidth() const;
80 StageHint stageHint() const;
83 StageHint stageHint() const;
81 FormFactorHint formFactorHint() const;
84 FormFactorHint formFactorHint() const;
@@ -108,11 +111,14 @@ class ApplicationManager : public ApplicationManagerInterface {
108 int rightMargin() const;
111 int rightMargin() const;
109 void setRightMargin(int rightMargin);
112 void setRightMargin(int rightMargin);
110
113
114+ QModelIndex findIndex(ApplicationInfo* application);
115+
116+ bool isEmpty() const;
117+
111 Q_SIGNALS:
118 Q_SIGNALS:
112− void keyboardHeightChanged();
113− void keyboardVisibleChanged();
114 void focusRequested(FavoriteApplication favoriteApplication);
119 void focusRequested(FavoriteApplication favoriteApplication);
115 void focusRequested(const QString &appId);
120 void focusRequested(const QString &appId);
121+ void emptyChanged(bool empty);
116
122
117 private:
123 private:
118 void add(ApplicationInfo *application);
124 void add(ApplicationInfo *application);
@@ -124,8 +130,6 @@ class ApplicationManager : public ApplicationManagerInterface {
124 void createMainStage();
130 void createMainStage();
125 void createSideStageComponent();
131 void createSideStageComponent();
126 void createSideStage();
132 void createSideStage();
127− int m_keyboardHeight;
128− bool m_keyboardVisible;
129 bool m_suspended;
133 bool m_suspended;
130 QList<ApplicationInfo*> m_runningApplications;
134 QList<ApplicationInfo*> m_runningApplications;
131 QList<ApplicationInfo*> m_availableApplications;
135 QList<ApplicationInfo*> m_availableApplications;
@@ -135,6 +139,8 @@ class ApplicationManager : public ApplicationManagerInterface {
135 QQuickItem *m_sideStage;
139 QQuickItem *m_sideStage;
136
140
137 int m_rightMargin;
141 int m_rightMargin;
142+
143+ static ApplicationManager *the_application_manager;
138};
144};
139
145
140Q_DECLARE_OPERATORS_FOR_FLAGS(ApplicationManager::ExecFlags)
146Q_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)
2
2
3set(FakeUnityApplicationQml_SOURCES
3set(FakeUnityApplicationQml_SOURCES
4 plugin.cpp
4 plugin.cpp
5+ ApplicationDBusAdaptor.cpp
5 ApplicationInfo.cpp
6 ApplicationInfo.cpp
6 ApplicationImage.cpp
7 ApplicationImage.cpp
7 ApplicationManager.cpp
8 ApplicationManager.cpp
8 ApplicationScreenshotProvider.cpp
9 ApplicationScreenshotProvider.cpp
10+ MirSurfaceItem.cpp
11+ SurfaceManager.cpp
12+ VirtualKeyboard.cpp
9 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
13 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
10 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
14 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
11)
15)
@@ -15,8 +19,8 @@ add_library(FakeUnityApplicationQml MODULE ${FakeUnityApplicationQml_SOURCES})
15add_library(NonMirUnityApplicationQml MODULE ${FakeUnityApplicationQml_SOURCES})
19add_library(NonMirUnityApplicationQml MODULE ${FakeUnityApplicationQml_SOURCES})
16set_target_properties(NonMirUnityApplicationQml PROPERTIES OUTPUT_NAME FakeUnityApplicationQml)
20set_target_properties(NonMirUnityApplicationQml PROPERTIES OUTPUT_NAME FakeUnityApplicationQml)
17
21
18−qt5_use_modules(FakeUnityApplicationQml Core Quick)
22+qt5_use_modules(FakeUnityApplicationQml Core Quick DBus)
19−qt5_use_modules(NonMirUnityApplicationQml Core Quick)
23+qt5_use_modules(NonMirUnityApplicationQml Core Quick DBus)
20
24
21add_unity8_mock(Unity.Application 0.1 Unity/Application PREFIX mocks TARGETS FakeUnityApplicationQml)
25add_unity8_mock(Unity.Application 0.1 Unity/Application PREFIX mocks TARGETS FakeUnityApplicationQml)
22add_unity8_mock(Unity.Application 0.1 Unity/Application
26add_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 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2014 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
5 * it under the terms of the GNU General Public License as published by
@@ -14,9 +14,22 @@
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
15 */
16
16
17−import QtQuick 2.0
17+#ifndef VIRTUALKEYBOARD_H
18−import Ubuntu.Components 0.1
18+#define VIRTUALKEYBOARD_H
19
19
20−BorderImage {
20+#include "MirSurfaceItem.h"
21− source: "graphics/sidestage_handle.sci"
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);
22−}
33+};
34+
35+#endif // VIRTUALKEYBOARD_H
tests/mocks/Unity/Application/plugin.cpp+16−9
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2014 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
5 * it under the terms of the GNU General Public License as published by
@@ -19,29 +19,37 @@
19#include "ApplicationImage.h"
19#include "ApplicationImage.h"
20#include "ApplicationManager.h"
20#include "ApplicationManager.h"
21#include "ApplicationScreenshotProvider.h"
21#include "ApplicationScreenshotProvider.h"
22+#include "MirSurfaceItem.h"
23+#include "SurfaceManager.h"
22
24
23#include <qqml.h>
25#include <qqml.h>
24#include <QQmlEngine>
26#include <QQmlEngine>
25
27
26−ApplicationManager *s_appManager = 0;
27−
28static QObject* applicationManagerSingleton(QQmlEngine* engine, QJSEngine* scriptEngine) {
28static QObject* applicationManagerSingleton(QQmlEngine* engine, QJSEngine* scriptEngine) {
29 Q_UNUSED(engine);
29 Q_UNUSED(engine);
30 Q_UNUSED(scriptEngine);
30 Q_UNUSED(scriptEngine);
31− if (!s_appManager) {
32− s_appManager = new ApplicationManager();
31+ return ApplicationManager::singleton();
33− }
32+}
34− return s_appManager;
33+
34+static QObject* surfaceManagerSingleton(QQmlEngine* engine, QJSEngine* scriptEngine) {
35+ Q_UNUSED(engine);
36+ Q_UNUSED(scriptEngine);
37+ return SurfaceManager::singleton();
35}
38}
36
39
37void FakeUnityApplicationQmlPlugin::registerTypes(const char *uri)
40void FakeUnityApplicationQmlPlugin::registerTypes(const char *uri)
38{
41{
42+ qRegisterMetaType<MirSurfaceItem*>("MirSurfaceItem*");
43+
39 qmlRegisterUncreatableType<unity::shell::application::ApplicationManagerInterface>(uri, 0, 1, "ApplicationManagerInterface", "Abstract interface. Cannot be created in QML");
44 qmlRegisterUncreatableType<unity::shell::application::ApplicationManagerInterface>(uri, 0, 1, "ApplicationManagerInterface", "Abstract interface. Cannot be created in QML");
40 qmlRegisterSingletonType<ApplicationManager>(uri, 0, 1, "ApplicationManager", applicationManagerSingleton);
45 qmlRegisterSingletonType<ApplicationManager>(uri, 0, 1, "ApplicationManager", applicationManagerSingleton);
46+ qmlRegisterSingletonType<SurfaceManager>(uri, 0, 1, "SurfaceManager", surfaceManagerSingleton);
41
47
42 qmlRegisterUncreatableType<unity::shell::application::ApplicationInfoInterface>(uri, 0, 1, "ApplicationInfoInterface", "Abstract interface. Cannot be created in QML");
48 qmlRegisterUncreatableType<unity::shell::application::ApplicationInfoInterface>(uri, 0, 1, "ApplicationInfoInterface", "Abstract interface. Cannot be created in QML");
43 qmlRegisterType<ApplicationInfo>(uri, 0, 1, "ApplicationInfo");
49 qmlRegisterType<ApplicationInfo>(uri, 0, 1, "ApplicationInfo");
44
50
51+ qmlRegisterUncreatableType<MirSurfaceItem>(uri, 0, 1, "MirSurfaceItem", "MirSurfaceItem can't be instantiated from QML");
52+
45 qmlRegisterType<ApplicationImage>(uri, 0, 1, "ApplicationImage");
53 qmlRegisterType<ApplicationImage>(uri, 0, 1, "ApplicationImage");
46}
54}
47
55
@@ -49,6 +57,5 @@ void FakeUnityApplicationQmlPlugin::initializeEngine(QQmlEngine *engine, const c
49{
57{
50 QQmlExtensionPlugin::initializeEngine(engine, uri);
58 QQmlExtensionPlugin::initializeEngine(engine, uri);
51
59
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()));
54}
61}
tests/mocks/Unity/CMakeLists.txt+1−1
@@ -6,7 +6,7 @@ add_subdirectory(Notifications)
6pkg_search_module(DEE dee-1.0 REQUIRED)
6pkg_search_module(DEE dee-1.0 REQUIRED)
7pkg_search_module(GOBJECT gobject-2.0 REQUIRED)
7pkg_search_module(GOBJECT gobject-2.0 REQUIRED)
8pkg_search_module(DEEQT libdee-qt5 REQUIRED)
8pkg_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)
10
10
11include_directories(
11include_directories(
12 ${CMAKE_CURRENT_BINARY_DIR}
12 ${CMAKE_CURRENT_BINARY_DIR}
tests/mocks/Unity/fake_scopes.cpp+5
@@ -125,6 +125,11 @@ bool Scopes::loaded() const
125 return m_loaded;
125 return m_loaded;
126}
126}
127
127
128+unity::shell::scopes::ScopeInterface* Scopes::overviewScope() const
129+{
130+ return nullptr;
131+}
132+
128void Scopes::addScope(Scope* scope)
133void Scopes::addScope(Scope* scope)
129{
134{
130 int index = rowCount();
135 int index = rowCount();
tests/mocks/Unity/fake_scopes.h+1
@@ -50,6 +50,7 @@ public:
50 QModelIndex parent ( const QModelIndex & index ) const;
50 QModelIndex parent ( const QModelIndex & index ) const;
51
51
52 bool loaded() const override;
52 bool loaded() const override;
53+ unity::shell::scopes::ScopeInterface* overviewScope() const override;
53
54
54private Q_SLOTS:
55private Q_SLOTS:
55 void updateScopes();
56 void updateScopes();
tests/plugins/Dash/CMakeLists.txt+1
@@ -79,3 +79,4 @@ set(qmltest_DEFAULT_NO_ADD_TEST TRUE)
79set(qmltest_DEFAULT_PROPERTIES ENVIRONMENT "LC_ALL=C")
79set(qmltest_DEFAULT_PROPERTIES ENVIRONMENT "LC_ALL=C")
80add_qml_test(. ScopeStyle IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
80add_qml_test(. ScopeStyle IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
81add_qml_test(. ListViewWithPageHeaderQML IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
81add_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 {
5 property var cardData;
5 property var cardData;
6 property var artShapeBorderSource: undefined;
6 property var artShapeBorderSource: undefined;
7 property real fontScale: 1.0;
7 property real fontScale: 1.0;
8− property var scopeStyle: null;
8+ property var scopeStyle: null;
9− property int headerAlignment: Text.AlignLeft;
9+ property int headerAlignment: Text.AlignLeft;
10 property int fixedHeaderHeight: -1;
10 property int fixedHeaderHeight: -1;
11 property size fixedArtShapeSize: Qt.size(-1, -1);
11 property size fixedArtShapeSize: Qt.size(-1, -1);
12 readonly property string title: cardData && cardData["title"] || "";
12 readonly property string title: cardData && cardData["title"] || "";
@@ -16,71 +16,73 @@ AbstractButton {
16onArtShapeBorderSourceChanged: { if (artShapeBorderSource !== undefined && artShapeLoader.item) artShapeLoader.item.borderSource = artShapeBorderSource; }
16onArtShapeBorderSourceChanged: { if (artShapeBorderSource !== undefined && artShapeLoader.item) artShapeLoader.item.borderSource = artShapeBorderSource; }
17readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
17readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
18Item {
18Item {
19− id: artShapeHolder;
19+ id: artShapeHolder;
20− height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
20+ height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
21− width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
21+ width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
22− anchors { horizontalCenter: parent.horizontalCenter; }
22+ anchors { horizontalCenter: parent.horizontalCenter; }
23− Loader {
23+ Loader {
24− id: artShapeLoader;
24+ id: artShapeLoader;
25− objectName: "artShapeLoader";
25+ objectName: "artShapeLoader";
26− active: cardData && cardData["art"] || false;
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;
56 asynchronous: root.asynchronous;
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;
56+ asynchronous: root.asynchronous;
57− fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop;
57+ fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop;
58− readonly property real aspect: implicitWidth / implicitHeight;
58+ readonly property real aspect: implicitWidth / implicitHeight;
59− width: root.width;
59+ width: root.width;
60− height: width / artShape.aspect;
60+ height: width / artShape.aspect;
61+ }
62+ }
61 }
63 }
62− }
64+ }
63− }
64− }
65readonly property int headerHeight: titleLabel.height;
65readonly property int headerHeight: titleLabel.height;
66Label {
66Label {
67− id: titleLabel;
67+ id: titleLabel;
68− objectName: "titleLabel";
68+ objectName: "titleLabel";
69− anchors { right: parent.right;left: parent.left;
69+ anchors { right: parent.right;
70+rightMargin: units.gu(1);
71+left: parent.left;
70top: artShapeHolder.bottom;
72top: artShapeHolder.bottom;
71 topMargin: units.gu(1);
73 topMargin: units.gu(1);
72leftMargin: units.gu(1);
74leftMargin: units.gu(1);
73− }
75+ }
74− elide: Text.ElideRight;
76+ elide: Text.ElideRight;
75− fontSize: "small";
77+ fontSize: "small";
76− wrapMode: Text.Wrap;
78+ wrapMode: Text.Wrap;
77− maximumLineCount: 2;
79+ maximumLineCount: 2;
78− font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
80+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
79− color: root.scopeStyle ? root.scopeStyle.foreground : "grey";
81+ color: root.scopeStyle ? root.scopeStyle.foreground : "grey";
80− visible: showHeader ;
82+ visible: showHeader ;
81− text: root.title;
83+ text: root.title;
82− font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
84+ font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
83− horizontalAlignment: root.headerAlignment;
85+ horizontalAlignment: root.headerAlignment;
84− }
86+ }
85implicitHeight: titleLabel.y + titleLabel.height + units.gu(1);
87implicitHeight: titleLabel.y + titleLabel.height + units.gu(1);
86}
88}
tests/plugins/Dash/cardcreator/2.res+94−91
@@ -5,8 +5,8 @@ AbstractButton {
5 property var cardData;
5 property var cardData;
6 property var artShapeBorderSource: undefined;
6 property var artShapeBorderSource: undefined;
7 property real fontScale: 1.0;
7 property real fontScale: 1.0;
8− property var scopeStyle: null;
8+ property var scopeStyle: null;
9− property int headerAlignment: Text.AlignLeft;
9+ property int headerAlignment: Text.AlignLeft;
10 property int fixedHeaderHeight: -1;
10 property int fixedHeaderHeight: -1;
11 property size fixedArtShapeSize: Qt.size(-1, -1);
11 property size fixedArtShapeSize: Qt.size(-1, -1);
12 readonly property string title: cardData && cardData["title"] || "";
12 readonly property string title: cardData && cardData["title"] || "";
@@ -14,104 +14,107 @@ AbstractButton {
14 property bool showHeader: true;
14 property bool showHeader: true;
15 implicitWidth: childrenRect.width;
15 implicitWidth: childrenRect.width;
16Loader {
16Loader {
17− id: backgroundLoader;
17+ id: backgroundLoader;
18− objectName: "backgroundLoader";
18+ objectName: "backgroundLoader";
19− anchors.fill: parent;
19+ anchors.fill: parent;
20− asynchronous: root.asynchronous;
20+ asynchronous: root.asynchronous;
21− visible: status == Loader.Ready;
21+ visible: status == Loader.Ready;
22− sourceComponent: UbuntuShape {
22+ sourceComponent: UbuntuShape {
23− objectName: "background";
23+ objectName: "background";
24− radius: "medium";
24+ radius: "medium";
25− color: getColor(0) || "white";
25+ color: getColor(0) || "white";
26− gradientColor: getColor(1) || color;
26+ gradientColor: getColor(1) || color;
27− anchors.fill: parent;
27+ anchors.fill: parent;
28− image: backgroundImage.source ? backgroundImage : null;
28+ image: backgroundImage.source ? backgroundImage : null;
29− property real luminance: 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
29+ property real luminance: 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
30− property Image backgroundImage: Image {
30+ property Image backgroundImage: Image {
31− objectName: "backgroundImage";
31+ objectName: "backgroundImage";
32− source: {
32+ source: {
33− if (cardData && typeof cardData["background"] === "string") return cardData["background"];
33+ if (cardData && typeof cardData["background"] === "string") return cardData["background"];
34− else if (template && typeof template["card-background"] === "string") return template["card-background"];
34+ else if (template && typeof template["card-background"] === "string") return template["card-background"];
35− else return "";
35+ else return "";
36− }
36+ }
37− }
37+ }
38− function getColor(index) {
38+ function getColor(index) {
39− if (cardData && typeof cardData["background"] === "object"
39+ if (cardData && typeof cardData["background"] === "object"
40− && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
40+ && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
41− return cardData["background"]["elements"][index];
41+ return cardData["background"]["elements"][index];
42− } else if (template && typeof template["card-background"] === "object"
42+ } else if (template && typeof template["card-background"] === "object"
43− && (template["card-background"]["type"] === "color" || template["card-background"]["type"] === "gradient")) {
43+ && (template["card-background"]["type"] === "color" || template["card-background"]["type"] === "gradient")) {
44− return template["card-background"]["elements"][index];
44+ return template["card-background"]["elements"][index];
45− } else return undefined;
45+ } else return undefined;
46− }
46+ }
47− }
47+ }
48− }
48+ }
49readonly property size artShapeSize: Qt.size(-1, -1);
49readonly property size artShapeSize: Qt.size(-1, -1);
50readonly property int headerHeight: row.height;
50readonly property int headerHeight: row.height;
51Row {
51Row {
52− id: row;
52+ id: row;
53− objectName: "outerRow";
53+ objectName: "outerRow";
54− property real margins: units.gu(1);
54+ property real margins: units.gu(1);
55− spacing: margins;
55+ spacing: margins;
56− height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight;
56+ height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight;
57− anchors { top: parent.top;
57+ anchors { top: parent.top;
58 topMargin: units.gu(1);
58 topMargin: units.gu(1);
59− left: parent.left;
59+left: parent.left;
60−}
60+ }
61− anchors.right: parent.right;
61+ anchors.right: parent.right;
62− anchors.margins: margins;
62+ anchors.margins: margins;
63−data: [ Image {
63+ data: [ Image {
64− id: mascotImage;
64+ id: mascotImage;
65− objectName: "mascotImage";
65+ objectName: "mascotImage";
66− anchors { verticalCenter: parent.verticalCenter; }
66+ anchors { verticalCenter: parent.verticalCenter; }
67− readonly property int maxSize: Math.max(width, height) * 4;
67+ readonly property int maxSize: Math.max(width, height) * 4;
68− source: cardData && cardData["mascot"];
68+ source: cardData && cardData["mascot"];
69− width: units.gu(6);
69+ width: units.gu(6);
70− height: units.gu(5.625);
70+ height: units.gu(5.625);
71− sourceSize { width: maxSize; height: maxSize }
71+ sourceSize { width: maxSize; height: maxSize }
72− fillMode: Image.PreserveAspectCrop;
72+ fillMode: Image.PreserveAspectCrop;
73− horizontalAlignment: Image.AlignHCenter;
73+ horizontalAlignment: Image.AlignHCenter;
74− verticalAlignment: Image.AlignVCenter;
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 ;
75+ visible: showHeader;
93− text: root.title;
76+ }
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 }
102
77
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 }
103 elide: Text.ElideRight;
87 elide: Text.ElideRight;
104 fontSize: "small";
88 fontSize: "small";
89+ wrapMode: Text.Wrap;
90+ maximumLineCount: 2;
105 font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
91 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");
92+ color: backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : (root.scopeStyle ? root.scopeStyle.foreground : "grey");
107− visible: titleLabel.visible && titleLabel.text;
93+ visible: showHeader ;
108− text: cardData && cardData["subtitle"] || "";
94+ text: root.title;
109− font.weight: Font.Light;
95+ font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
110 horizontalAlignment: root.headerAlignment;
96 horizontalAlignment: root.headerAlignment;
111 }
97 }
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+
112−]
114+ ]
113−}
115+ }
116+
114−]
117+ ]
115−}
118+ }
116implicitHeight: row.y + row.height + units.gu(1);
119implicitHeight: row.y + row.height + units.gu(1);
117}
120}
tests/plugins/Dash/cardcreator/3.res+77−74
@@ -5,8 +5,8 @@ AbstractButton {
5 property var cardData;
5 property var cardData;
6 property var artShapeBorderSource: undefined;
6 property var artShapeBorderSource: undefined;
7 property real fontScale: 1.0;
7 property real fontScale: 1.0;
8− property var scopeStyle: null;
8+ property var scopeStyle: null;
9− property int headerAlignment: Text.AlignLeft;
9+ property int headerAlignment: Text.AlignLeft;
10 property int fixedHeaderHeight: -1;
10 property int fixedHeaderHeight: -1;
11 property size fixedArtShapeSize: Qt.size(-1, -1);
11 property size fixedArtShapeSize: Qt.size(-1, -1);
12 readonly property string title: cardData && cardData["title"] || "";
12 readonly property string title: cardData && cardData["title"] || "";
@@ -16,89 +16,92 @@ AbstractButton {
16onArtShapeBorderSourceChanged: { if (artShapeBorderSource !== undefined && artShapeLoader.item) artShapeLoader.item.borderSource = artShapeBorderSource; }
16onArtShapeBorderSourceChanged: { if (artShapeBorderSource !== undefined && artShapeLoader.item) artShapeLoader.item.borderSource = artShapeBorderSource; }
17readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
17readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
18Item {
18Item {
19− id: artShapeHolder;
19+ id: artShapeHolder;
20− height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
20+ height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
21− width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
21+ width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
22− anchors { horizontalCenter: parent.horizontalCenter; }
22+ anchors { horizontalCenter: parent.horizontalCenter; }
23− Loader {
23+ Loader {
24− id: artShapeLoader;
24+ id: artShapeLoader;
25− objectName: "artShapeLoader";
25+ objectName: "artShapeLoader";
26− active: cardData && cardData["art"] || false;
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;
56 asynchronous: root.asynchronous;
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;
56+ asynchronous: root.asynchronous;
57− fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop;
57+ fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop;
58− readonly property real aspect: implicitWidth / implicitHeight;
58+ readonly property real aspect: implicitWidth / implicitHeight;
59− width: root.width;
59+ width: root.width;
60− height: width / artShape.aspect;
60+ height: width / artShape.aspect;
61+ }
62+ }
61 }
63 }
62− }
64+ }
63− }
64− }
65readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;
65readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;
66Label {
66Label {
67− id: titleLabel;
67+ id: titleLabel;
68− objectName: "titleLabel";
68+ objectName: "titleLabel";
69− anchors { right: parent.right;left: parent.left;
69+ anchors { right: parent.right;
70+rightMargin: units.gu(1);
71+left: parent.left;
70top: artShapeHolder.bottom;
72top: artShapeHolder.bottom;
71 topMargin: units.gu(1);
73 topMargin: units.gu(1);
72leftMargin: units.gu(1);
74leftMargin: 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+ }
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− }
94 elide: Text.ElideRight;
76 elide: Text.ElideRight;
95 fontSize: "small";
77 fontSize: "small";
78+ wrapMode: Text.Wrap;
79+ maximumLineCount: 2;
96 font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
80 font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
97− color: root.scopeStyle ? root.scopeStyle.foreground : "grey";
81+ color: root.scopeStyle ? root.scopeStyle.foreground : "grey";
98− visible: titleLabel.visible && titleLabel.text;
82+ visible: showHeader ;
99− text: cardData && cardData["subtitle"] || "";
83+ text: root.title;
100− font.weight: Font.Light;
84+ font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
101 horizontalAlignment: root.headerAlignment;
85 horizontalAlignment: root.headerAlignment;
102 }
86 }
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+ }
103implicitHeight: subtitleLabel.y + subtitleLabel.height + units.gu(1);
106implicitHeight: subtitleLabel.y + subtitleLabel.height + units.gu(1);
104}
107}
tests/plugins/Dash/cardcreator/5.res+119−117
@@ -4,9 +4,9 @@ AbstractButton {
4 property var components;
4 property var components;
5 property var cardData;
5 property var cardData;
6 property var artShapeBorderSource: undefined;
6 property var artShapeBorderSource: undefined;
7− property real fontScale: 1.0;
7+ property real fontScale: 1.0;
8− property var scopeStyle: null;
8+ property var scopeStyle: null;
9− property int headerAlignment: Text.AlignLeft;
9+ property int headerAlignment: Text.AlignLeft;
10 property int fixedHeaderHeight: -1;
10 property int fixedHeaderHeight: -1;
11 property size fixedArtShapeSize: Qt.size(-1, -1);
11 property size fixedArtShapeSize: Qt.size(-1, -1);
12 readonly property string title: cardData && cardData["title"] || "";
12 readonly property string title: cardData && cardData["title"] || "";
@@ -16,130 +16,132 @@ AbstractButton {
16onArtShapeBorderSourceChanged: { if (artShapeBorderSource !== undefined && artShapeLoader.item) artShapeLoader.item.borderSource = artShapeBorderSource; }
16onArtShapeBorderSourceChanged: { if (artShapeBorderSource !== undefined && artShapeLoader.item) artShapeLoader.item.borderSource = artShapeBorderSource; }
17readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
17readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
18Item {
18Item {
19− id: artShapeHolder;
19+ id: artShapeHolder;
20− height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
20+ height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
21− width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
21+ width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
22− anchors { horizontalCenter: parent.horizontalCenter; }
22+ anchors { horizontalCenter: parent.horizontalCenter; }
23− Loader {
23+ Loader {
24− id: artShapeLoader;
24+ id: artShapeLoader;
25− objectName: "artShapeLoader";
25+ objectName: "artShapeLoader";
26− active: cardData && cardData["art"] || false;
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;
56 asynchronous: root.asynchronous;
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;
56+ asynchronous: root.asynchronous;
57− fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop;
57+ fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop;
58− readonly property real aspect: implicitWidth / implicitHeight;
58+ readonly property real aspect: implicitWidth / implicitHeight;
59− width: root.width;
59+ width: root.width;
60− height: width / artShape.aspect;
60+ height: width / artShape.aspect;
61+ }
62+ }
61 }
63 }
62− }
64+ }
63− }
64− }
65Loader {
65Loader {
66− id: overlayLoader;
66+ id: overlayLoader;
67− anchors {
67+ anchors {
68− left: artShapeHolder.left;
68+ left: artShapeHolder.left;
69− right: artShapeHolder.right;
69+ right: artShapeHolder.right;
70− bottom: artShapeHolder.bottom;
70+ bottom: artShapeHolder.bottom;
71− }
71+ }
72− active: artShapeLoader.active && artShapeLoader.item && artShapeLoader.item.image.status === Image.Ready || false;
72+ active: artShapeLoader.active && artShapeLoader.item && artShapeLoader.item.image.status === Image.Ready || false;
73− asynchronous: root.asynchronous;
73+ asynchronous: root.asynchronous;
74− visible: showHeader && status == Loader.Ready;
74+ visible: showHeader && status == Loader.Ready;
75− sourceComponent: ShaderEffect {
75+ sourceComponent: ShaderEffect {
76− id: overlay;
76+ id: overlay;
77− height: (fixedHeaderHeight > 0 ? fixedHeaderHeight : headerHeight) + units.gu(2);
77+ height: (fixedHeaderHeight > 0 ? fixedHeaderHeight : headerHeight) + units.gu(2);
78− opacity: 0.6;
78+ opacity: 0.6;
79− property var source: ShaderEffectSource {
79+ property var source: ShaderEffectSource {
80− id: shaderSource;
80+ id: shaderSource;
81− sourceItem: artShapeLoader.item;
81+ sourceItem: artShapeLoader.item;
82− onVisibleChanged: if (visible) scheduleUpdate();
82+ onVisibleChanged: if (visible) scheduleUpdate();
83− live: false;
83+ live: false;
84− sourceRect: Qt.rect(0, artShapeLoader.height - overlay.height, artShapeLoader.width, overlay.height);
84+ sourceRect: Qt.rect(0, artShapeLoader.height - overlay.height, artShapeLoader.width, overlay.height);
85− }
85+ }
86− vertexShader: "
86+ vertexShader: "
87− uniform highp mat4 qt_Matrix;
87+ uniform highp mat4 qt_Matrix;
88− attribute highp vec4 qt_Vertex;
88+ attribute highp vec4 qt_Vertex;
89− attribute highp vec2 qt_MultiTexCoord0;
89+ attribute highp vec2 qt_MultiTexCoord0;
90− varying highp vec2 coord;
90+ varying highp vec2 coord;
91− void main() {
91+ void main() {
92− coord = qt_MultiTexCoord0;
92+ coord = qt_MultiTexCoord0;
93− gl_Position = qt_Matrix * qt_Vertex;
93+ gl_Position = qt_Matrix * qt_Vertex;
94− }";
94+ }";
95− fragmentShader: "
95+ fragmentShader: "
96− varying highp vec2 coord;
96+ varying highp vec2 coord;
97− uniform sampler2D source;
97+ uniform sampler2D source;
98− uniform lowp float qt_Opacity;
98+ uniform lowp float qt_Opacity;
99− void main() {
99+ void main() {
100− lowp vec4 tex = texture2D(source, coord);
100+ lowp vec4 tex = texture2D(source, coord);
101− gl_FragColor = vec4(0, 0, 0, tex.a) * qt_Opacity;
101+ gl_FragColor = vec4(0, 0, 0, tex.a) * qt_Opacity;
102− }";
102+ }";
103− }
103+ }
104− }
104+ }
105readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;
105readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;
106Label {
106Label {
107− id: titleLabel;
107+ id: titleLabel;
108− objectName: "titleLabel";
108+ objectName: "titleLabel";
109− anchors { left: parent.left;
109+ anchors { left: parent.left;
110− leftMargin: units.gu(1);
110+ leftMargin: units.gu(1);
111− right: parent.right;
111+ right: parent.right;
112− top: overlayLoader.top;
113− topMargin: units.gu(1);
112+ rightMargin: 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;
113+ top: overlayLoader.top;
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);
114+ topMargin: units.gu(1);
134− }
115+ }
135 elide: Text.ElideRight;
116 elide: Text.ElideRight;
136 fontSize: "small";
117 fontSize: "small";
118+ wrapMode: Text.Wrap;
119+ maximumLineCount: 2;
137 font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
120 font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
138 color: "white";
121 color: "white";
139− visible: titleLabel.visible && titleLabel.text;
122+ visible: showHeader && overlayLoader.active;
140− text: cardData && cardData["subtitle"] || "";
123+ text: root.title;
141− font.weight: Font.Light;
124+ font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
142 horizontalAlignment: root.headerAlignment;
125 horizontalAlignment: root.headerAlignment;
143 }
126 }
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+ }
144implicitHeight: subtitleLabel.y + subtitleLabel.height + units.gu(1);
146implicitHeight: subtitleLabel.y + subtitleLabel.height + units.gu(1);
145}
147}
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 @@
19#include <QQuickItem>
19#include <QQuickItem>
20#include <QQuickView>
20#include <QQuickView>
21#include <QtTestGui>
21#include <QtTestGui>
22+#include <QTemporaryFile>
22
23
23class CardCreatorTest : public QObject
24class CardCreatorTest : public QObject
24{
25{
@@ -74,7 +75,14 @@ private Q_SLOTS:
74 QVERIFY(testResultFile.open(QIODevice::ReadOnly));
75 QVERIFY(testResultFile.open(QIODevice::ReadOnly));
75 QTextStream ts2(&testResultFile);
76 QTextStream ts2(&testResultFile);
76 const QString expectedResult = ts2.readAll();
77 const QString expectedResult = ts2.readAll();
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());
77− QCOMPARE(cardStringResult.toString().simplified(), expectedResult.simplified());
84+ QCOMPARE(executedResult.simplified(), expectedResult.simplified());
85+ tmpFile.setAutoRemove(true); // Remove the result if it passed
78
86
79 QVariant createCardComponentResult;
87 QVariant createCardComponentResult;
80 QMetaObject::invokeMethod(view->rootObject(), "createCardComponent", Q_RETURN_ARG(QVariant, createCardComponentResult), Q_ARG(QVariant, templateJSON), Q_ARG(QVariant, componentsJSON));
88 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:
40 QTRY_COMPARE(lvwph->m_visibleItems[visibleIndex]->height(), height);
40 QTRY_COMPARE(lvwph->m_visibleItems[visibleIndex]->height(), height);
41 QCOMPARE(QQuickItemPrivate::get(lvwph->m_visibleItems[visibleIndex]->m_item)->culled, culled);
41 QCOMPARE(QQuickItemPrivate::get(lvwph->m_visibleItems[visibleIndex]->m_item)->culled, culled);
42 QCOMPARE(section(lvwph->m_visibleItems[visibleIndex]->sectionItem()), sectionHeader);
42 QCOMPARE(section(lvwph->m_visibleItems[visibleIndex]->sectionItem()), sectionHeader);
43− QTRY_COMPARE(hasSectionHeaderProperty(lvwph->m_visibleItems[visibleIndex]->m_item), !sectionHeader.isNull());
44 if (!sectionHeader.isNull()) {
43 if (!sectionHeader.isNull()) {
45 QCOMPARE(QQuickItemPrivate::get(lvwph->m_visibleItems[visibleIndex]->sectionItem())->culled, sectionHeaderCulled);
44 QCOMPARE(QQuickItemPrivate::get(lvwph->m_visibleItems[visibleIndex]->sectionItem())->culled, sectionHeaderCulled);
46 QCOMPARE(sectionDelegateIndex(lvwph->m_visibleItems[visibleIndex]->sectionItem()), lvwph->m_firstVisibleIndex + visibleIndex);
45 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)
40add_qml_benchmark(Dash CardBenchmark 1000)
40add_qml_benchmark(Dash CardBenchmark 1000)
41add_qml_test(Dash CardTool)
41add_qml_test(Dash CardTool)
42add_qml_test(Dash GenericScopeView)
42add_qml_test(Dash GenericScopeView)
43−add_qml_test(Dash/Apps RunningApplicationsGrid)
44add_qml_test(Dash/Previews Preview)
43add_qml_test(Dash/Previews Preview)
45add_qml_test(Dash/Previews PreviewActions)
44add_qml_test(Dash/Previews PreviewActions)
46add_qml_test(Dash/Previews PreviewAudioPlayback)
45add_qml_test(Dash/Previews PreviewAudioPlayback)
tests/qmltests/Components/tst_PageHeader.qml+36−6
@@ -36,6 +36,16 @@ Item {
36
36
37 function init() {
37 function init() {
38 searchEnabled = true;
38 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);
39 }
49 }
40
50
41 function test_search_disabled() {
51 function test_search_disabled() {
@@ -80,9 +90,6 @@ Item {
80 }
90 }
81
91
82 function test_history() {
92 function test_history() {
83− pageHeader.searchHistory.clear()
84− compare(pageHeader.searchHistory.count, 0)
85−
86 pageHeader.triggerSearch()
93 pageHeader.triggerSearch()
87 typeString("humppa1")
94 typeString("humppa1")
88 pageHeader.resetSearch()
95 pageHeader.resetSearch()
@@ -166,16 +173,26 @@ Item {
166 tryCompareFunction(function() { return headerContainer.popover !== null; }, true);
173 tryCompareFunction(function() { return headerContainer.popover !== null; }, true);
167
174
168 tryCompare(headerContainer.popover, "visible", true);
175 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);
169 }
186 }
170
187
171− function test_tap_outside_closes_popup_data() {
188+ function test_popup_closing_data() {
172 return [
189 return [
173 { tag: "with search text", searchText: "foobar", hideSearch: false },
190 { tag: "with search text", searchText: "foobar", hideSearch: false },
174 { tag: "without search text", searchText: "", hideSearch: true }
191 { tag: "without search text", searchText: "", hideSearch: true }
175 ];
192 ];
176 }
193 }
177
194
178− function test_tap_outside_closes_popup(data) {
195+ function test_popup_closing(data) {
179 searchEnabled = true;
196 searchEnabled = true;
180 pageHeader.searchHistory.clear();
197 pageHeader.searchHistory.clear();
181
198
@@ -191,13 +208,26 @@ Item {
191
208
192 pageHeader.searchQuery = data.searchText;
209 pageHeader.searchQuery = data.searchText;
193
210
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
194− mouseClick(root, root.width / 2, root.height - 1);
214+ mouseClick(root, root.width / 2, root.height - 1);
215+ }
195
216
196 tryCompare(headerContainer, "showSearch", !data.hideSearch);
217 tryCompare(headerContainer, "showSearch", !data.hideSearch);
197 tryCompareFunction(function() { return headerContainer.popover === null; }, true);
218 tryCompareFunction(function() { return headerContainer.popover === null; }, true);
198
219
199 pageHeader.resetSearch();
220 pageHeader.resetSearch();
200 }
221 }
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+ }
201 }
231 }
202
232
203 Column {
233 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 @@
16
16
17.pragma library
17.pragma library
18
18
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"]
20
20
21var defaultLayout = ' \
21var defaultLayout = ' \
22{ \
22{ \
@@ -48,7 +48,8 @@ var defaultLayout = ' \
48 "empty": "image://theme/rating-star-empty" \
48 "empty": "image://theme/rating-star-empty" \
49 }, \
49 }, \
50 "alt-rating": null, \
50 "alt-rating": null, \
51− "summary": null \
51+ "summary": null, \
52+ "attributes": { "max-count": 2 } \
52 }, \
53 }, \
53 "resources": {} \
54 "resources": {} \
54}'
55}'
@@ -59,7 +60,8 @@ var fullMapping = ' \
59 "art": "art", \
60 "art": "art", \
60 "subtitle": "subtitle", \
61 "subtitle": "subtitle", \
61 "mascot": "mascot", \
62 "mascot": "mascot", \
62− "summary": "summary" \
63+ "summary": "summary", \
64+ "attributes": "attributes" \
63}'
65}'
64
66
65
67
tests/qmltests/Dash/tst_Card.qml+4−2
@@ -33,7 +33,8 @@ Rectangle {
33 "mascot": "../../../tests/qmltests/Dash/artwork/avatar.png",
33 "mascot": "../../../tests/qmltests/Dash/artwork/avatar.png",
34 "title": "foo",
34 "title": "foo",
35 "subtitle": "bar",
35 "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"}]
37 }'
38 }'
38
39
39 property var cardsModel: [
40 property var cardsModel: [
@@ -110,6 +111,7 @@ Rectangle {
110 anchors { top: parent.top; left: parent.left; margins: units.gu(1) }
111 anchors { top: parent.top; left: parent.left; margins: units.gu(1) }
111
112
112 sourceComponent: cardTool.cardComponent
113 sourceComponent: cardTool.cardComponent
114+ clip: true
113 onLoaded: {
115 onLoaded: {
114 item.template = Qt.binding(function() { return cardTool.template; });
116 item.template = Qt.binding(function() { return cardTool.template; });
115 item.components = Qt.binding(function() { return cardTool.components; });
117 item.components = Qt.binding(function() { return cardTool.components; });
@@ -359,7 +361,7 @@ Rectangle {
359
361
360 waitForRendering(card);
362 waitForRendering(card);
361
363
362− tryCompareFunction(function() { return testCase.summary.y === data.top() }, true);
364+ tryCompareFunction(function() { return art.height > 0 && testCase.summary.y === data.top() }, true);
363 }
365 }
364
366
365 function test_art_visibility() {
367 function test_art_visibility() {
tests/qmltests/Greeter/tst_Lockscreen.qml+11−5
@@ -18,8 +18,9 @@ import QtQuick 2.0
18import QtTest 1.0
18import QtTest 1.0
19import ".."
19import ".."
20import "../../../qml/Components"
20import "../../../qml/Components"
21−import Ubuntu.Components 0.1
21+import AccountsService 0.1
22import LightDM 0.1 as LightDM
22import LightDM 0.1 as LightDM
23+import Ubuntu.Components 0.1
23import Unity.Test 0.1 as UT
24import Unity.Test 0.1 as UT
24
25
25Rectangle {
26Rectangle {
@@ -53,11 +54,16 @@ Rectangle {
53 }
54 }
54 }
55 }
55
56
57+ function setUser(username) {
58+ AccountsService.user = username
59+ LightDM.Greeter.authenticate(username)
60+ }
61+
56 Connections {
62 Connections {
57 target: LightDM.Greeter
63 target: LightDM.Greeter
58
64
59 onShowPrompt: {
65 onShowPrompt: {
60− if (text === "PIN") {
66+ if (AccountsService.passwordDisplayHint === AccountsService.Numeric) {
61 pinPadCheckBox.checked = false
67 pinPadCheckBox.checked = false
62 } else {
68 } else {
63 pinPadCheckBox.checked = true
69 pinPadCheckBox.checked = true
@@ -139,12 +145,12 @@ Rectangle {
139 Button {
145 Button {
140 text: "start auth (1234)"
146 text: "start auth (1234)"
141 width: parent.width
147 width: parent.width
142− onClicked: LightDM.Greeter.authenticate("has-pin")
148+ onClicked: setUser("has-pin")
143 }
149 }
144 Button {
150 Button {
145 text: "start auth (password)"
151 text: "start auth (password)"
146 width: parent.width
152 width: parent.width
147− onClicked: LightDM.Greeter.authenticate("has-password")
153+ onClicked: setUser("has-password")
148 }
154 }
149
155
150 TextField {
156 TextField {
@@ -255,7 +261,7 @@ Rectangle {
255 enteredLabel.text = ""
261 enteredLabel.text = ""
256 minPinLengthTextField.text = data.minPinLength
262 minPinLengthTextField.text = data.minPinLength
257 maxPinLengthTextField.text = data.maxPinLength
263 maxPinLengthTextField.text = data.maxPinLength
258− LightDM.Greeter.authenticate(data.username)
264+ setUser(data.username)
259 waitForLockscreenReady();
265 waitForLockscreenReady();
260
266
261 var inputField = findChild(lockscreen, "pinentryField")
267 var inputField = findChild(lockscreen, "pinentryField")
tests/qmltests/Launcher/tst_Launcher.qml+11−2
@@ -94,9 +94,16 @@ Item {
94 name: "Launcher"
94 name: "Launcher"
95 when: windowShown && initTestCase.completed
95 when: windowShown && initTestCase.completed
96
96
97+ function cleanup() {
98+ mouseClick(root, root.width / 2, root.height / 2);
99+ revealer.waitUntilLauncherDisappears();
100+ }
101+
97 // Drag from the left edge of the screen rightwards and check that the launcher
102 // Drag from the left edge of the screen rightwards and check that the launcher
98 // appears (as if being dragged by the finger/pointer)
103 // appears (as if being dragged by the finger/pointer)
99 function test_dragLeftEdgeToRevealLauncherAndTapCenterToDismiss() {
104 function test_dragLeftEdgeToRevealLauncherAndTapCenterToDismiss() {
105+ revealer.waitUntilLauncherDisappears();
106+
100 var panel = findChild(launcher, "launcherPanel")
107 var panel = findChild(launcher, "launcherPanel")
101 verify(panel != undefined)
108 verify(panel != undefined)
102
109
@@ -117,10 +124,9 @@ Item {
117 corresponding desktop file. E.g. clicking on phone icon should yield
124 corresponding desktop file. E.g. clicking on phone icon should yield
118 launcherApplicationSelected("[...]dialer-app.desktop") */
125 launcherApplicationSelected("[...]dialer-app.desktop") */
119 function test_clickingOnAppIconCausesSignalEmission() {
126 function test_clickingOnAppIconCausesSignalEmission() {
127+ revealer.dragLauncherIntoView();
120 launcher.lastSelectedApplication = ""
128 launcher.lastSelectedApplication = ""
121
129
122− revealer.dragLauncherIntoView()
123−
124 var listView = findChild(launcher, "launcherListView");
130 var listView = findChild(launcher, "launcherListView");
125 listView.positionViewAtEnd();
131 listView.positionViewAtEnd();
126
132
@@ -184,6 +190,7 @@ Item {
184 }
190 }
185
191
186 function test_countEmblems() {
192 function test_countEmblems() {
193+ revealer.dragLauncherIntoView();
187 var launcherListView = findChild(launcher, "launcherListView");
194 var launcherListView = findChild(launcher, "launcherListView");
188 for (var i = 0; i < launcherListView.count; ++i) {
195 for (var i = 0; i < launcherListView.count; ++i) {
189 var delegate = findChild(launcherListView, "launcherDelegate" + i)
196 var delegate = findChild(launcherListView, "launcherDelegate" + i)
@@ -192,6 +199,7 @@ Item {
192 }
199 }
193
200
194 function test_progressOverlays() {
201 function test_progressOverlays() {
202+ revealer.dragLauncherIntoView();
195 var launcherListView = findChild(launcher, "launcherListView");
203 var launcherListView = findChild(launcher, "launcherListView");
196 for (var i = 0; i < launcherListView.count; ++i) {
204 for (var i = 0; i < launcherListView.count; ++i) {
197 var delegate = findChild(launcherListView, "launcherDelegate" + i)
205 var delegate = findChild(launcherListView, "launcherDelegate" + i)
@@ -200,6 +208,7 @@ Item {
200 }
208 }
201
209
202 function test_focusedHighlight() {
210 function test_focusedHighlight() {
211+ revealer.dragLauncherIntoView();
203 var launcherListView = findChild(launcher, "launcherListView");
212 var launcherListView = findChild(launcher, "launcherListView");
204 for (var i = 0; i < launcherListView.count; ++i) {
213 for (var i = 0; i < launcherListView.count; ++i) {
205 var delegate = findChild(launcherListView, "launcherDelegate" + i)
214 var delegate = findChild(launcherListView, "launcherDelegate" + i)
tests/qmltests/Panel/tst_ActiveCallHint.qml+1
@@ -95,6 +95,7 @@ Item {
95 function test_activeHint(data) {
95 function test_activeHint(data) {
96 if (data.dialer) {
96 if (data.dialer) {
97 var application = ApplicationManager.startApplication("dialer-app");
97 var application = ApplicationManager.startApplication("dialer-app");
98+ ApplicationManager.focusApplication("dialer-app");
98 tryCompare(ApplicationManager, "focusedApplicationId", "dialer-app");
99 tryCompare(ApplicationManager, "focusedApplicationId", "dialer-app");
99 tryCompare(application, "state", ApplicationInfo.Running);
100 tryCompare(application, "state", ApplicationInfo.Running);
100
101
tests/qmltests/Stages/tst_PhoneStage.qml+9−56
@@ -33,7 +33,6 @@ Item {
33 PhoneStage {
33 PhoneStage {
34 id: phoneStage
34 id: phoneStage
35 anchors { fill: parent; rightMargin: units.gu(30) }
35 anchors { fill: parent; rightMargin: units.gu(30) }
36− shown: true
37 dragAreaWidth: units.gu(2)
36 dragAreaWidth: units.gu(2)
38 }
37 }
39
38
@@ -45,7 +44,6 @@ Item {
45
44
46 Rectangle {
45 Rectangle {
47 anchors { fill: parent; leftMargin: phoneStage.width }
46 anchors { fill: parent; leftMargin: phoneStage.width }
48−// color: "blue"
49
47
50 Column {
48 Column {
51 anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
49 anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
@@ -59,7 +57,10 @@ Item {
59 }
57 }
60 Button {
58 Button {
61 anchors { left: parent.left; right: parent.right }
59 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+ }
63 }
64 }
64 }
65 }
65 }
66 }
@@ -74,11 +75,8 @@ Item {
74 for (var i = 0; i < count; i++) {
75 for (var i = 0; i < count; i++) {
75 var app = ApplicationManager.startApplication(ApplicationManager.availableApplications()[ApplicationManager.count])
76 var app = ApplicationManager.startApplication(ApplicationManager.availableApplications()[ApplicationManager.count])
76 tryCompare(app, "state", ApplicationInfoInterface.Running)
77 tryCompare(app, "state", ApplicationInfoInterface.Running)
77− // Fixme: Right now there is a timeout in the PhoneStage that displays a white splash
78+ var spreadView = findChild(phoneStage, "spreadView");
78− // screen rectangle when an app starts. This is because we don't yet have a way of
79+ tryCompare(spreadView, "contentX", -spreadView.shift);
79− // knowing when an app has finished launching. That workaround and this wait() should
80− // go away at some point and the app's state only changing to Running when ready for real.
81−// wait(1000)
82 waitForRendering(phoneStage)
80 waitForRendering(phoneStage)
83 }
81 }
84 }
82 }
@@ -114,8 +112,6 @@ Item {
114 true /* beginTouch */, true /* endTouch */, units.gu(10), 50);
112 true /* beginTouch */, true /* endTouch */, units.gu(10), 50);
115
113
116 tryCompare(ApplicationManager, "focusedApplicationId", activeApp.appId)
114 tryCompare(ApplicationManager, "focusedApplicationId", activeApp.appId)
117−
118− tryCompare(phoneStage, "painting", false);
119 }
115 }
120
116
121 function test_enterSpread_data() {
117 function test_enterSpread_data() {
@@ -139,7 +135,7 @@ Item {
139 var startX = phoneStage.width;
135 var startX = phoneStage.width;
140 var startY = phoneStage.height / 2;
136 var startY = phoneStage.height / 2;
141 var endY = startY;
137 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;
143
139
144 var oldFocusedApp = ApplicationManager.get(0);
140 var oldFocusedApp = ApplicationManager.get(0);
145 var newFocusedApp = ApplicationManager.get(data.newFocusedIndex);
141 var newFocusedApp = ApplicationManager.get(data.newFocusedIndex);
@@ -165,7 +161,6 @@ Item {
165 mouseClick(app2, units.gu(1), units.gu(1));
161 mouseClick(app2, units.gu(1), units.gu(1));
166 }
162 }
167
163
168− tryCompare(phoneStage, "painting", false);
169 tryCompare(ApplicationManager, "focusedApplicationId", newFocusedApp.appId);
164 tryCompare(ApplicationManager, "focusedApplicationId", newFocusedApp.appId);
170 }
165 }
171
166
@@ -212,16 +207,6 @@ Item {
212 tryCompare(spreadView, "phase", 0);
207 tryCompare(spreadView, "phase", 0);
213 }
208 }
214
209
215− function test_animateAppStartup() {
216− compare(phoneStage.painting, false);
217− addApps(2);
218− tryCompare(phoneStage, "painting", true);
219− tryCompare(phoneStage, "painting", false);
220− addApps(1);
221− tryCompare(phoneStage, "painting", true);
222− tryCompare(phoneStage, "painting", false);
223− }
224−
225 function test_select_data() {
210 function test_select_data() {
226 return [
211 return [
227 { tag: "0", index: 0 },
212 { tag: "0", index: 0 },
@@ -240,41 +225,9 @@ Item {
240
225
241 phoneStage.select(selectedApp.appId);
226 phoneStage.select(selectedApp.appId);
242
227
243− tryCompare(phoneStage, "painting", false);
228+ tryCompare(spreadView, "contentX", -spreadView.shift);
244− compare(ApplicationManager.focusedApplicationId, selectedApp.appId);
245− }
246
229
247− function test_fullscreenMode() {
230+ compare(ApplicationManager.focusedApplicationId, selectedApp.appId);
248− var fullscreenApp = null;
249− var normalApp = null;
250−
251− for (var i = 0; i < 5; i++) {
252− addApps(1);
253− var newApp = ApplicationManager.get(0);
254− tryCompare(phoneStage, "fullscreen", newApp.fullscreen);
255− if (newApp.fullscreen && fullscreenApp == null) {
256− fullscreenApp = newApp;
257− } else if (!newApp.fullscreen && normalApp == null){
258− normalApp = newApp;
259− }
260− }
261− verify(fullscreenApp != null); // Can't continue the test without having a fullscreen app
262− verify(normalApp != null); // Can't continue the test without having a non-fullscreen app
263−
264− // Select a normal app
265− goToSpread();
266− phoneStage.select(normalApp.appId);
267− tryCompare(phoneStage, "fullscreen", false);
268−
269− // Select a fullscreen app
270− goToSpread();
271− phoneStage.select(fullscreenApp.appId);
272− tryCompare(phoneStage, "fullscreen", true);
273−
274− // Select a normal app
275− goToSpread();
276− phoneStage.select(normalApp.appId);
277− tryCompare(phoneStage, "fullscreen", false);
278 }
231 }
279
232
280 function cleanup() {
233 function cleanup() {
tests/qmltests/tst_Shell.qml+109−12
@@ -236,6 +236,7 @@ Item {
236
236
237 // wait until the animation has finished
237 // wait until the animation has finished
238 tryCompare(greeter, "showProgress", 0);
238 tryCompare(greeter, "showProgress", 0);
239+ waitForRendering(greeter);
239 }
240 }
240
241
241 /*
242 /*
@@ -312,9 +313,9 @@ Item {
312 - perform long left edge swipe to go minimize the app and go back to the dash.
313 - perform long left edge swipe to go minimize the app and go back to the dash.
313
314
314 Expected Results
315 Expected Results
315− - apps lens shown and Running Apps visible on screen
316+ - apps lens shown
316 */
317 */
317− function test_minimizingAppTakesToRunningApps() {
318+ function test_minimizingAppTakesToDashApps() {
318 var dashApps = findChild(shell, "clickscope");
319 var dashApps = findChild(shell, "clickscope");
319 swipeUntilScopeViewIsReached(dashApps);
320 swipeUntilScopeViewIsReached(dashApps);
320
321
@@ -352,10 +353,58 @@ Item {
352 tryCompare(appsCategoryListView, "moving", false);
353 tryCompare(appsCategoryListView, "moving", false);
353
354
354 verify(itemIsOnScreen(dashApps));
355 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);
355
404
356− var runningApplicationsGrid = findChild(appsCategoryListView, "running.apps.category");
405+ dragToCloseIndicatorsPanel();
357− verify(runningApplicationsGrid);
406+
358− verify(itemIsOnScreen(runningApplicationsGrid));
407+ tryCompare(app.surface, "focus", true);
359 }
408 }
360
409
361 // Wait for the whole UI to settle down
410 // Wait for the whole UI to settle down
@@ -365,6 +414,18 @@ Item {
365 tryCompare(dashContentList, "moving", false);
414 tryCompare(dashContentList, "moving", false);
366 }
415 }
367
416
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+
368 function dragLauncherIntoView() {
429 function dragLauncherIntoView() {
369 var launcherPanel = findChild(shell, "launcherPanel");
430 var launcherPanel = findChild(shell, "launcherPanel");
370 verify(launcherPanel.x = - launcherPanel.width);
431 verify(launcherPanel.x = - launcherPanel.width);
@@ -489,7 +550,7 @@ Item {
489 {tag: "under greeter", greeter: true, app: false, launcher: false, indicators: false, expectedShown: false},
550 {tag: "under greeter", greeter: true, app: false, launcher: false, indicators: false, expectedShown: false},
490 {tag: "under app", greeter: false, app: true, launcher: false, indicators: false, expectedShown: false},
551 {tag: "under app", greeter: false, app: true, launcher: false, indicators: false, expectedShown: false},
491 {tag: "under launcher", greeter: false, app: false, launcher: true, indicators: false, expectedShown: true},
552 {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},
493 ]
554 ]
494 }
555 }
495
556
@@ -519,16 +580,39 @@ Item {
519 }
580 }
520
581
521 function test_focusRequestedHidesGreeter() {
582 function test_focusRequestedHidesGreeter() {
522− var greeter = findChild(shell, "greeter")
583+ var greeter = findChild(shell, "greeter");
523
584
524− greeter.show()
585+ var app = ApplicationManager.startApplication("dialer-app");
525− tryCompare(greeter, "showProgress", 1)
586+ // wait until the app is fully loaded (ie, real surface replaces splash screen)
587+ tryCompareFunction(function() { return app.surface != null }, true);
526
588
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
527− ApplicationManager.focusRequested("notes-app")
599+ ApplicationManager.requestFocusApplication("dialer-app");
528− tryCompare(greeter, "showProgress", 0)
600+ tryCompare(greeter, "showProgress", 0);
529− waitUntilApplicationWindowIsFullyVisible()
601+ waitForRendering(greeter);
530 }
602 }
531
603
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+
532 function test_showGreeterDBusCall() {
616 function test_showGreeterDBusCall() {
533 var greeter = findChild(shell, "greeter")
617 var greeter = findChild(shell, "greeter")
534 tryCompare(greeter, "showProgress", 0)
618 tryCompare(greeter, "showProgress", 0)
@@ -547,5 +631,18 @@ Item {
547 swipeAwayGreeter()
631 swipeAwayGreeter()
548 tryCompare(sessionSpy, "count", 1)
632 tryCompare(sessionSpy, "count", 1)
549 }
633 }
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+ }
550 }
647 }
551}
648}