on xenial help sign in
Merged with trunk.
commit265c37d780e48480266d893909cd44ca21e0d187 authorLeo Arias <leo.arias@canonical.com> dateThu 31 Jul 2014, 15:02 (12 years ago) parent9307e2ec3a03b5
54 files changed+1209−483
cmake/modules/QmlTest.cmake+1−1 debian/changelog+45 debian/control+2−1 plugins/AccountsService/AccountsService.cpp+21−1 plugins/AccountsService/AccountsService.h+13 plugins/AccountsService/plugin.cpp+1 plugins/Dash/CardAttributes.qml+65 plugins/Dash/CardCreator.js+76−7 plugins/Dash/qmldir+1 plugins/LightDM/plugin.cpp+2 po/da.po+4−4 po/hu.po+4−4 po/ru.po+12−12 po/ug.po+1−1 po/uk.po+1−1 po/zh_CN.po+1−1 qml/Components/PageHeader.qml+24−7 qml/Components/PassphraseLockscreen.qml+3 qml/Dash/CardTool.qml+27−2 qml/Launcher/LauncherDelegate.qml+9−6 qml/Launcher/graphics/notification@30.png qml/Launcher/graphics/notification@30.sci−5 qml/Panel/Indicators/DefaultIndicatorPage.qml+1−1 qml/Shell.qml+27−21 run.sh+15−6 tests/autopilot/unity8/shell/tests/test_upstart.py+1−1 tests/mocks/AccountsService/AccountsService.cpp+12−2 tests/mocks/AccountsService/AccountsService.h+12 tests/mocks/AccountsService/CMakeLists.txt+4−1 tests/mocks/AccountsService/plugin.cpp+1 tests/mocks/LightDM/GreeterPrivate.h+2 tests/mocks/LightDM/demo/CMakeLists.txt+4−2 tests/mocks/LightDM/demo/GreeterPrivate.cpp+202−30 tests/mocks/LightDM/demo/UsersModelPrivate.cpp+1−1 tests/mocks/LightDM/full/GreeterPrivate.cpp+1−1 tests/mocks/LightDM/single-pin/GreeterPrivate.cpp+1−1 tests/mocks/LightDM/single-pin/UsersModelPrivate.cpp+1−1 tests/mocks/Unity/CMakeLists.txt+1−1 tests/mocks/Unity/fake_scopes.cpp+5 tests/mocks/Unity/fake_scopes.h+1 tests/plugins/Dash/CMakeLists.txt+1 tests/plugins/Dash/cardcreator/1.res+63−61 tests/plugins/Dash/cardcreator/2.res+94−91 tests/plugins/Dash/cardcreator/3.res+77−74 tests/plugins/Dash/cardcreator/5.res+119−117 tests/plugins/Dash/cardcreator/7.res+128 tests/plugins/Dash/cardcreator/7.tst+3 tests/plugins/Dash/cardcreatortest.cpp+9−1 tests/plugins/Dash/listviewwithpageheadersectiontest.cpp−1 tests/plugins/Dash/tst_CardAttributes.qml+54 tests/qmltests/Components/tst_PageHeader.qml+36−6 tests/qmltests/Dash/CardHelpers.js+5−3 tests/qmltests/Dash/tst_Card.qml+4−2 tests/qmltests/Greeter/tst_Lockscreen.qml+11−5
cmake/modules/QmlTest.cmake+1−1
@@ -135,7 +135,7 @@ macro(add_binary_qml_test CLASS_NAME LD_PATH DEPS)
135135 set(testCommand
136136 LD_LIBRARY_PATH=${LD_PATH}
137137 ${CMAKE_CURRENT_BINARY_DIR}/${CLASS_NAME}TestExec
138− -o ${CMAKE_BINARY_DIR}/${CLASSNAME}Test.xml,xunitxml
138+ -o ${CMAKE_BINARY_DIR}/${CLASS_NAME}Test.xml,xunitxml
139139 -o -,txt)
140140
141141 add_qmltest_target(test${CLASS_NAME} "${testCommand}" FALSE TRUE)
debian/changelog+45
@@ -1,3 +1,48 @@
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+
146unity8 (8.00+14.10.20140729-0ubuntu1) utopic; urgency=medium
247
348 [ Gerry Boland ]
debian/control+2−1
@@ -19,11 +19,12 @@ Build-Depends: cmake,
1919 libgsettings-qt-dev,
2020 libhardware-dev,
2121 libhud-client2-dev,
22+ libpam0g-dev,
2223 libpay2-dev,
2324 libpulse-dev,
2425 libqmenumodel-dev (>= 0.2.8),
2526 libqt5xmlpatterns5-dev,
26− libunity-api-dev (>= 7.85),
27+ libunity-api-dev (>= 7.87),
2728 libusermetricsoutput1-dev,
2829 libxcb1-dev,
2930 pkg-config,
plugins/AccountsService/AccountsService.cpp+21−1
@@ -26,7 +26,8 @@ AccountsService::AccountsService(QObject* parent)
2626 m_service(new AccountsServiceDBusAdaptor(this)),
2727 m_user(qgetenv("USER")),
2828 m_demoEdges(false),
29− m_statsWelcomeScreen(false)
29+ m_statsWelcomeScreen(false),
30+ m_passwordDisplayHint(Keyboard)
3031{
3132 connect(m_service, SIGNAL(propertiesChanged(const QString &, const QString &, const QStringList &)),
3233 this, SLOT(propertiesChanged(const QString &, const QString &, const QStringList &)));
@@ -47,6 +48,7 @@ void AccountsService::setUser(const QString &user)
4748 updateDemoEdges();
4849 updateBackgroundFile();
4950 updateStatsWelcomeScreen();
51+ updatePasswordDisplayHint();
5052}
5153
5254bool AccountsService::demoEdges() const
@@ -70,6 +72,11 @@ bool AccountsService::statsWelcomeScreen() const
7072 return m_statsWelcomeScreen;
7173}
7274
75+AccountsService::PasswordDisplayHint AccountsService::passwordDisplayHint() const
76+{
77+ return m_passwordDisplayHint;
78+}
79+
7380void AccountsService::updateDemoEdges()
7481{
7582 auto demoEdges = m_service->getUserProperty(m_user, "com.canonical.unity.AccountsService", "demo-edges").toBool();
@@ -97,6 +104,15 @@ void AccountsService::updateStatsWelcomeScreen()
97104 }
98105}
99106
107+void AccountsService::updatePasswordDisplayHint()
108+{
109+ PasswordDisplayHint passwordDisplayHint = (PasswordDisplayHint)m_service->getUserProperty(m_user, "com.ubuntu.AccountsService.SecurityPrivacy", "PasswordDisplayHint").toInt();
110+ if (m_passwordDisplayHint != passwordDisplayHint) {
111+ m_passwordDisplayHint = passwordDisplayHint;
112+ Q_EMIT passwordDisplayHintChanged();
113+ }
114+}
115+
100116void AccountsService::propertiesChanged(const QString &user, const QString &interface, const QStringList &changed)
101117{
102118 if (m_user != user) {
@@ -111,6 +127,10 @@ void AccountsService::propertiesChanged(const QString &user, const QString &inte
111127 if (changed.contains("StatsWelcomeScreen")) {
112128 updateStatsWelcomeScreen();
113129 }
130+ } else if (interface == "com.ubuntu.AccountsService.SecurityPrivacy") {
131+ if (changed.contains("PasswordDisplayHint")) {
132+ updatePasswordDisplayHint();
133+ }
114134 }
115135}
116136
plugins/AccountsService/AccountsService.h+13
@@ -27,6 +27,7 @@ class AccountsServiceDBusAdaptor;
2727class AccountsService: public QObject
2828{
2929 Q_OBJECT
30+ Q_ENUMS(PasswordDisplayHint)
3031 Q_PROPERTY (QString user
3132 READ user
3233 WRITE setUser
@@ -41,8 +42,16 @@ class AccountsService: public QObject
4142 Q_PROPERTY (bool statsWelcomeScreen
4243 READ statsWelcomeScreen
4344 NOTIFY statsWelcomeScreenChanged)
45+ Q_PROPERTY (PasswordDisplayHint passwordDisplayHint
46+ READ passwordDisplayHint
47+ NOTIFY passwordDisplayHintChanged)
4448
4549public:
50+ enum PasswordDisplayHint {
51+ Keyboard,
52+ Numeric,
53+ };
54+
4655 explicit AccountsService(QObject *parent = 0);
4756
4857 QString user() const;
@@ -51,12 +60,14 @@ public:
5160 void setDemoEdges(bool demoEdges);
5261 QString backgroundFile() const;
5362 bool statsWelcomeScreen() const;
63+ PasswordDisplayHint passwordDisplayHint() const;
5464
5565Q_SIGNALS:
5666 void userChanged();
5767 void demoEdgesChanged();
5868 void backgroundFileChanged();
5969 void statsWelcomeScreenChanged();
70+ void passwordDisplayHintChanged();
6071
6172private Q_SLOTS:
6273 void propertiesChanged(const QString &user, const QString &interface, const QStringList &changed);
@@ -66,12 +77,14 @@ private:
6677 void updateDemoEdges();
6778 void updateBackgroundFile();
6879 void updateStatsWelcomeScreen();
80+ void updatePasswordDisplayHint();
6981
7082 AccountsServiceDBusAdaptor *m_service;
7183 QString m_user;
7284 bool m_demoEdges;
7385 QString m_backgroundFile;
7486 bool m_statsWelcomeScreen;
87+ PasswordDisplayHint m_passwordDisplayHint;
7588};
7689
7790#endif
plugins/AccountsService/plugin.cpp+1
@@ -33,5 +33,6 @@ void AccountsServicePlugin::registerTypes(const char *uri)
3333{
3434 Q_ASSERT(uri == QLatin1String("AccountsService"));
3535 qDBusRegisterMetaType<QList<QVariantMap>>();
36+ qRegisterMetaType<AccountsService::PasswordDisplayHint>("AccountsService::PasswordDisplayHint");
3637 qmlRegisterSingletonType<AccountsService>(uri, 0, 1, "AccountsService", service_provider);
3738}
plugins/Dash/CardAttributes.qmlnew file+65
@@ -0,0 +1,65 @@
1+/*
2+ * Copyright 2014 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.2
18+import QtQuick.Layouts 1.1
19+import Ubuntu.Components 1.1
20+import Ubuntu.Settings.Components 0.1
21+
22+GridLayout {
23+ id: grid
24+ anchors {
25+ left: parent.left;
26+ right: parent.right;
27+ }
28+ columns: 2 + repeater.count % 2
29+ property alias model: repeater.model
30+ property var color
31+
32+ Repeater {
33+ id: repeater
34+ delegate: Row {
35+ spacing: units.gu(0.5)
36+ readonly property int column: index % grid.columns;
37+ Layout.alignment: {
38+ if (column == 0) return Qt.AlignLeft;
39+ if (column == grid.columns - 1 || index == repeater.count - 1) return Qt.AlignRight;
40+ if (column == 1) return Qt.AlignHCenter;
41+ }
42+ Layout.columnSpan: index == repeater.count - 1 && grid.columns == 3 && column == 1 ? 2 : 1
43+ Layout.maximumWidth: Math.max(icon.width, label.x + label.implicitWidth)
44+ Layout.fillWidth: true
45+ StatusIcon {
46+ id: icon
47+ height: units.gu(2)
48+ sets: ["actions", "status", "apps"]
49+ source: "icon" in modelData ? modelData["icon"] : ""
50+ }
51+ Label {
52+ id: label
53+ width: parent.width - x
54+ anchors.verticalCenter: parent.verticalCenter
55+ text: "value" in modelData ? modelData["value"] : "";
56+ elide: Text.ElideRight
57+ maximumLineCount: 1
58+ font.weight: "style" in modelData && modelData["style"] == "highlighted" ? Font.DemiBold : Font.Light
59+ fontSize: "small"
60+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale)
61+ color: grid.color
62+ }
63+ }
64+ }
65+}
plugins/Dash/CardCreator.js+76−7
@@ -193,6 +193,21 @@ var kHeaderColumnCode = 'Column { \n\
193193 ] \n\
194194 }\n';
195195
196+// multiple column version of kHeaderColumnCode.
197+function kHeaderColumnCodeGenerator() {
198+ var headerColumnCodeTemplate = 'Column { \n\
199+ anchors.verticalCenter: parent.verticalCenter; \n\
200+ spacing: units.dp(2); \n\
201+ width: parent.width - x;\n\
202+ data: [ \n\
203+ %1 \n\
204+ ]\n\
205+ }\n';
206+ var args = Array.prototype.slice.call(arguments);
207+ var code = headerColumnCodeTemplate.arg(args.join(',\n'));
208+ return code;
209+}
210+
196211// %1 is used as anchors of mascotShapeLoader
197212var kMascotShapeLoaderCode = 'Loader { \n\
198213 id: mascotShapeLoader; \n\
@@ -224,7 +239,7 @@ var kMascotImageCode = 'Image { \n\
224239 }\n';
225240
226241// %1 is used as anchors of titleLabel
227−// %1 is used as color of titleLabel
242+// %2 is used as color of titleLabel
228243// %3 is used as extra condition for visible of titleLabel
229244var kTitleLabelCode = 'Label { \n\
230245 id: titleLabel; \n\
@@ -258,6 +273,15 @@ var kSubtitleLabelCode = 'Label { \n\
258273 horizontalAlignment: root.headerAlignment; \n\
259274 }\n';
260275
276+// %1 is used as anchors of attributesRow
277+var kAttributesRowCode = 'CardAttributes { \n\
278+ id: attributesRow; \n\
279+ objectName: "attributesRow"; \n\
280+ anchors { %1 } \n\
281+ color: %2; \n\
282+ model: cardData && cardData["attributes"] || undefined; \n\
283+ }\n';
284+
261285// %1 is used as top anchor of summary
262286// %2 is used as topMargin anchor of summary
263287// %3 is used as color of summary
@@ -308,6 +332,7 @@ function cardString(template, components) {
308332 var headerAsOverlay = hasArt && template && template["overlay"] === true && (hasTitle || hasMascot);
309333 var hasSubtitle = hasTitle && components["subtitle"] || false;
310334 var hasHeaderRow = hasMascot && hasTitle;
335+ var hasAttributes = hasTitle && components["attributes"] || false;
311336
312337 if (hasBackground) {
313338 code += kBackgroundLoaderCode;
@@ -377,6 +402,14 @@ function cardString(template, components) {
377402 code += 'readonly property int headerHeight: row.height;\n'
378403 } else if (hasMascot) {
379404 code += 'readonly property int headerHeight: mascotImage.height;\n'
405+ } else if (hasAttributes) {
406+ if (hasTitle && hasSubtitle) {
407+ code += 'readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin + attributesRow.height + attributesRow.anchors.topMargin;\n'
408+ } else if (hasTitle) {
409+ code += 'readonly property int headerHeight: titleLabel.height + attributesRow.height + attributesRow.anchors.topMargin;\n'
410+ } else {
411+ code += 'readonly property int headerHeight: attributesRow.height;\n'
412+ }
380413 } else if (hasSubtitle) {
381414 code += 'readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;\n'
382415 } else if (hasTitle) {
@@ -425,10 +458,12 @@ function cardString(template, components) {
425458
426459 var titleAnchors;
427460 var subtitleAnchors;
428− if (hasMascot && hasSubtitle) {
461+ var attributesAnchors;
462+ if (hasMascot && (hasSubtitle || hasAttributes)) {
429463 // Using row + column
430464 titleAnchors = 'left: parent.left; right: parent.right';
431465 subtitleAnchors = titleAnchors;
466+ attributesAnchors = subtitleAnchors;
432467 } else if (hasMascot) {
433468 // Using row + label
434469 titleAnchors = 'verticalCenter: parent.verticalCenter;\n'
@@ -438,11 +473,13 @@ function cardString(template, components) {
438473 titleAnchors = 'left: parent.left; \n\
439474 leftMargin: units.gu(1); \n\
440475 right: parent.right; \n\
476+ rightMargin: units.gu(1); \n\
441477 top: overlayLoader.top; \n\
442478 topMargin: units.gu(1);\n';
443479 } else {
444480 // Using anchors to the mascot/parent
445− titleAnchors = "right: parent.right;";
481+ titleAnchors = "right: parent.right;\n";
482+ titleAnchors += "rightMargin: units.gu(1);\n";
446483 titleAnchors += headerLeftAnchor;
447484 titleAnchors += headerVerticalAnchors;
448485 if (!headerLeftAnchorHasMargin) {
@@ -452,8 +489,19 @@ function cardString(template, components) {
452489 subtitleAnchors = 'left: titleLabel.left; \n\
453490 leftMargin: titleLabel.leftMargin; \n\
454491 right: titleLabel.right; \n\
492+ rightMargin: titleLabel.rightMargin; \n\
455493 top: titleLabel.bottom; \n\
456494 topMargin: units.dp(2);\n';
495+ if (hasSubtitle) {
496+ attributesAnchors = 'left: subtitleLabel.left; \n\
497+ leftMargin: subtitleLabel.leftMargin; \n\
498+ right: subtitleLabel.right; \n\
499+ rightMargin: subtitleLabel.rightMargin; \n\
500+ top: subtitleLabel.bottom; \n\
501+ topMargin: units.dp(2);\n';
502+ } else {
503+ attributesAnchors = subtitleAnchors;
504+ }
457505 }
458506
459507 var titleLabelVisibleExtra = (headerAsOverlay ? '&& overlayLoader.active': '');
@@ -463,11 +511,27 @@ function cardString(template, components) {
463511 subtitleCode += kSubtitleLabelCode.arg(subtitleAnchors).arg(color);
464512 }
465513
466− if (hasMascot && hasSubtitle) {
514+ if (hasMascot && (hasSubtitle || hasAttributes)) {
467515 // If using row + column wrap the code in the column
468516 titleSubtitleCode = kHeaderColumnCode.arg(titleCode).arg(subtitleCode);
517+ if (hasSubtitle && hasAttributes) {
518+ var attributesCode = kAttributesRowCode.arg(attributesAnchors).arg(color);
519+ titleSubtitleCode = kHeaderColumnCodeGenerator(titleCode, subtitleCode, attributesCode);
520+ } else if (hasSubtitle) {
521+ titleSubtitleCode = kHeaderColumnCode.arg(titleCode).arg(subtitleCode);
522+ } else if (hasAttributes) {
523+ var attributesCode = kAttributesRowCode.arg(attributesAnchors).arg(color);
524+ titleSubtitleCode = kHeaderColumnCode.arg(titleCode).arg(attributesCode);
525+ }
469526 } else {
470− titleSubtitleCode = titleCode + subtitleCode;
527+ titleSubtitleCode = titleCode;
528+ if (hasSubtitle) {
529+ titleSubtitleCode = titleSubtitleCode + subtitleCode;
530+ }
531+ if (hasAttributes) {
532+ var attributesCode = kAttributesRowCode.arg(attributesAnchors).arg(color);
533+ titleSubtitleCode = titleSubtitleCode + attributesCode;
534+ }
471535 }
472536 }
473537
@@ -487,6 +551,7 @@ function cardString(template, components) {
487551 else if (headerAsOverlay && hasArt) summaryTopAnchor = "artShapeHolder.bottom";
488552 else if (hasHeaderRow) summaryTopAnchor = "row.bottom";
489553 else if (hasMascot) summaryTopAnchor = "mascotImage.bottom";
554+ else if (hasAttributes) summaryTopAnchor = "attributesRow.bottom";
490555 else if (hasSubtitle) summaryTopAnchor = "subtitleLabel.bottom";
491556 else if (hasTitle) summaryTopAnchor = "titleLabel.bottom";
492557 else if (hasArt) summaryTopAnchor = "artShapeHolder.bottom";
@@ -499,7 +564,7 @@ function cardString(template, components) {
499564 color = 'root.scopeStyle ? root.scopeStyle.foreground : "grey"';
500565 }
501566
502− var summaryTopMargin = (hasMascot || hasSubtitle ? 'anchors.margins' : '0');
567+ var summaryTopMargin = (hasMascot || hasSubtitle || hasAttributes ? 'anchors.margins' : '0');
503568
504569 code += kSummaryLabelCode.arg(summaryTopAnchor).arg(summaryTopMargin).arg(color);
505570 }
@@ -510,6 +575,8 @@ function cardString(template, components) {
510575 code += 'implicitHeight: row.y + row.height + units.gu(1);\n';
511576 } else if (hasMascot) {
512577 code += 'implicitHeight: mascotImage.y + mascotImage.height;\n';
578+ } else if (hasAttributes) {
579+ code += 'implicitHeight: attributesRow.y + attributesRow.height + units.gu(1);\n';
513580 } else if (hasSubtitle) {
514581 code += 'implicitHeight: subtitleLabel.y + subtitleLabel.height + units.gu(1);\n';
515582 } else if (hasTitle) {
@@ -526,8 +593,10 @@ function cardString(template, components) {
526593function createCardComponent(parent, template, components) {
527594 var imports = 'import QtQuick 2.2; \n\
528595 import Ubuntu.Components 0.1; \n\
529− import Ubuntu.Thumbnailer 0.1;\n';
596+ import Ubuntu.Thumbnailer 0.1;\n\
597+ import Dash 0.1;\n';
530598 var card = cardString(template, components);
531599 var code = imports + 'Component {\n' + card + '}\n';
600+
532601 return Qt.createQmlObject(code, parent, "createCardComponent");
533602}
plugins/Dash/qmldir+1
@@ -3,3 +3,4 @@ plugin Dash-qml
33typeinfo Dash.qmltypes
44singleton CardCreatorCache 0.1 CardCreatorCache.qml
55ScopeStyle 0.1 ScopeStyle.qml
6+CardAttributes 0.1 CardAttributes.qml
plugins/LightDM/plugin.cpp+2
@@ -69,6 +69,8 @@ void LightDMPlugin::registerTypes(const char *uri)
6969 qmlRegisterType<UserMetricsOutput::ColorTheme>();
7070
7171 Q_ASSERT(uri == QLatin1String("LightDM"));
72+ qRegisterMetaType<QLightDM::Greeter::MessageType>("QLightDM::Greeter::MessageType");
73+ qRegisterMetaType<QLightDM::Greeter::PromptType>("QLightDM::Greeter::PromptType");
7274 qmlRegisterSingletonType<Greeter>(uri, 0, 1, "Greeter", greeter_provider);
7375 qmlRegisterSingletonType<UsersModel>(uri, 0, 1, "Users", users_provider);
7476 qmlRegisterUncreatableType<QLightDM::UsersModel>(uri, 0, 1, "UserRoles", "Type is not instantiable");
po/da.po+4−4
@@ -9,13 +9,13 @@ msgstr ""
99"Project-Id-Version: unity8\n"
1010"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1111"POT-Creation-Date: 2014-07-11 14:59+0200\n"
12−"PO-Revision-Date: 2014-07-29 23:18+0000\n"
12+"PO-Revision-Date: 2014-07-30 12:37+0000\n"
1313"Last-Translator: Ask Hjorth Larsen <asklarsen@gmail.com>\n"
1414"Language-Team: Danish <dansk@dansk-gruppen.dk>\n"
1515"MIME-Version: 1.0\n"
1616"Content-Type: text/plain; charset=UTF-8\n"
1717"Content-Transfer-Encoding: 8bit\n"
18−"X-Launchpad-Export-Date: 2014-07-30 07:08+0000\n"
18+"X-Launchpad-Export-Date: 2014-07-31 07:15+0000\n"
1919"X-Generator: Launchpad (build 17131)\n"
2020
2121#: plugins/Unity/Launcher/launcheritem.cpp:43
@@ -114,7 +114,7 @@ msgstr "Seneste"
114114
115115#: qml/Dash/GenericScopeView.qml:330
116116msgid "See all"
117−msgstr ""
117+msgstr "Se alle"
118118
119119#: qml/Dash/Previews/PreviewActionCombo.qml:34
120120msgid "More..."
@@ -170,7 +170,7 @@ msgstr "Indtast venligst PIN for SIM"
170170
171171#: qml/Panel/ActiveCallHint.qml:77
172172msgid "Tap to return to call..."
173−msgstr ""
173+msgstr "Tryk for at besvare opkaldet..."
174174
175175#: qml/Panel/ActiveCallHint.qml:90
176176msgid "Conference"
po/hu.po+4−4
@@ -8,13 +8,13 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2014-07-11 14:59+0200\n"
11−"PO-Revision-Date: 2014-07-27 15:04+0000\n"
11+"PO-Revision-Date: 2014-07-30 18:42+0000\n"
1212"Last-Translator: Richard Somlói <ricsipontaz@gmail.com>\n"
1313"Language-Team: Hungarian <hu@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-07-28 06:44+0000\n"
17+"X-Launchpad-Export-Date: 2014-07-31 07:15+0000\n"
1818"X-Generator: Launchpad (build 17131)\n"
1919
2020#: plugins/Unity/Launcher/launcheritem.cpp:43
@@ -82,7 +82,7 @@ msgstr "Vészhívás"
8282
8383#: qml/Components/Lockscreen.qml:231
8484msgid "OK"
85−msgstr "OK"
85+msgstr "Oké"
8686
8787#: qml/Components/PassphraseLockscreen.qml:60
8888#, qt-format
@@ -127,7 +127,7 @@ msgstr "Kevesebb…"
127127
128128#: qml/Dash/Previews/PreviewRatingInput.qml:81
129129msgid "Rate this"
130−msgstr "Osztályzás"
130+msgstr "Értékelje"
131131
132132#: qml/Dash/Previews/PreviewRatingInput.qml:126
133133msgid "Add a review"
po/ru.po+12−12
@@ -8,13 +8,13 @@ msgstr ""
88"Project-Id-Version: unity\n"
99"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
1010"POT-Creation-Date: 2014-07-11 14:59+0200\n"
11−"PO-Revision-Date: 2014-05-15 12:57+0000\n"
12−"Last-Translator: Zonov Roman <Unknown>\n"
11+"PO-Revision-Date: 2014-07-30 12:49+0000\n"
12+"Last-Translator: Jay ZDLin <Unknown>\n"
1313"Language-Team: Russian <ru@li.org>\n"
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-07-26 07:17+0000\n"
17+"X-Launchpad-Export-Date: 2014-07-31 07:15+0000\n"
1818"X-Generator: Launchpad (build 17131)\n"
1919
2020#: plugins/Unity/Launcher/launcheritem.cpp:43
@@ -85,7 +85,7 @@ msgstr "Экстренный вызов"
8585
8686#: qml/Components/Lockscreen.qml:231
8787msgid "OK"
88−msgstr ""
88+msgstr "ОК"
8989
9090#: qml/Components/PassphraseLockscreen.qml:60
9191#, qt-format
@@ -98,11 +98,11 @@ msgstr "Привет"
9898
9999#: qml/Components/PinLockscreen.qml:198
100100msgid "CANCEL"
101−msgstr ""
101+msgstr "ОТМЕНИТЬ"
102102
103103#: qml/Components/PinLockscreen.qml:216
104104msgid "DONE"
105−msgstr ""
105+msgstr "ГОТОВО"
106106
107107#: qml/Components/SeeMore.qml:35
108108msgid "See more"
@@ -118,7 +118,7 @@ msgstr "Недавние"
118118
119119#: qml/Dash/GenericScopeView.qml:330
120120msgid "See all"
121−msgstr ""
121+msgstr "Показать всё"
122122
123123#: qml/Dash/Previews/PreviewActionCombo.qml:34
124124msgid "More..."
@@ -178,15 +178,15 @@ msgstr ""
178178
179179#: qml/Panel/ActiveCallHint.qml:90
180180msgid "Conference"
181−msgstr ""
181+msgstr "Конференция"
182182
183183#: qml/Panel/Indicators/MenuItemFactory.qml:583
184184msgid "In queue…"
185−msgstr ""
185+msgstr "В очереди..."
186186
187187#: qml/Panel/Indicators/MenuItemFactory.qml:587
188188msgid "Downloading"
189−msgstr ""
189+msgstr "Загрузка"
190190
191191#: qml/Panel/Indicators/MenuItemFactory.qml:589
192192msgid "Paused, tap to resume"
@@ -194,11 +194,11 @@ msgstr ""
194194
195195#: qml/Panel/Indicators/MenuItemFactory.qml:591
196196msgid "Canceled"
197−msgstr ""
197+msgstr "Отменено"
198198
199199#: qml/Panel/Indicators/MenuItemFactory.qml:593
200200msgid "Finished"
201−msgstr ""
201+msgstr "Завершено"
202202
203203#: qml/Panel/Indicators/MenuItemFactory.qml:595
204204msgid "Failed, tap to retry"
po/ug.po+1−1
@@ -14,7 +14,7 @@ msgstr ""
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-07-30 07:08+0000\n"
17+"X-Launchpad-Export-Date: 2014-07-31 07:15+0000\n"
1818"X-Generator: Launchpad (build 17131)\n"
1919
2020#: plugins/Unity/Launcher/launcheritem.cpp:43
po/uk.po+1−1
@@ -14,7 +14,7 @@ msgstr ""
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-07-30 07:08+0000\n"
17+"X-Launchpad-Export-Date: 2014-07-31 07:15+0000\n"
1818"X-Generator: Launchpad (build 17131)\n"
1919
2020#: plugins/Unity/Launcher/launcheritem.cpp:43
po/zh_CN.po+1−1
@@ -14,7 +14,7 @@ msgstr ""
1414"MIME-Version: 1.0\n"
1515"Content-Type: text/plain; charset=UTF-8\n"
1616"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-07-30 07:08+0000\n"
17+"X-Launchpad-Export-Date: 2014-07-31 07:15+0000\n"
1818"X-Generator: Launchpad (build 17131)\n"
1919
2020#: plugins/Unity/Launcher/launcheritem.cpp:43
qml/Components/PageHeader.qml+24−7
@@ -44,6 +44,12 @@ Item {
4444 signal backClicked()
4545
4646 onScopeStyleChanged: refreshLogo()
47+ onSearchQueryChanged: {
48+ // Make sure we are at the search page if the search query changes behind our feet
49+ if (searchQuery) {
50+ headerContainer.showSearch = true;
51+ }
52+ }
4753
4854 function triggerSearch() {
4955 if (searchEntryEnabled) {
@@ -52,6 +58,12 @@ Item {
5258 }
5359 }
5460
61+ function closePopup() {
62+ if (headerContainer.popover != null) {
63+ PopupUtils.close(headerContainer.popover);
64+ }
65+ }
66+
5567 function resetSearch(keepFocus) {
5668 if (searchHistory) {
5769 searchHistory.addQuery(searchTextField.text);
@@ -60,9 +72,7 @@ Item {
6072 unfocus();
6173 }
6274 searchTextField.text = "";
63− if (headerContainer.popover != null) {
64− PopupUtils.close(headerContainer.popover);
65− }
75+ closePopup();
6676 }
6777
6878 function unfocus() {
@@ -103,9 +113,7 @@ Item {
103113 anchors { fill: parent; margins: units.gu(1); bottomMargin: units.gu(3) + bottomContainer.height }
104114 visible: headerContainer.showSearch
105115 onPressed: {
106− if (headerContainer.popover) {
107− PopupUtils.close(headerContainer.popover);
108− }
116+ closePopup();
109117 if (!searchTextField.text) {
110118 headerContainer.showSearch = false;
111119 }
@@ -172,6 +180,7 @@ Item {
172180 property var contents: TextField {
173181 id: searchTextField
174182 objectName: "searchTextField"
183+ inputMethodHints: Qt.ImhNoPredictiveText
175184 hasClearButton: false
176185 anchors {
177186 fill: parent
@@ -221,6 +230,12 @@ Item {
221230 root.openSearchHistory();
222231 }
223232 }
233+
234+ onTextChanged: {
235+ if (text != "") {
236+ closePopup();
237+ }
238+ }
224239 }
225240 }
226241
@@ -298,6 +313,7 @@ Item {
298313
299314 Repeater {
300315 id: recentSearches
316+ objectName: "recentSearches"
301317 model: searchHistory
302318
303319 delegate: Standard {
@@ -306,7 +322,8 @@ Item {
306322 onClicked: {
307323 searchHistory.addQuery(text);
308324 searchTextField.text = text;
309− PopupUtils.close(popover);
325+ closePopup();
326+ unfocus();
310327 }
311328 }
312329 }
qml/Components/PassphraseLockscreen.qml+3
@@ -31,8 +31,10 @@ Item {
3131
3232 function clear(playAnimation) {
3333 pinentryField.text = "";
34+ pinentryField.enabled = true
3435 if (playAnimation) {
3536 wrongPasswordAnimation.start();
37+ pinentryField.forceActiveFocus();
3638 } else {
3739 pinentryField.focus = false
3840 }
@@ -78,6 +80,7 @@ Item {
7880
7981 onAccepted: {
8082 if (pinentryField.text) {
83+ pinentryField.enabled = false;
8184 root.entered(pinentryField.text);
8285 }
8386 }
qml/Dash/CardTool.qml+27−2
@@ -165,15 +165,40 @@ Item {
165165 }
166166 }
167167
168+ Item {
169+ id: attributesModel
170+ property int numOfAttributes: 0
171+ property var model: []
172+ property bool hasAttributes: {
173+ var attributes = components["attributes"];
174+ var hasAttributesFlag = (attributes != undefined) && attributes["field"];
175+
176+ if (hasAttributesFlag) {
177+ if (attributes["max-count"]) {
178+ numOfAttributes = attributes["max-count"];
179+ }
180+ }
181+ return hasAttributesFlag
182+ }
183+
184+ onNumOfAttributesChanged: {
185+ model = []
186+ for (var i = 0; i < numOfAttributes; i++) {
187+ model.push( {"value":"text"+(i+1), "icon":"image://theme/ok" } );
188+ }
189+ }
190+ }
191+
168192 Loader {
169193 id: cardLoader
170− property var fields: ["art", "mascot", "title", "subtitle", "summary"]
194+ property var fields: ["art", "mascot", "title", "subtitle", "summary", "attributes"]
171195 property var maxData: {
172196 "art": Qt.resolvedUrl("graphics/checkers.png"),
173197 "mascot": Qt.resolvedUrl("graphics/checkers.png"),
174198 "title": "—\n—",
175199 "subtitle": "—",
176− "summary": "—\n—\n—\n—\n—"
200+ "summary": "—\n—\n—\n—\n—",
201+ "attributes": attributesModel.model
177202 }
178203 sourceComponent: cardTool.cardComponent
179204 onLoaded: {
qml/Launcher/LauncherDelegate.qml+9−6
@@ -70,28 +70,31 @@ Item {
7070 height: root.itemHeight - units.gu(1)
7171 }
7272
73− BorderImage {
73+ UbuntuShape {
7474 objectName: "countEmblem"
7575 anchors {
7676 right: parent.right
7777 top: parent.top
7878 margins: units.dp(3)
7979 }
80− width: Math.min(root.itemWidth, Math.max(units.gu(3), countLabel.implicitWidth + units.gu(2.5)))
81− height: units.gu(3)
82− source: "graphics/notification.sci"
80+ width: Math.min(root.itemWidth, Math.max(units.gu(2), countLabel.implicitWidth + units.gu(1)))
81+ height: units.gu(2)
82+ color: UbuntuColors.orange
8383 visible: root.count > 0
84+ borderSource: "none"
8485
8586 Label {
8687 id: countLabel
8788 text: root.count
8889 anchors.centerIn: parent
90+ // FIXME: verticalCenter seems to be off wee bit and QML doesn't have a centerLine
91+ // property for Text: https://bugreports.qt-project.org/browse/QTBUG-40479
92+ anchors.verticalCenterOffset: -units.dp(.5)
8993 width: root.itemWidth - units.gu(1)
9094 horizontalAlignment: Text.AlignHCenter
9195 elide: Text.ElideRight
9296 color: "white"
93− fontSize: "small"
94− font.bold: true
97+ fontSize: "x-small"
9598 }
9699 }
97100
qml/Launcher/graphics/notification@30.pngdeleted

Binary file not shown.

qml/Launcher/graphics/notification@30.scideleted−5
@@ -1,5 +0,0 @@
1−border.left: 36
2−border.top: 36
3−border.bottom: 36
4−border.right: 36
5−source: notification@30.png
qml/Panel/Indicators/DefaultIndicatorPage.qml+1−1
@@ -126,7 +126,7 @@ IndicatorBase {
126126 id: loader
127127 objectName: "menuItem" + index
128128 asynchronous: false
129− visible: height > 0
129+ visible: status == Loader.Ready
130130
131131 property int modelIndex: index
132132
qml/Shell.qml+27−21
@@ -244,6 +244,7 @@ Item {
244244 width: parent.width
245245 height: parent.height - panel.panelHeight
246246 background: shell.background
247+ alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
247248 minPinLength: 4
248249 maxPinLength: 4
249250
@@ -258,7 +259,7 @@ Item {
258259 onShownChanged: if (shown) greeter.fakeActiveForApp = ""
259260
260261 Component.onCompleted: {
261− if (LightDM.Users.count == 1) {
262+ if (greeter.narrowMode) {
262263 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
263264 }
264265 }
@@ -270,14 +271,17 @@ Item {
270271 onShowGreeter: greeter.show()
271272
272273 onShowPrompt: {
273− if (LightDM.Users.count == 1) {
274− // TODO: There's no better way for now to determine if its a PIN or a passphrase.
275− if (text == "PIN") {
276− lockscreen.alphaNumeric = false
277− } else {
278− lockscreen.alphaNumeric = true
279− }
280− lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
274+ if (greeter.narrowMode) {
275+ lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text.toLowerCase());
276+ lockscreen.show();
277+ }
278+ }
279+
280+ onPromptlessChanged: {
281+ if (LightDM.Greeter.promptless) {
282+ lockscreen.hide()
283+ } else {
284+ lockscreen.reset();
281285 lockscreen.show();
282286 }
283287 }
@@ -291,6 +295,9 @@ Item {
291295 greeter.login();
292296 } else {
293297 lockscreen.clear(true);
298+ if (greeter.narrowMode) {
299+ LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
300+ }
294301 }
295302 }
296303 }
@@ -345,25 +352,24 @@ Item {
345352
346353 function login() {
347354 enabled = false;
348− LightDM.Greeter.startSessionSync();
349− sessionStarted();
350− greeter.hide();
351− lockscreen.hide();
352− launcher.hide();
355+ if (LightDM.Greeter.startSessionSync()) {
356+ sessionStarted();
357+ greeter.hide();
358+ lockscreen.hide();
359+ launcher.hide();
360+ }
353361 enabled = true;
354362 }
355363
356364 onShownChanged: {
357365 if (shown) {
358− lockscreen.reset();
359− if (!LightDM.Greeter.promptless) {
360− lockscreen.show();
361− }
362− // If there is only one user, we start authenticating with that one here.
363− // If there are more users, the Greeter will handle that
364− if (LightDM.Users.count == 1) {
366+ if (greeter.narrowMode) {
365367 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
366368 }
369+ if (!LightDM.Greeter.promptless) {
370+ lockscreen.reset();
371+ lockscreen.show();
372+ }
367373 greeter.fakeActiveForApp = "";
368374 greeter.forceActiveFocus();
369375 }
run.sh+15−6
@@ -8,6 +8,7 @@ GDB=false
88FAKE=false
99PINLOCK=false
1010KEYLOCK=false
11+USE_MOCKS=false
1112MOUSE_TOUCH=true
1213
1314usage() {
@@ -31,9 +32,9 @@ eval set -- "$ARGS"
3132while [ $# -gt 0 ]
3233do
3334 case "$1" in
34− -f|--fake) FAKE=true;;
35− -p|--pinlock) PINLOCK=true;;
36− -k|--keylock) KEYLOCK=true;;
35+ -f|--fake) FAKE=true; USE_MOCKS=true;;
36+ -p|--pinlock) PINLOCK=true; USE_MOCKS=true;;
37+ -k|--keylock) KEYLOCK=true; USE_MOCKS=true;;
3738 -g|--gdb) GDB=true;;
3839 -h|--help) usage;;
3940 -m|--nomousetouch) MOUSE_TOUCH=false;;
@@ -43,20 +44,28 @@ do
4344done
4445
4546if $FAKE; then
46− export QML2_IMPORT_PATH=$QML2_IMPORT_PATH:$PWD/builddir/tests/mocks:$PWD/builddir/plugins:$PWD/builddir/modules
4747 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/builddir/tests/mocks/libusermetrics:$PWD/builddir/tests/mocks/LightDM/single
4848fi
4949
5050if $PINLOCK; then
51− export QML2_IMPORT_PATH=$QML2_IMPORT_PATH:$PWD/builddir/tests/mocks:$PWD/builddir/plugins:$PWD/builddir/modules
5251 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/builddir/tests/mocks/libusermetrics:$PWD/builddir/tests/mocks/LightDM/single-pin
5352fi
5453
5554if $KEYLOCK; then
56− export QML2_IMPORT_PATH=$QML2_IMPORT_PATH:$PWD/builddir/tests/mocks:$PWD/builddir/plugins:$PWD/builddir/modules
5755 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/builddir/tests/mocks/libusermetrics:$PWD/builddir/tests/mocks/LightDM/single-passphrase
5856fi
5957
58+if $USE_MOCKS; then
59+ rm -f $PWD/builddir/nonmirplugins/LightDM # undo symlink (from below) for cleanliness
60+ export QML2_IMPORT_PATH=$QML2_IMPORT_PATH:$PWD/builddir/tests/mocks:$PWD/builddir/plugins:$PWD/builddir/modules
61+else
62+ # Still fake no-login user for convenience (it's annoying to be prompted for your password when testing)
63+ # And in particular, just link our LightDM mock into the nonmirplugins folder. We don't want the rest of
64+ # our plugins to be used.
65+ ln -s $PWD/builddir/tests/mocks/LightDM $PWD/builddir/nonmirplugins/
66+ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/builddir/tests/mocks/LightDM/single
67+fi
68+
6069QML_PHONE_SHELL_ARGS=""
6170if $MOUSE_TOUCH; then
6271 QML_PHONE_SHELL_ARGS="$QML_PHONE_SHELL_ARGS -mousetouch"
tests/autopilot/unity8/shell/tests/test_upstart.py+1−1
@@ -113,7 +113,7 @@ class UpstartIntegrationTests(UnityTestCase):
113113 except MismatchError:
114114 pass
115115 else:
116− self.process.send_singal(signal.SIGCONT)
116+ self.process.send_signal(signal.SIGCONT)
117117 self.fail('Unity8 raised SIGSTOP')
118118
119119 self._set_proxy()
tests/mocks/AccountsService/AccountsService.cpp+12−2
@@ -29,12 +29,14 @@ AccountsService::AccountsService(QObject* parent)
2929
3030QString AccountsService::user() const
3131{
32− return "testuser";
32+ return m_user;
3333}
3434
3535void AccountsService::setUser(const QString &user)
3636{
37− Q_UNUSED(user)
37+ m_user = user;
38+ Q_EMIT userChanged();
39+ Q_EMIT passwordDisplayHintChanged();
3840}
3941
4042bool AccountsService::demoEdges() const
@@ -68,3 +70,11 @@ void AccountsService::setStatsWelcomeScreen(bool statsWelcomeScreen)
6870 m_statsWelcomeScreen = statsWelcomeScreen;
6971 statsWelcomeScreenChanged();
7072}
73+
74+AccountsService::PasswordDisplayHint AccountsService::passwordDisplayHint() const
75+{
76+ if (m_user == "has-pin")
77+ return PasswordDisplayHint::Numeric;
78+ else
79+ return PasswordDisplayHint::Keyboard;
80+}
tests/mocks/AccountsService/AccountsService.h+12
@@ -27,6 +27,7 @@
2727class AccountsService: public QObject
2828{
2929 Q_OBJECT
30+ Q_ENUMS(PasswordDisplayHint)
3031 Q_PROPERTY (QString user
3132 READ user
3233 WRITE setUser
@@ -43,8 +44,16 @@ class AccountsService: public QObject
4344 READ statsWelcomeScreen
4445 WRITE setStatsWelcomeScreen // only available in mock
4546 NOTIFY statsWelcomeScreenChanged)
47+ Q_PROPERTY (PasswordDisplayHint passwordDisplayHint
48+ READ passwordDisplayHint
49+ NOTIFY passwordDisplayHintChanged)
4650
4751public:
52+ enum PasswordDisplayHint {
53+ Keyboard,
54+ Numeric,
55+ };
56+
4857 explicit AccountsService(QObject *parent = 0);
4958
5059 QString user() const;
@@ -55,15 +64,18 @@ public:
5564 void setBackgroundFile(const QString &backgroundFile);
5665 bool statsWelcomeScreen() const;
5766 void setStatsWelcomeScreen(bool statsWelcomeScreen);
67+ PasswordDisplayHint passwordDisplayHint() const;
5868
5969Q_SIGNALS:
6070 void userChanged();
6171 void demoEdgesChanged();
6272 void backgroundFileChanged();
6373 void statsWelcomeScreenChanged();
74+ void passwordDisplayHintChanged();
6475
6576private:
6677 QString m_backgroundFile;
78+ QString m_user;
6779 bool m_statsWelcomeScreen;
6880};
6981
tests/mocks/AccountsService/CMakeLists.txt+4−1
@@ -5,4 +5,7 @@ add_library(MockAccountsService-qml MODULE
55
66qt5_use_modules(MockAccountsService-qml DBus Qml)
77
8−add_unity8_mock(AccountsService 0.1 AccountsService TARGETS MockAccountsService-qml)
8+add_unity8_mock(AccountsService 0.1 AccountsService
9+ PREFIX mocks
10+ TARGETS MockAccountsService-qml
11+ )
tests/mocks/AccountsService/plugin.cpp+1
@@ -32,5 +32,6 @@ static QObject *service_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
3232void AccountsServicePlugin::registerTypes(const char *uri)
3333{
3434 Q_ASSERT(uri == QLatin1String("AccountsService"));
35+ qRegisterMetaType<AccountsService::PasswordDisplayHint>("AccountsService::PasswordDisplayHint");
3536 qmlRegisterSingletonType<AccountsService>(uri, 0, 1, "AccountsService", service_provider);
3637}
tests/mocks/LightDM/GreeterPrivate.h+2
@@ -24,6 +24,7 @@
2424namespace QLightDM
2525{
2626class Greeter;
27+class GreeterImpl;
2728
2829class GreeterPrivate
2930{
@@ -40,6 +41,7 @@ public:
4041 void handleRespond(const QString &response);
4142
4243protected:
44+ GreeterImpl *m_impl; // if the backend needs more private data
4345 Greeter * const q_ptr;
4446
4547private:
tests/mocks/LightDM/demo/CMakeLists.txt+4−2
@@ -11,11 +11,13 @@ set(LibLightDM_SOURCES
1111add_library(MockLightDM-demo STATIC ${LibLightDM_SOURCES})
1212
1313include_directories(
14+ ${CMAKE_CURRENT_BINARY_DIR}
1415 ${LIBUSERMETRICSOUTPUT_INCLUDE_DIRS}
1516)
1617
1718target_link_libraries(MockLightDM-demo
1819 ${LIBUSERMETRICSOUTPUT_LDFLAGS}
19− )
20+ -lpam
21+)
2022
21−qt5_use_modules(MockLightDM-demo Gui)
23+qt5_use_modules(MockLightDM-demo Concurrent Gui)
tests/mocks/LightDM/demo/GreeterPrivate.cpp+202−30
@@ -18,53 +18,225 @@
1818
1919#include "../Greeter.h"
2020#include "../GreeterPrivate.h"
21−#include <QtCore/QDir>
22−#include <QtCore/QSettings>
21+#include <QFuture>
22+#include <QFutureInterface>
23+#include <QFutureWatcher>
24+#include <QQueue>
25+#include <QtConcurrent>
26+#include <QVector>
27+#include <security/pam_appl.h>
2328
2429namespace QLightDM
2530{
2631
32+class GreeterImpl : public QObject
33+{
34+ Q_OBJECT
35+
36+ typedef QFutureInterface<QString> ResponseFuture;
37+
38+public:
39+ explicit GreeterImpl(Greeter *parent, GreeterPrivate *greeterPrivate)
40+ : QObject(parent),
41+ greeter(parent),
42+ greeterPrivate(greeterPrivate),
43+ pamHandle(NULL)
44+ {
45+ qRegisterMetaType<QLightDM::GreeterImpl::ResponseFuture>("QLightDM::GreeterImpl::ResponseFuture");
46+
47+ connect(&futureWatcher, SIGNAL(finished()),
48+ this, SLOT(finishPam()));
49+ connect(this, SIGNAL(showMessage(QString, QLightDM::Greeter::MessageType)),
50+ greeter, SIGNAL(showMessage(QString, QLightDM::Greeter::MessageType)));
51+ // This next connect is how we pass ResponseFutures between threads
52+ connect(this, SIGNAL(showPrompt(QString, QLightDM::Greeter::PromptType, QLightDM::GreeterImpl::ResponseFuture)),
53+ this, SLOT(handlePrompt(QString, QLightDM::Greeter::PromptType, QLightDM::GreeterImpl::ResponseFuture)));
54+ }
55+
56+ void start(QString username)
57+ {
58+ // Clear out any existing PAM interactions first (we can't simply
59+ // cancel our QFuture because QtConcurrent::run doesn't support cancel)
60+ if (pamHandle != NULL) {
61+ pam_handle *handle = pamHandle;
62+ pamHandle = NULL; // to disable normal finishPam() handling
63+ while (respond(QString())); // clear our local queue of QFutures
64+ pam_end(handle, PAM_CONV_ERR);
65+ }
66+
67+ // Now actually start a new conversation with PAM
68+ pam_conv conversation;
69+ conversation.conv = converseWithPam;
70+ conversation.appdata_ptr = static_cast<void*>(this);
71+
72+ if (pam_start("lightdm", username.toUtf8(), &conversation, &pamHandle) == PAM_SUCCESS) {
73+ futureWatcher.setFuture(QtConcurrent::run(authenticateWithPam, pamHandle));
74+ } else {
75+ greeterPrivate->authenticated = false;
76+ Q_EMIT greeter->showMessage("Internal error: could not start PAM authentication", QLightDM::Greeter::MessageTypeError);
77+ Q_EMIT greeter->authenticationComplete();
78+ }
79+ }
80+
81+ static int authenticateWithPam(pam_handle* pamHandle)
82+ {
83+ int pamStatus = pam_authenticate(pamHandle, 0);
84+ if (pamStatus == PAM_SUCCESS) {
85+ pamStatus = pam_acct_mgmt(pamHandle, 0);
86+ }
87+ if (pamStatus == PAM_NEW_AUTHTOK_REQD) {
88+ pamStatus = pam_chauthtok(pamHandle, PAM_CHANGE_EXPIRED_AUTHTOK);
89+ }
90+ if (pamStatus == PAM_SUCCESS) {
91+ pam_setcred(pamHandle, PAM_REINITIALIZE_CRED);
92+ }
93+ return pamStatus;
94+ }
95+
96+ static int converseWithPam(int num_msg, const pam_message** msg,
97+ pam_response** resp, void* appdata_ptr)
98+ {
99+ if (num_msg <= 0)
100+ return PAM_CONV_ERR;
101+
102+ auto* tmp_response = static_cast<pam_response*>(calloc(num_msg, sizeof(pam_response)));
103+ if (!tmp_response)
104+ return PAM_CONV_ERR;
105+
106+ GreeterImpl* impl = static_cast<GreeterImpl*>(appdata_ptr);
107+
108+ int count;
109+ QVector<ResponseFuture> responses;
110+
111+ for (count = 0; count < num_msg; ++count)
112+ {
113+ switch (msg[count]->msg_style)
114+ {
115+ case PAM_PROMPT_ECHO_ON:
116+ {
117+ QString message(msg[count]->msg);
118+ responses.append(ResponseFuture());
119+ responses.last().reportStarted();
120+ Q_EMIT impl->showPrompt(message, Greeter::PromptTypeQuestion, responses.last());
121+ break;
122+ }
123+ case PAM_PROMPT_ECHO_OFF:
124+ {
125+ QString message(msg[count]->msg);
126+ responses.append(ResponseFuture());
127+ responses.last().reportStarted();
128+ Q_EMIT impl->showPrompt(message, Greeter::PromptTypeSecret, responses.last());
129+ break;
130+ }
131+ case PAM_TEXT_INFO:
132+ {
133+ QString message(msg[count]->msg);
134+ Q_EMIT impl->showMessage(message, Greeter::MessageTypeInfo);
135+ break;
136+ }
137+ default:
138+ {
139+ QString message(msg[count]->msg);
140+ Q_EMIT impl->showMessage(message, Greeter::MessageTypeError);
141+ break;
142+ }
143+ }
144+ }
145+
146+ int i = 0;
147+ bool raise_error = false;
148+
149+ for (auto &response : responses)
150+ {
151+ pam_response* resp_item = &tmp_response[i++];
152+ resp_item->resp_retcode = 0;
153+ resp_item->resp = strdup(response.future().result().toUtf8());
154+
155+ if (!resp_item->resp)
156+ {
157+ raise_error = true;
158+ break;
159+ }
160+ }
161+
162+ if (raise_error)
163+ {
164+ for (int i = 0; i < count; ++i)
165+ free(tmp_response[i].resp);
166+
167+ free(tmp_response);
168+ return PAM_CONV_ERR;
169+ }
170+ else
171+ {
172+ *resp = tmp_response;
173+ return PAM_SUCCESS;
174+ }
175+ }
176+
177+public Q_SLOTS:
178+ bool respond(QString response)
179+ {
180+ if (!futures.isEmpty()) {
181+ futures.dequeue().reportFinished(&response);
182+ return true;
183+ } else {
184+ return false;
185+ }
186+ }
187+
188+Q_SIGNALS:
189+ void showMessage(QString text, QLightDM::Greeter::MessageType type);
190+ void showPrompt(QString text, QLightDM::Greeter::PromptType type, QLightDM::GreeterImpl::ResponseFuture response);
191+
192+private Q_SLOTS:
193+ void finishPam()
194+ {
195+ if (pamHandle == NULL) {
196+ return;
197+ }
198+
199+ int pamStatus = futureWatcher.result();
200+
201+ pam_end(pamHandle, pamStatus);
202+ pamHandle = NULL;
203+
204+ greeterPrivate->authenticated = (pamStatus == PAM_SUCCESS);
205+ Q_EMIT greeter->authenticationComplete();
206+ }
207+
208+ void handlePrompt(QString text, QLightDM::Greeter::PromptType type, QLightDM::GreeterImpl::ResponseFuture future)
209+ {
210+ futures.enqueue(future);
211+ Q_EMIT greeter->showPrompt(text, type);
212+ }
213+
214+private:
215+ Greeter *greeter;
216+ GreeterPrivate *greeterPrivate;
217+ pam_handle* pamHandle;
218+ QFutureWatcher<int> futureWatcher;
219+ QQueue<ResponseFuture> futures;
220+};
221+
27222GreeterPrivate::GreeterPrivate(Greeter* parent)
28223 : authenticated(false),
29224 authenticationUser(),
225+ m_impl(new GreeterImpl(parent, this)),
30226 q_ptr(parent)
31227{
32228}
33229
34230void GreeterPrivate::handleAuthenticate()
35231{
36− Q_Q(Greeter);
37−
38− QSettings settings(QDir::homePath() + "/.unity8-greeter-demo", QSettings::NativeFormat);
39− settings.beginGroup(authenticationUser);
40− QVariant password = settings.value("password", "none");
41−
42− if (password == "pin") {
43− Q_EMIT q->showPrompt("PIN", Greeter::PromptTypeSecret);
44− } else if (password == "keyboard") {
45− Q_EMIT q->showPrompt("Password", Greeter::PromptTypeSecret);
46− } else {
47− authenticated = true;
48− Q_EMIT q->authenticationComplete();
49− }
232+ m_impl->start(authenticationUser);
50233}
51234
52235void GreeterPrivate::handleRespond(const QString &response)
53236{
54− Q_Q(Greeter);
55−
56− QSettings settings(QDir::homePath() + "/.unity8-greeter-demo", QSettings::NativeFormat);
57− settings.beginGroup(authenticationUser);
58− QVariant password = settings.value("password", "none");
59−
60− QString passwordValue;
61− if (password == "pin") {
62− passwordValue = settings.value("passwordValue", "1234").toString();
63− } else {
64− passwordValue = settings.value("passwordValue", "password").toString();
65− }
66− authenticated = (response == passwordValue);
67− Q_EMIT q->authenticationComplete();
237+ m_impl->respond(response);
68238}
69239
70240}
241+
242+#include "GreeterPrivate.moc"
tests/mocks/LightDM/demo/UsersModelPrivate.cpp+1−1
@@ -29,7 +29,7 @@ UsersModelPrivate::UsersModelPrivate(UsersModel* parent)
2929 : q_ptr(parent)
3030{
3131 QSettings settings(QDir::homePath() + "/.unity8-greeter-demo", QSettings::NativeFormat);
32− QStringList users = settings.value("users", QStringList() << "phablet").toStringList();
32+ QStringList users = settings.value("users", QStringList() << qgetenv("USER")).toStringList();
3333
3434 Q_FOREACH(const QString &user, users)
3535 {
tests/mocks/LightDM/full/GreeterPrivate.cpp+1−1
@@ -54,7 +54,7 @@ void GreeterPrivate::handleAuthenticate()
5454 authenticated = true;
5555 Q_EMIT q->authenticationComplete();
5656 } else if (authenticationUser == "has-pin"){
57− Q_EMIT q->showPrompt("PIN", Greeter::PromptTypeSecret);
57+ Q_EMIT q->showPrompt("Password:", Greeter::PromptTypeSecret);
5858 } else if (authenticationUser == "auth-error") {
5959 authenticated = false;
6060 Q_EMIT q->authenticationComplete();
tests/mocks/LightDM/single-pin/GreeterPrivate.cpp+1−1
@@ -32,7 +32,7 @@ GreeterPrivate::GreeterPrivate(Greeter* parent)
3232void GreeterPrivate::handleAuthenticate()
3333{
3434 Q_Q(Greeter);
35− Q_EMIT q->showPrompt("PIN", Greeter::PromptTypeSecret);
35+ Q_EMIT q->showPrompt("Password:", Greeter::PromptTypeSecret);
3636}
3737
3838void GreeterPrivate::handleRespond(const QString &response)
tests/mocks/LightDM/single-pin/UsersModelPrivate.cpp+1−1
@@ -26,7 +26,7 @@ UsersModelPrivate::UsersModelPrivate(UsersModel* parent)
2626{
2727 entries =
2828 {
29− { "single", "Has PIN", 0, 0, false, false, 0, 0 },
29+ { "has-pin", "Has PIN", 0, 0, false, false, 0, 0 },
3030 };
3131}
3232
tests/mocks/Unity/CMakeLists.txt+1−1
@@ -7,7 +7,7 @@ add_subdirectory(DashCommunicator)
77pkg_search_module(DEE dee-1.0 REQUIRED)
88pkg_search_module(GOBJECT gobject-2.0 REQUIRED)
99pkg_search_module(DEEQT libdee-qt5 REQUIRED)
10−pkg_check_modules(SCOPES_API REQUIRED unity-shell-scopes=2)
10+pkg_check_modules(SCOPES_API REQUIRED unity-shell-scopes=3)
1111
1212include_directories(
1313 ${CMAKE_CURRENT_BINARY_DIR}
tests/mocks/Unity/fake_scopes.cpp+5
@@ -125,6 +125,11 @@ bool Scopes::loaded() const
125125 return m_loaded;
126126}
127127
128+unity::shell::scopes::ScopeInterface* Scopes::overviewScope() const
129+{
130+ return nullptr;
131+}
132+
128133void Scopes::addScope(Scope* scope)
129134{
130135 int index = rowCount();
tests/mocks/Unity/fake_scopes.h+1
@@ -50,6 +50,7 @@ public:
5050 QModelIndex parent ( const QModelIndex & index ) const;
5151
5252 bool loaded() const override;
53+ unity::shell::scopes::ScopeInterface* overviewScope() const override;
5354
5455private Q_SLOTS:
5556 void updateScopes();
tests/plugins/Dash/CMakeLists.txt+1
@@ -79,3 +79,4 @@ set(qmltest_DEFAULT_NO_ADD_TEST TRUE)
7979set(qmltest_DEFAULT_PROPERTIES ENVIRONMENT "LC_ALL=C")
8080add_qml_test(. ScopeStyle IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
8181add_qml_test(. ListViewWithPageHeaderQML IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
82+add_qml_test(. CardAttributes IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
tests/plugins/Dash/cardcreator/1.res+63−61
@@ -5,8 +5,8 @@ AbstractButton {
55 property var cardData;
66 property var artShapeBorderSource: undefined;
77 property real fontScale: 1.0;
8− property var scopeStyle: null;
9− property int headerAlignment: Text.AlignLeft;
8+ property var scopeStyle: null;
9+ property int headerAlignment: Text.AlignLeft;
1010 property int fixedHeaderHeight: -1;
1111 property size fixedArtShapeSize: Qt.size(-1, -1);
1212 readonly property string title: cardData && cardData["title"] || "";
@@ -16,71 +16,73 @@ AbstractButton {
1616onArtShapeBorderSourceChanged: { if (artShapeBorderSource !== undefined && artShapeLoader.item) artShapeLoader.item.borderSource = artShapeBorderSource; }
1717readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
1818Item {
19− id: artShapeHolder;
20− height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
21− width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
22− anchors { horizontalCenter: parent.horizontalCenter; }
23− Loader {
24− id: artShapeLoader;
25− objectName: "artShapeLoader";
26− active: cardData && cardData["art"] || false;
27− asynchronous: root.asynchronous;
28− visible: status == Loader.Ready;
29− sourceComponent: UbuntuShape {
30− id: artShape;
31− objectName: "artShape";
32− radius: "medium";
33− visible: image.status == Image.Ready;
34− readonly property real fixedArtShapeSizeAspect: (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) ? root.fixedArtShapeSize.width / root.fixedArtShapeSize.height : -1;
35− readonly property real aspect: fixedArtShapeSizeAspect > 0 ? fixedArtShapeSizeAspect : components !== undefined ? components["art"]["aspect-ratio"] : 1;
36− readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect;
37− Component.onCompleted: { updateWidthHeightBindings(); if (artShapeBorderSource !== undefined) borderSource = artShapeBorderSource; }
38− onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings();
39− Connections { target: root; onFixedArtShapeSizeChanged: updateWidthHeightBindings(); }
40− function updateWidthHeightBindings() {
41− if (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) {
42− width = root.fixedArtShapeSize.width;
43− height = root.fixedArtShapeSize.height;
44− } else if (aspectSmallerThanImageAspect) {
45− width = Qt.binding(function() { return !visible ? 0 : image.width });
46− height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect });
47− } else {
48− width = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.width : height * image.aspect });
49− height = Qt.binding(function() { return !visible ? 0 : image.height });
50− }
51− }
52− image: Image {
53− objectName: "artImage";
54− source: cardData && cardData["art"] || "";
55− cache: true;
19+ id: artShapeHolder;
20+ height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
21+ width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
22+ anchors { horizontalCenter: parent.horizontalCenter; }
23+ Loader {
24+ id: artShapeLoader;
25+ objectName: "artShapeLoader";
26+ active: cardData && cardData["art"] || false;
5627 asynchronous: root.asynchronous;
57− fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop;
58− readonly property real aspect: implicitWidth / implicitHeight;
59− width: root.width;
60− height: width / artShape.aspect;
28+ visible: status == Loader.Ready;
29+ sourceComponent: UbuntuShape {
30+ id: artShape;
31+ objectName: "artShape";
32+ radius: "medium";
33+ visible: image.status == Image.Ready;
34+ readonly property real fixedArtShapeSizeAspect: (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) ? root.fixedArtShapeSize.width / root.fixedArtShapeSize.height : -1;
35+ readonly property real aspect: fixedArtShapeSizeAspect > 0 ? fixedArtShapeSizeAspect : components !== undefined ? components["art"]["aspect-ratio"] : 1;
36+ readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect;
37+ Component.onCompleted: { updateWidthHeightBindings(); if (artShapeBorderSource !== undefined) borderSource = artShapeBorderSource; }
38+ onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings();
39+ Connections { target: root; onFixedArtShapeSizeChanged: updateWidthHeightBindings(); }
40+ function updateWidthHeightBindings() {
41+ if (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) {
42+ width = root.fixedArtShapeSize.width;
43+ height = root.fixedArtShapeSize.height;
44+ } else if (aspectSmallerThanImageAspect) {
45+ width = Qt.binding(function() { return !visible ? 0 : image.width });
46+ height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect });
47+ } else {
48+ width = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.width : height * image.aspect });
49+ height = Qt.binding(function() { return !visible ? 0 : image.height });
50+ }
51+ }
52+ image: Image {
53+ objectName: "artImage";
54+ source: cardData && cardData["art"] || "";
55+ cache: true;
56+ asynchronous: root.asynchronous;
57+ fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop;
58+ readonly property real aspect: implicitWidth / implicitHeight;
59+ width: root.width;
60+ height: width / artShape.aspect;
61+ }
62+ }
6163 }
62− }
63− }
64− }
64+ }
6565readonly property int headerHeight: titleLabel.height;
6666Label {
67− id: titleLabel;
68− objectName: "titleLabel";
69− anchors { right: parent.right;left: parent.left;
67+ id: titleLabel;
68+ objectName: "titleLabel";
69+ anchors { right: parent.right;
70+rightMargin: units.gu(1);
71+left: parent.left;
7072top: artShapeHolder.bottom;
7173 topMargin: units.gu(1);
7274leftMargin: units.gu(1);
73− }
74− elide: Text.ElideRight;
75− fontSize: "small";
76− wrapMode: Text.Wrap;
77− maximumLineCount: 2;
78− font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
79− color: root.scopeStyle ? root.scopeStyle.foreground : "grey";
80− visible: showHeader ;
81− text: root.title;
82− font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
83− horizontalAlignment: root.headerAlignment;
84− }
75+ }
76+ elide: Text.ElideRight;
77+ fontSize: "small";
78+ wrapMode: Text.Wrap;
79+ maximumLineCount: 2;
80+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
81+ color: root.scopeStyle ? root.scopeStyle.foreground : "grey";
82+ visible: showHeader ;
83+ text: root.title;
84+ font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
85+ horizontalAlignment: root.headerAlignment;
86+ }
8587implicitHeight: titleLabel.y + titleLabel.height + units.gu(1);
8688}
tests/plugins/Dash/cardcreator/2.res+94−91
@@ -5,8 +5,8 @@ AbstractButton {
55 property var cardData;
66 property var artShapeBorderSource: undefined;
77 property real fontScale: 1.0;
8− property var scopeStyle: null;
9− property int headerAlignment: Text.AlignLeft;
8+ property var scopeStyle: null;
9+ property int headerAlignment: Text.AlignLeft;
1010 property int fixedHeaderHeight: -1;
1111 property size fixedArtShapeSize: Qt.size(-1, -1);
1212 readonly property string title: cardData && cardData["title"] || "";
@@ -14,104 +14,107 @@ AbstractButton {
1414 property bool showHeader: true;
1515 implicitWidth: childrenRect.width;
1616Loader {
17− id: backgroundLoader;
18− objectName: "backgroundLoader";
19− anchors.fill: parent;
20− asynchronous: root.asynchronous;
21− visible: status == Loader.Ready;
22− sourceComponent: UbuntuShape {
23− objectName: "background";
24− radius: "medium";
25− color: getColor(0) || "white";
26− gradientColor: getColor(1) || color;
27− anchors.fill: parent;
28− image: backgroundImage.source ? backgroundImage : null;
29− property real luminance: 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
30− property Image backgroundImage: Image {
31− objectName: "backgroundImage";
32− source: {
33− if (cardData && typeof cardData["background"] === "string") return cardData["background"];
34− else if (template && typeof template["card-background"] === "string") return template["card-background"];
35− else return "";
36− }
37− }
38− function getColor(index) {
39− if (cardData && typeof cardData["background"] === "object"
40− && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
41− return cardData["background"]["elements"][index];
42− } else if (template && typeof template["card-background"] === "object"
43− && (template["card-background"]["type"] === "color" || template["card-background"]["type"] === "gradient")) {
44− return template["card-background"]["elements"][index];
45− } else return undefined;
46− }
47− }
48− }
17+ id: backgroundLoader;
18+ objectName: "backgroundLoader";
19+ anchors.fill: parent;
20+ asynchronous: root.asynchronous;
21+ visible: status == Loader.Ready;
22+ sourceComponent: UbuntuShape {
23+ objectName: "background";
24+ radius: "medium";
25+ color: getColor(0) || "white";
26+ gradientColor: getColor(1) || color;
27+ anchors.fill: parent;
28+ image: backgroundImage.source ? backgroundImage : null;
29+ property real luminance: 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
30+ property Image backgroundImage: Image {
31+ objectName: "backgroundImage";
32+ source: {
33+ if (cardData && typeof cardData["background"] === "string") return cardData["background"];
34+ else if (template && typeof template["card-background"] === "string") return template["card-background"];
35+ else return "";
36+ }
37+ }
38+ function getColor(index) {
39+ if (cardData && typeof cardData["background"] === "object"
40+ && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
41+ return cardData["background"]["elements"][index];
42+ } else if (template && typeof template["card-background"] === "object"
43+ && (template["card-background"]["type"] === "color" || template["card-background"]["type"] === "gradient")) {
44+ return template["card-background"]["elements"][index];
45+ } else return undefined;
46+ }
47+ }
48+ }
4949readonly property size artShapeSize: Qt.size(-1, -1);
5050readonly property int headerHeight: row.height;
5151Row {
52− id: row;
53− objectName: "outerRow";
54− property real margins: units.gu(1);
55− spacing: margins;
56− height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight;
57− anchors { top: parent.top;
52+ id: row;
53+ objectName: "outerRow";
54+ property real margins: units.gu(1);
55+ spacing: margins;
56+ height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight;
57+ anchors { top: parent.top;
5858 topMargin: units.gu(1);
59− left: parent.left;
60−}
61− anchors.right: parent.right;
62− anchors.margins: margins;
63−data: [ Image {
64− id: mascotImage;
65− objectName: "mascotImage";
66− anchors { verticalCenter: parent.verticalCenter; }
67− readonly property int maxSize: Math.max(width, height) * 4;
68− source: cardData && cardData["mascot"];
69− width: units.gu(6);
70− height: units.gu(5.625);
71− sourceSize { width: maxSize; height: maxSize }
72− fillMode: Image.PreserveAspectCrop;
73− horizontalAlignment: Image.AlignHCenter;
74− verticalAlignment: Image.AlignVCenter;
75− visible: showHeader;
76− }
77−,
78−Column {
79− anchors.verticalCenter: parent.verticalCenter;
80− spacing: units.dp(2);
81− width: parent.width - x;
82− data: [ Label {
83− id: titleLabel;
84− objectName: "titleLabel";
85− anchors { left: parent.left; right: parent.right }
86− elide: Text.ElideRight;
87− fontSize: "small";
88− wrapMode: Text.Wrap;
89− maximumLineCount: 2;
90− font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
91− color: backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : (root.scopeStyle ? root.scopeStyle.foreground : "grey");
92− visible: showHeader ;
93− text: root.title;
94− font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
95− horizontalAlignment: root.headerAlignment;
96− }
97−,
98−Label {
99− id: subtitleLabel;
100− objectName: "subtitleLabel";
101− anchors { left: parent.left; right: parent.right }
59+left: parent.left;
60+ }
61+ anchors.right: parent.right;
62+ anchors.margins: margins;
63+ data: [ Image {
64+ id: mascotImage;
65+ objectName: "mascotImage";
66+ anchors { verticalCenter: parent.verticalCenter; }
67+ readonly property int maxSize: Math.max(width, height) * 4;
68+ source: cardData && cardData["mascot"];
69+ width: units.gu(6);
70+ height: units.gu(5.625);
71+ sourceSize { width: maxSize; height: maxSize }
72+ fillMode: Image.PreserveAspectCrop;
73+ horizontalAlignment: Image.AlignHCenter;
74+ verticalAlignment: Image.AlignVCenter;
75+ visible: showHeader;
76+ }
10277
78+ ,
79+ Column {
80+ anchors.verticalCenter: parent.verticalCenter;
81+ spacing: units.dp(2);
82+ width: parent.width - x;
83+ data: [ Label {
84+ id: titleLabel;
85+ objectName: "titleLabel";
86+ anchors { left: parent.left; right: parent.right }
10387 elide: Text.ElideRight;
10488 fontSize: "small";
89+ wrapMode: Text.Wrap;
90+ maximumLineCount: 2;
10591 font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
106− color: backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : (root.scopeStyle ? root.scopeStyle.foreground : "grey");
107− visible: titleLabel.visible && titleLabel.text;
108− text: cardData && cardData["subtitle"] || "";
109− font.weight: Font.Light;
92+ color: backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : (root.scopeStyle ? root.scopeStyle.foreground : "grey");
93+ visible: showHeader ;
94+ text: root.title;
95+ font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
11096 horizontalAlignment: root.headerAlignment;
11197 }
112−]
113−}
114−]
115−}
98+
99+ ,
100+ Label {
101+ id: subtitleLabel;
102+ objectName: "subtitleLabel";
103+ anchors { left: parent.left; right: parent.right }
104+ elide: Text.ElideRight;
105+ fontSize: "small";
106+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
107+ color: backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : (root.scopeStyle ? root.scopeStyle.foreground : "grey");
108+ visible: titleLabel.visible && titleLabel.text;
109+ text: cardData && cardData["subtitle"] || "";
110+ font.weight: Font.Light;
111+ horizontalAlignment: root.headerAlignment;
112+ }
113+
114+ ]
115+ }
116+
117+ ]
118+ }
116119implicitHeight: row.y + row.height + units.gu(1);
117120}
tests/plugins/Dash/cardcreator/3.res+77−74
@@ -5,8 +5,8 @@ AbstractButton {
55 property var cardData;
66 property var artShapeBorderSource: undefined;
77 property real fontScale: 1.0;
8− property var scopeStyle: null;
9− property int headerAlignment: Text.AlignLeft;
8+ property var scopeStyle: null;
9+ property int headerAlignment: Text.AlignLeft;
1010 property int fixedHeaderHeight: -1;
1111 property size fixedArtShapeSize: Qt.size(-1, -1);
1212 readonly property string title: cardData && cardData["title"] || "";
@@ -16,89 +16,92 @@ AbstractButton {
1616onArtShapeBorderSourceChanged: { if (artShapeBorderSource !== undefined && artShapeLoader.item) artShapeLoader.item.borderSource = artShapeBorderSource; }
1717readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
1818Item {
19− id: artShapeHolder;
20− height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
21− width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
22− anchors { horizontalCenter: parent.horizontalCenter; }
23− Loader {
24− id: artShapeLoader;
25− objectName: "artShapeLoader";
26− active: cardData && cardData["art"] || false;
27− asynchronous: root.asynchronous;
28− visible: status == Loader.Ready;
29− sourceComponent: UbuntuShape {
30− id: artShape;
31− objectName: "artShape";
32− radius: "medium";
33− visible: image.status == Image.Ready;
34− readonly property real fixedArtShapeSizeAspect: (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) ? root.fixedArtShapeSize.width / root.fixedArtShapeSize.height : -1;
35− readonly property real aspect: fixedArtShapeSizeAspect > 0 ? fixedArtShapeSizeAspect : components !== undefined ? components["art"]["aspect-ratio"] : 1;
36− readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect;
37− Component.onCompleted: { updateWidthHeightBindings(); if (artShapeBorderSource !== undefined) borderSource = artShapeBorderSource; }
38− onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings();
39− Connections { target: root; onFixedArtShapeSizeChanged: updateWidthHeightBindings(); }
40− function updateWidthHeightBindings() {
41− if (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) {
42− width = root.fixedArtShapeSize.width;
43− height = root.fixedArtShapeSize.height;
44− } else if (aspectSmallerThanImageAspect) {
45− width = Qt.binding(function() { return !visible ? 0 : image.width });
46− height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect });
47− } else {
48− width = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.width : height * image.aspect });
49− height = Qt.binding(function() { return !visible ? 0 : image.height });
50− }
51− }
52− image: Image {
53− objectName: "artImage";
54− source: cardData && cardData["art"] || "";
55− cache: true;
19+ id: artShapeHolder;
20+ height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
21+ width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
22+ anchors { horizontalCenter: parent.horizontalCenter; }
23+ Loader {
24+ id: artShapeLoader;
25+ objectName: "artShapeLoader";
26+ active: cardData && cardData["art"] || false;
5627 asynchronous: root.asynchronous;
57− fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop;
58− readonly property real aspect: implicitWidth / implicitHeight;
59− width: root.width;
60− height: width / artShape.aspect;
28+ visible: status == Loader.Ready;
29+ sourceComponent: UbuntuShape {
30+ id: artShape;
31+ objectName: "artShape";
32+ radius: "medium";
33+ visible: image.status == Image.Ready;
34+ readonly property real fixedArtShapeSizeAspect: (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) ? root.fixedArtShapeSize.width / root.fixedArtShapeSize.height : -1;
35+ readonly property real aspect: fixedArtShapeSizeAspect > 0 ? fixedArtShapeSizeAspect : components !== undefined ? components["art"]["aspect-ratio"] : 1;
36+ readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect;
37+ Component.onCompleted: { updateWidthHeightBindings(); if (artShapeBorderSource !== undefined) borderSource = artShapeBorderSource; }
38+ onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings();
39+ Connections { target: root; onFixedArtShapeSizeChanged: updateWidthHeightBindings(); }
40+ function updateWidthHeightBindings() {
41+ if (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) {
42+ width = root.fixedArtShapeSize.width;
43+ height = root.fixedArtShapeSize.height;
44+ } else if (aspectSmallerThanImageAspect) {
45+ width = Qt.binding(function() { return !visible ? 0 : image.width });
46+ height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect });
47+ } else {
48+ width = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.width : height * image.aspect });
49+ height = Qt.binding(function() { return !visible ? 0 : image.height });
50+ }
51+ }
52+ image: Image {
53+ objectName: "artImage";
54+ source: cardData && cardData["art"] || "";
55+ cache: true;
56+ asynchronous: root.asynchronous;
57+ fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop;
58+ readonly property real aspect: implicitWidth / implicitHeight;
59+ width: root.width;
60+ height: width / artShape.aspect;
61+ }
62+ }
6163 }
62− }
63− }
64− }
64+ }
6565readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;
6666Label {
67− id: titleLabel;
68− objectName: "titleLabel";
69− anchors { right: parent.right;left: parent.left;
67+ id: titleLabel;
68+ objectName: "titleLabel";
69+ anchors { right: parent.right;
70+rightMargin: units.gu(1);
71+left: parent.left;
7072top: artShapeHolder.bottom;
7173 topMargin: units.gu(1);
7274leftMargin: units.gu(1);
73−}
74− elide: Text.ElideRight;
75− fontSize: "small";
76− wrapMode: Text.Wrap;
77− maximumLineCount: 2;
78− font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
79− color: root.scopeStyle ? root.scopeStyle.foreground : "grey";
80− visible: showHeader ;
81− text: root.title;
82− font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
83− horizontalAlignment: root.headerAlignment;
84− }
85−Label {
86− id: subtitleLabel;
87− objectName: "subtitleLabel";
88− anchors { left: titleLabel.left;
89− leftMargin: titleLabel.leftMargin;
90− right: titleLabel.right;
91− top: titleLabel.bottom;
92− topMargin: units.dp(2);
93− }
75+ }
9476 elide: Text.ElideRight;
9577 fontSize: "small";
78+ wrapMode: Text.Wrap;
79+ maximumLineCount: 2;
9680 font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
97− color: root.scopeStyle ? root.scopeStyle.foreground : "grey";
98− visible: titleLabel.visible && titleLabel.text;
99− text: cardData && cardData["subtitle"] || "";
100− font.weight: Font.Light;
81+ color: root.scopeStyle ? root.scopeStyle.foreground : "grey";
82+ visible: showHeader ;
83+ text: root.title;
84+ font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
10185 horizontalAlignment: root.headerAlignment;
10286 }
87+Label {
88+ id: subtitleLabel;
89+ objectName: "subtitleLabel";
90+ anchors { left: titleLabel.left;
91+ leftMargin: titleLabel.leftMargin;
92+ right: titleLabel.right;
93+ rightMargin: titleLabel.rightMargin;
94+ top: titleLabel.bottom;
95+ topMargin: units.dp(2);
96+ }
97+ elide: Text.ElideRight;
98+ fontSize: "small";
99+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
100+ color: root.scopeStyle ? root.scopeStyle.foreground : "grey";
101+ visible: titleLabel.visible && titleLabel.text;
102+ text: cardData && cardData["subtitle"] || "";
103+ font.weight: Font.Light;
104+ horizontalAlignment: root.headerAlignment;
105+ }
103106implicitHeight: subtitleLabel.y + subtitleLabel.height + units.gu(1);
104107}
tests/plugins/Dash/cardcreator/5.res+119−117
@@ -4,9 +4,9 @@ AbstractButton {
44 property var components;
55 property var cardData;
66 property var artShapeBorderSource: undefined;
7− property real fontScale: 1.0;
8− property var scopeStyle: null;
9− property int headerAlignment: Text.AlignLeft;
7+ property real fontScale: 1.0;
8+ property var scopeStyle: null;
9+ property int headerAlignment: Text.AlignLeft;
1010 property int fixedHeaderHeight: -1;
1111 property size fixedArtShapeSize: Qt.size(-1, -1);
1212 readonly property string title: cardData && cardData["title"] || "";
@@ -16,130 +16,132 @@ AbstractButton {
1616onArtShapeBorderSourceChanged: { if (artShapeBorderSource !== undefined && artShapeLoader.item) artShapeLoader.item.borderSource = artShapeBorderSource; }
1717readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
1818Item {
19− id: artShapeHolder;
20− height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
21− width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
22− anchors { horizontalCenter: parent.horizontalCenter; }
23− Loader {
24− id: artShapeLoader;
25− objectName: "artShapeLoader";
26− active: cardData && cardData["art"] || false;
27− asynchronous: root.asynchronous;
28− visible: status == Loader.Ready;
29− sourceComponent: UbuntuShape {
30− id: artShape;
31− objectName: "artShape";
32− radius: "medium";
33− visible: image.status == Image.Ready;
34− readonly property real fixedArtShapeSizeAspect: (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) ? root.fixedArtShapeSize.width / root.fixedArtShapeSize.height : -1;
35− readonly property real aspect: fixedArtShapeSizeAspect > 0 ? fixedArtShapeSizeAspect : components !== undefined ? components["art"]["aspect-ratio"] : 1;
36− readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect;
37− Component.onCompleted: { updateWidthHeightBindings(); if (artShapeBorderSource !== undefined) borderSource = artShapeBorderSource; }
38− onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings();
39− Connections { target: root; onFixedArtShapeSizeChanged: updateWidthHeightBindings(); }
40− function updateWidthHeightBindings() {
41− if (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) {
42− width = root.fixedArtShapeSize.width;
43− height = root.fixedArtShapeSize.height;
44− } else if (aspectSmallerThanImageAspect) {
45− width = Qt.binding(function() { return !visible ? 0 : image.width });
46− height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect });
47− } else {
48− width = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.width : height * image.aspect });
49− height = Qt.binding(function() { return !visible ? 0 : image.height });
50− }
51− }
52− image: Image {
53− objectName: "artImage";
54− source: cardData && cardData["art"] || "";
55− cache: true;
19+ id: artShapeHolder;
20+ height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
21+ width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
22+ anchors { horizontalCenter: parent.horizontalCenter; }
23+ Loader {
24+ id: artShapeLoader;
25+ objectName: "artShapeLoader";
26+ active: cardData && cardData["art"] || false;
5627 asynchronous: root.asynchronous;
57− fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop;
58− readonly property real aspect: implicitWidth / implicitHeight;
59− width: root.width;
60− height: width / artShape.aspect;
28+ visible: status == Loader.Ready;
29+ sourceComponent: UbuntuShape {
30+ id: artShape;
31+ objectName: "artShape";
32+ radius: "medium";
33+ visible: image.status == Image.Ready;
34+ readonly property real fixedArtShapeSizeAspect: (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) ? root.fixedArtShapeSize.width / root.fixedArtShapeSize.height : -1;
35+ readonly property real aspect: fixedArtShapeSizeAspect > 0 ? fixedArtShapeSizeAspect : components !== undefined ? components["art"]["aspect-ratio"] : 1;
36+ readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect;
37+ Component.onCompleted: { updateWidthHeightBindings(); if (artShapeBorderSource !== undefined) borderSource = artShapeBorderSource; }
38+ onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings();
39+ Connections { target: root; onFixedArtShapeSizeChanged: updateWidthHeightBindings(); }
40+ function updateWidthHeightBindings() {
41+ if (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) {
42+ width = root.fixedArtShapeSize.width;
43+ height = root.fixedArtShapeSize.height;
44+ } else if (aspectSmallerThanImageAspect) {
45+ width = Qt.binding(function() { return !visible ? 0 : image.width });
46+ height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect });
47+ } else {
48+ width = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.width : height * image.aspect });
49+ height = Qt.binding(function() { return !visible ? 0 : image.height });
50+ }
51+ }
52+ image: Image {
53+ objectName: "artImage";
54+ source: cardData && cardData["art"] || "";
55+ cache: true;
56+ asynchronous: root.asynchronous;
57+ fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop;
58+ readonly property real aspect: implicitWidth / implicitHeight;
59+ width: root.width;
60+ height: width / artShape.aspect;
61+ }
62+ }
6163 }
62− }
63− }
64− }
64+ }
6565Loader {
66− id: overlayLoader;
67− anchors {
68− left: artShapeHolder.left;
69− right: artShapeHolder.right;
70− bottom: artShapeHolder.bottom;
71− }
72− active: artShapeLoader.active && artShapeLoader.item && artShapeLoader.item.image.status === Image.Ready || false;
73− asynchronous: root.asynchronous;
74− visible: showHeader && status == Loader.Ready;
75− sourceComponent: ShaderEffect {
76− id: overlay;
77− height: (fixedHeaderHeight > 0 ? fixedHeaderHeight : headerHeight) + units.gu(2);
78− opacity: 0.6;
79− property var source: ShaderEffectSource {
80− id: shaderSource;
81− sourceItem: artShapeLoader.item;
82− onVisibleChanged: if (visible) scheduleUpdate();
83− live: false;
84− sourceRect: Qt.rect(0, artShapeLoader.height - overlay.height, artShapeLoader.width, overlay.height);
85− }
86− vertexShader: "
87− uniform highp mat4 qt_Matrix;
88− attribute highp vec4 qt_Vertex;
89− attribute highp vec2 qt_MultiTexCoord0;
90− varying highp vec2 coord;
91− void main() {
92− coord = qt_MultiTexCoord0;
93− gl_Position = qt_Matrix * qt_Vertex;
94− }";
95− fragmentShader: "
96− varying highp vec2 coord;
97− uniform sampler2D source;
98− uniform lowp float qt_Opacity;
99− void main() {
100− lowp vec4 tex = texture2D(source, coord);
101− gl_FragColor = vec4(0, 0, 0, tex.a) * qt_Opacity;
102− }";
103− }
104− }
66+ id: overlayLoader;
67+ anchors {
68+ left: artShapeHolder.left;
69+ right: artShapeHolder.right;
70+ bottom: artShapeHolder.bottom;
71+ }
72+ active: artShapeLoader.active && artShapeLoader.item && artShapeLoader.item.image.status === Image.Ready || false;
73+ asynchronous: root.asynchronous;
74+ visible: showHeader && status == Loader.Ready;
75+ sourceComponent: ShaderEffect {
76+ id: overlay;
77+ height: (fixedHeaderHeight > 0 ? fixedHeaderHeight : headerHeight) + units.gu(2);
78+ opacity: 0.6;
79+ property var source: ShaderEffectSource {
80+ id: shaderSource;
81+ sourceItem: artShapeLoader.item;
82+ onVisibleChanged: if (visible) scheduleUpdate();
83+ live: false;
84+ sourceRect: Qt.rect(0, artShapeLoader.height - overlay.height, artShapeLoader.width, overlay.height);
85+ }
86+ vertexShader: "
87+ uniform highp mat4 qt_Matrix;
88+ attribute highp vec4 qt_Vertex;
89+ attribute highp vec2 qt_MultiTexCoord0;
90+ varying highp vec2 coord;
91+ void main() {
92+ coord = qt_MultiTexCoord0;
93+ gl_Position = qt_Matrix * qt_Vertex;
94+ }";
95+ fragmentShader: "
96+ varying highp vec2 coord;
97+ uniform sampler2D source;
98+ uniform lowp float qt_Opacity;
99+ void main() {
100+ lowp vec4 tex = texture2D(source, coord);
101+ gl_FragColor = vec4(0, 0, 0, tex.a) * qt_Opacity;
102+ }";
103+ }
104+ }
105105readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;
106106Label {
107− id: titleLabel;
108− objectName: "titleLabel";
109− anchors { left: parent.left;
110− leftMargin: units.gu(1);
111− right: parent.right;
112− top: overlayLoader.top;
113− topMargin: units.gu(1);
114−}
115− elide: Text.ElideRight;
116− fontSize: "small";
117− wrapMode: Text.Wrap;
118− maximumLineCount: 2;
119− font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
120− color: "white";
121− visible: showHeader && overlayLoader.active;
122− text: root.title;
123− font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
124− horizontalAlignment: root.headerAlignment;
125− }
126−Label {
127− id: subtitleLabel;
128− objectName: "subtitleLabel";
129− anchors { left: titleLabel.left;
130− leftMargin: titleLabel.leftMargin;
131− right: titleLabel.right;
132− top: titleLabel.bottom;
133− topMargin: units.dp(2);
134− }
107+ id: titleLabel;
108+ objectName: "titleLabel";
109+ anchors { left: parent.left;
110+ leftMargin: units.gu(1);
111+ right: parent.right;
112+ rightMargin: units.gu(1);
113+ top: overlayLoader.top;
114+ topMargin: units.gu(1);
115+ }
135116 elide: Text.ElideRight;
136117 fontSize: "small";
118+ wrapMode: Text.Wrap;
119+ maximumLineCount: 2;
137120 font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
138121 color: "white";
139− visible: titleLabel.visible && titleLabel.text;
140− text: cardData && cardData["subtitle"] || "";
141− font.weight: Font.Light;
122+ visible: showHeader && overlayLoader.active;
123+ text: root.title;
124+ font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
142125 horizontalAlignment: root.headerAlignment;
143126 }
127+Label {
128+ id: subtitleLabel;
129+ objectName: "subtitleLabel";
130+ anchors { left: titleLabel.left;
131+ leftMargin: titleLabel.leftMargin;
132+ right: titleLabel.right;
133+ rightMargin: titleLabel.rightMargin;
134+ top: titleLabel.bottom;
135+ topMargin: units.dp(2);
136+ }
137+ elide: Text.ElideRight;
138+ fontSize: "small";
139+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
140+ color: "white";
141+ visible: titleLabel.visible && titleLabel.text;
142+ text: cardData && cardData["subtitle"] || "";
143+ font.weight: Font.Light;
144+ horizontalAlignment: root.headerAlignment;
145+ }
144146implicitHeight: subtitleLabel.y + subtitleLabel.height + units.gu(1);
145147}
tests/plugins/Dash/cardcreator/7.resnew file+128
@@ -0,0 +1,128 @@
1+AbstractButton {
2+ id: root;
3+ property var template;
4+ property var components;
5+ property var cardData;
6+ property var artShapeBorderSource: undefined;
7+ property real fontScale: 1.0;
8+ property var scopeStyle: null;
9+ property int headerAlignment: Text.AlignLeft;
10+ property int fixedHeaderHeight: -1;
11+ property size fixedArtShapeSize: Qt.size(-1, -1);
12+ readonly property string title: cardData && cardData["title"] || "";
13+ property bool asynchronous: true;
14+ property bool showHeader: true;
15+ implicitWidth: childrenRect.width;
16+Loader {
17+ id: backgroundLoader;
18+ objectName: "backgroundLoader";
19+ anchors.fill: parent;
20+ asynchronous: root.asynchronous;
21+ visible: status == Loader.Ready;
22+ sourceComponent: UbuntuShape {
23+ objectName: "background";
24+ radius: "medium";
25+ color: getColor(0) || "white";
26+ gradientColor: getColor(1) || color;
27+ anchors.fill: parent;
28+ image: backgroundImage.source ? backgroundImage : null;
29+ property real luminance: 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
30+ property Image backgroundImage: Image {
31+ objectName: "backgroundImage";
32+ source: {
33+ if (cardData && typeof cardData["background"] === "string") return cardData["background"];
34+ else if (template && typeof template["card-background"] === "string") return template["card-background"];
35+ else return "";
36+ }
37+ }
38+ function getColor(index) {
39+ if (cardData && typeof cardData["background"] === "object"
40+ && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
41+ return cardData["background"]["elements"][index];
42+ } else if (template && typeof template["card-background"] === "object"
43+ && (template["card-background"]["type"] === "color" || template["card-background"]["type"] === "gradient")) {
44+ return template["card-background"]["elements"][index];
45+ } else return undefined;
46+ }
47+ }
48+ }
49+readonly property size artShapeSize: Qt.size(-1, -1);
50+readonly property int headerHeight: row.height;
51+Row {
52+ id: row;
53+ objectName: "outerRow";
54+ property real margins: units.gu(1);
55+ spacing: margins;
56+ height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight;
57+ anchors { top: parent.top;
58+ topMargin: units.gu(1);
59+left: parent.left;
60+ }
61+ anchors.right: parent.right;
62+ anchors.margins: margins;
63+ data: [ Image {
64+ id: mascotImage;
65+ objectName: "mascotImage";
66+ anchors { verticalCenter: parent.verticalCenter; }
67+ readonly property int maxSize: Math.max(width, height) * 4;
68+ source: cardData && cardData["mascot"];
69+ width: units.gu(6);
70+ height: units.gu(5.625);
71+ sourceSize { width: maxSize; height: maxSize }
72+ fillMode: Image.PreserveAspectCrop;
73+ horizontalAlignment: Image.AlignHCenter;
74+ verticalAlignment: Image.AlignVCenter;
75+ visible: showHeader;
76+ }
77+
78+ ,
79+ Column {
80+ anchors.verticalCenter: parent.verticalCenter;
81+ spacing: units.dp(2);
82+ width: parent.width - x;
83+ data: [
84+ Label {
85+ id: titleLabel;
86+ objectName: "titleLabel";
87+ anchors { left: parent.left; right: parent.right }
88+ elide: Text.ElideRight;
89+ fontSize: "small";
90+ wrapMode: Text.Wrap;
91+ maximumLineCount: 2;
92+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
93+ color: backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : (root.scopeStyle ? root.scopeStyle.foreground : "grey");
94+ visible: showHeader ;
95+ text: root.title;
96+ font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
97+ horizontalAlignment: root.headerAlignment;
98+ }
99+,
100+Label {
101+ id: subtitleLabel;
102+ objectName: "subtitleLabel";
103+ anchors { left: parent.left; right: parent.right }
104+ elide: Text.ElideRight;
105+ fontSize: "small";
106+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
107+ color: backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : (root.scopeStyle ? root.scopeStyle.foreground : "grey");
108+ visible: titleLabel.visible && titleLabel.text;
109+ text: cardData && cardData["subtitle"] || "";
110+ font.weight: Font.Light;
111+ horizontalAlignment: root.headerAlignment;
112+ }
113+,
114+CardAttributes {
115+ id: attributesRow;
116+ objectName: "attributesRow";
117+ anchors { left: parent.left; right: parent.right }
118+ color: backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : (root.scopeStyle ? root.scopeStyle.foreground : "grey");
119+ model: cardData["attributes"]
120+ }
121+
122+ ]
123+ }
124+
125+ ]
126+ }
127+implicitHeight: row.y + row.height + units.gu(1);
128+}
tests/plugins/Dash/cardcreator/7.tstnew file+3
@@ -0,0 +1,3 @@
1+template: {"card-background":{"elements":["#E9E9E9"],"type":"color"},"card-layout":"vertical","card-size":"medium","category-layout":"grid","collapsed-rows":2}
2+components: {"art":{"aspect-ratio":1,"fill-mode":"crop"},"background":{"field":"background"},"mascot":{"field":"icon"},"subtitle":{"field":"author"},"title":{"field":"title"},"attributes":{"field":"attributes","max-count":2}}
3+result: 7.res
tests/plugins/Dash/cardcreatortest.cpp+9−1
@@ -19,6 +19,7 @@
1919#include <QQuickItem>
2020#include <QQuickView>
2121#include <QtTestGui>
22+#include <QTemporaryFile>
2223
2324class CardCreatorTest : public QObject
2425{
@@ -74,7 +75,14 @@ private Q_SLOTS:
7475 QVERIFY(testResultFile.open(QIODevice::ReadOnly));
7576 QTextStream ts2(&testResultFile);
7677 const QString expectedResult = ts2.readAll();
77− QCOMPARE(cardStringResult.toString().simplified(), expectedResult.simplified());
78+ const QString executedResult = cardStringResult.toString();
79+ // Record failed results to /tmp
80+ QTemporaryFile tmpFile(QDir::tempPath() + QDir::separator() + "testCardCreatorFailedResultXXXXXX");
81+ tmpFile.open();
82+ tmpFile.setAutoRemove(false);
83+ tmpFile.write(executedResult.toUtf8().constData());
84+ QCOMPARE(executedResult.simplified(), expectedResult.simplified());
85+ tmpFile.setAutoRemove(true); // Remove the result if it passed
7886
7987 QVariant createCardComponentResult;
8088 QMetaObject::invokeMethod(view->rootObject(), "createCardComponent", Q_RETURN_ARG(QVariant, createCardComponentResult), Q_ARG(QVariant, templateJSON), Q_ARG(QVariant, componentsJSON));
tests/plugins/Dash/listviewwithpageheadersectiontest.cpp−1
@@ -40,7 +40,6 @@ private:
4040 QTRY_COMPARE(lvwph->m_visibleItems[visibleIndex]->height(), height);
4141 QCOMPARE(QQuickItemPrivate::get(lvwph->m_visibleItems[visibleIndex]->m_item)->culled, culled);
4242 QCOMPARE(section(lvwph->m_visibleItems[visibleIndex]->sectionItem()), sectionHeader);
43− QTRY_COMPARE(hasSectionHeaderProperty(lvwph->m_visibleItems[visibleIndex]->m_item), !sectionHeader.isNull());
4443 if (!sectionHeader.isNull()) {
4544 QCOMPARE(QQuickItemPrivate::get(lvwph->m_visibleItems[visibleIndex]->sectionItem())->culled, sectionHeaderCulled);
4645 QCOMPARE(sectionDelegateIndex(lvwph->m_visibleItems[visibleIndex]->sectionItem()), lvwph->m_firstVisibleIndex + visibleIndex);
tests/plugins/Dash/tst_CardAttributes.qmlnew file+54
@@ -0,0 +1,54 @@
1+/*
2+ * Copyright 2013 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import QtTest 1.0
19+import Dash 0.1
20+
21+Item {
22+ width: units.gu(40)
23+ height: units.gu(4.5)
24+
25+ property var testData: [
26+ [{"value":"text1","icon":"image://theme/ok"},{"value":"text2","icon":"image://theme/cancel"}],
27+ [{"value":"text1","icon":"image://theme/ok"},{"value":"text2","icon":"image://theme/cancel"},{"value":"text3"}],
28+ [{"value":"text1","icon":"image://theme/ok"},{"value":"text2","icon":"image://theme/cancel"},{"value":"text3"},{"value":"text4"}],
29+ [{"value":"text1","icon":"image://theme/ok"},{"value":"text2","icon":"image://theme/cancel"},{"value":"text3","style":"highlighted"},{"value":"text4","icon":"image://theme/close","style":"highlighted"},{"value":"text5"}]
30+ ]
31+
32+ CardAttributes {
33+ id: cardAttributes
34+ model: testData[3]
35+ clip: true
36+ }
37+
38+ TestCase {
39+ name: "CardAttributesTest"
40+ when: windowShown
41+
42+ function init() {
43+ }
44+
45+ function test_columns_data() {
46+ return testData;
47+ }
48+
49+ function test_columns(data) {
50+ cardAttributes.model = data;
51+ compare(cardAttributes.columns, 2 + data.length % 2);
52+ }
53+ }
54+}
tests/qmltests/Components/tst_PageHeader.qml+36−6
@@ -36,6 +36,16 @@ Item {
3636
3737 function init() {
3838 searchEnabled = true;
39+
40+ // Reset to initial state
41+ pageHeader.searchQuery = "";
42+ pageHeader.closePopup();
43+ pageHeader.searchHistory.clear();
44+
45+ // Check initial state
46+ var headerContainer = findChild(pageHeader, "headerContainer");
47+ tryCompareFunction(function() { return headerContainer.popover === null; }, true);
48+ compare(pageHeader.searchHistory.count, 0);
3949 }
4050
4151 function test_search_disabled() {
@@ -80,9 +90,6 @@ Item {
8090 }
8191
8292 function test_history() {
83− pageHeader.searchHistory.clear()
84− compare(pageHeader.searchHistory.count, 0)
85−
8693 pageHeader.triggerSearch()
8794 typeString("humppa1")
8895 pageHeader.resetSearch()
@@ -166,16 +173,26 @@ Item {
166173 tryCompareFunction(function() { return headerContainer.popover !== null; }, true);
167174
168175 tryCompare(headerContainer.popover, "visible", true);
176+
177+ var searchTextField = findChild(pageHeader, "searchTextField");
178+ compare(searchTextField.focus, true);
179+
180+ var recentSearches = findChild(headerContainer.popover, "recentSearches");
181+ mouseClick(recentSearches.itemAt(0), 0, 0);
182+
183+ compare(pageHeader.searchQuery, "Search2");
184+ tryCompareFunction(function() { return headerContainer.popover === null; }, true);
185+ compare(searchTextField.focus, false);
169186 }
170187
171− function test_tap_outside_closes_popup_data() {
188+ function test_popup_closing_data() {
172189 return [
173190 { tag: "with search text", searchText: "foobar", hideSearch: false },
174191 { tag: "without search text", searchText: "", hideSearch: true }
175192 ];
176193 }
177194
178− function test_tap_outside_closes_popup(data) {
195+ function test_popup_closing(data) {
179196 searchEnabled = true;
180197 pageHeader.searchHistory.clear();
181198
@@ -191,13 +208,26 @@ Item {
191208
192209 pageHeader.searchQuery = data.searchText;
193210
194− mouseClick(root, root.width / 2, root.height - 1);
211+ if (data.searchText == "") {
212+ // When the text is empty the user can also close the
213+ // popup by clicking outside the header instead of by starting a search
214+ mouseClick(root, root.width / 2, root.height - 1);
215+ }
195216
196217 tryCompare(headerContainer, "showSearch", !data.hideSearch);
197218 tryCompareFunction(function() { return headerContainer.popover === null; }, true);
198219
199220 pageHeader.resetSearch();
200221 }
222+
223+ function test_search_change_shows_search() {
224+ var headerContainer = findChild(pageHeader, "headerContainer");
225+ compare(headerContainer.showSearch, false);
226+ compare(searchQuery, "");
227+
228+ searchQuery = "H";
229+ compare(headerContainer.showSearch, true);
230+ }
201231 }
202232
203233 Column {
tests/qmltests/Dash/CardHelpers.js+5−3
@@ -16,7 +16,7 @@
1616
1717.pragma library
1818
19−var components = ["title", "art", "subtitle", "mascot", "emblem", "old-price", "price", "alt-price", "rating", "alt-rating", "summary"]
19+var components = ["title", "art", "subtitle", "mascot", "emblem", "old-price", "price", "alt-price", "rating", "alt-rating", "summary", "attributes"]
2020
2121var defaultLayout = ' \
2222{ \
@@ -48,7 +48,8 @@ var defaultLayout = ' \
4848 "empty": "image://theme/rating-star-empty" \
4949 }, \
5050 "alt-rating": null, \
51− "summary": null \
51+ "summary": null, \
52+ "attributes": { "max-count": 2 } \
5253 }, \
5354 "resources": {} \
5455}'
@@ -59,7 +60,8 @@ var fullMapping = ' \
5960 "art": "art", \
6061 "subtitle": "subtitle", \
6162 "mascot": "mascot", \
62− "summary": "summary" \
63+ "summary": "summary", \
64+ "attributes": "attributes" \
6365}'
6466
6567
tests/qmltests/Dash/tst_Card.qml+4−2
@@ -33,7 +33,8 @@ Rectangle {
3333 "mascot": "../../../tests/qmltests/Dash/artwork/avatar.png",
3434 "title": "foo",
3535 "subtitle": "bar",
36− "summary": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
36+ "summary": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
37+ "attributes": [{"value":"text1","icon":"image://theme/ok"},{"value":"text2","icon":"image://theme/cancel"}]
3738 }'
3839
3940 property var cardsModel: [
@@ -110,6 +111,7 @@ Rectangle {
110111 anchors { top: parent.top; left: parent.left; margins: units.gu(1) }
111112
112113 sourceComponent: cardTool.cardComponent
114+ clip: true
113115 onLoaded: {
114116 item.template = Qt.binding(function() { return cardTool.template; });
115117 item.components = Qt.binding(function() { return cardTool.components; });
@@ -359,7 +361,7 @@ Rectangle {
359361
360362 waitForRendering(card);
361363
362− tryCompareFunction(function() { return testCase.summary.y === data.top() }, true);
364+ tryCompareFunction(function() { return art.height > 0 && testCase.summary.y === data.top() }, true);
363365 }
364366
365367 function test_art_visibility() {
tests/qmltests/Greeter/tst_Lockscreen.qml+11−5
@@ -18,8 +18,9 @@ import QtQuick 2.0
1818import QtTest 1.0
1919import ".."
2020import "../../../qml/Components"
21−import Ubuntu.Components 0.1
21+import AccountsService 0.1
2222import LightDM 0.1 as LightDM
23+import Ubuntu.Components 0.1
2324import Unity.Test 0.1 as UT
2425
2526Rectangle {
@@ -53,11 +54,16 @@ Rectangle {
5354 }
5455 }
5556
57+ function setUser(username) {
58+ AccountsService.user = username
59+ LightDM.Greeter.authenticate(username)
60+ }
61+
5662 Connections {
5763 target: LightDM.Greeter
5864
5965 onShowPrompt: {
60− if (text === "PIN") {
66+ if (AccountsService.passwordDisplayHint === AccountsService.Numeric) {
6167 pinPadCheckBox.checked = false
6268 } else {
6369 pinPadCheckBox.checked = true
@@ -139,12 +145,12 @@ Rectangle {
139145 Button {
140146 text: "start auth (1234)"
141147 width: parent.width
142− onClicked: LightDM.Greeter.authenticate("has-pin")
148+ onClicked: setUser("has-pin")
143149 }
144150 Button {
145151 text: "start auth (password)"
146152 width: parent.width
147− onClicked: LightDM.Greeter.authenticate("has-password")
153+ onClicked: setUser("has-password")
148154 }
149155
150156 TextField {
@@ -255,7 +261,7 @@ Rectangle {
255261 enteredLabel.text = ""
256262 minPinLengthTextField.text = data.minPinLength
257263 maxPinLengthTextField.text = data.maxPinLength
258− LightDM.Greeter.authenticate(data.username)
264+ setUser(data.username)
259265 waitForLockscreenReady();
260266
261267 var inputField = findChild(lockscreen, "pinentryField")