on xenial help sign in
Merge.
0470a26Michał Sawicz
CMakeLists.txt+1
@@ -96,6 +96,7 @@ include(autopilot)
96declare_autopilot_test(shell unity8.shell ${CMAKE_SOURCE_DIR}/tests/autopilot/)
96declare_autopilot_test(shell unity8.shell ${CMAKE_SOURCE_DIR}/tests/autopilot/)
97
97
98set(SHELL_APP unity8)
98set(SHELL_APP unity8)
99+set(DASH_APP unity8-dash)
99set(SCOPE_TOOL unity-scope-tool)
100set(SCOPE_TOOL unity-scope-tool)
100
101
101include_directories(
102include_directories(
cmake/modules/QmlTest.cmake+1−1
@@ -135,7 +135,7 @@ macro(add_binary_qml_test CLASS_NAME LD_PATH DEPS)
135 set(testCommand
135 set(testCommand
136 LD_LIBRARY_PATH=${LD_PATH}
136 LD_LIBRARY_PATH=${LD_PATH}
137 ${CMAKE_CURRENT_BINARY_DIR}/${CLASS_NAME}TestExec
137 ${CMAKE_CURRENT_BINARY_DIR}/${CLASS_NAME}TestExec
138− -o ${CMAKE_BINARY_DIR}/${CLASSNAME}Test.xml,xunitxml
138+ -o ${CMAKE_BINARY_DIR}/${CLASS_NAME}Test.xml,xunitxml
139 -o -,txt)
139 -o -,txt)
140
140
141 add_qmltest_target(test${CLASS_NAME} "${testCommand}" FALSE TRUE)
141 add_qmltest_target(test${CLASS_NAME} "${testCommand}" FALSE TRUE)
data/CMakeLists.txt+5−2
@@ -1,7 +1,10 @@
1−# generate desktop file
1+# generate desktop files
2configure_file(${SHELL_APP}.desktop.in ${CMAKE_CURRENT_BINARY_DIR}/${SHELL_APP}.desktop @ONLY)
2configure_file(${SHELL_APP}.desktop.in ${CMAKE_CURRENT_BINARY_DIR}/${SHELL_APP}.desktop @ONLY)
3+configure_file(${DASH_APP}.desktop.in ${CMAKE_CURRENT_BINARY_DIR}/${DASH_APP}.desktop @ONLY)
3
4
4# install desktop files
5# install desktop files
6+install(FILES
5−install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${SHELL_APP}.desktop
7+ ${CMAKE_CURRENT_BINARY_DIR}/${SHELL_APP}.desktop
8+ ${CMAKE_CURRENT_BINARY_DIR}/${DASH_APP}.desktop
6 DESTINATION ${CMAKE_INSTALL_DATADIR}/applications
9 DESTINATION ${CMAKE_INSTALL_DATADIR}/applications
7 )
10 )
data/unity8-dash.confnew file+22
@@ -0,0 +1,22 @@
1+description "Unity Shell v8 Dash"
2+author "Michael Zanetti <michael.zanetti@canonical.com>"
3+
4+emits scope-ui-starting
5+
6+start on started unity8
7+stop on stopping unity8
8+
9+kill timeout 120
10+
11+respawn
12+
13+pre-start script
14+ if [ -z "$UNITY_SCOPES_LIST" ]; then
15+ # FIXME: remove once we have this in dconf
16+ initctl set-env UNITY_SCOPES_LIST="scopes;clickscope;musicaggregator;videoaggregator"
17+ fi
18+
19+ initctl emit scope-ui-starting
20+end script
21+
22+exec ${BINARY:-unity8-dash} $ARGS --desktop_file_hint=/usr/share/applications/unity8-dash.desktop
data/unity8-dash.desktop.innew file+9
@@ -0,0 +1,9 @@
1+[Desktop Entry]
2+Type=Application
3+Name=Unity 8 Dash
4+Comment=The converged Unity shell dash
5+Exec=@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_BINDIR@/unity8-dash
6+Terminal=false
7+Icon=unity8
8+NoDisplay=true
9+X-Ubuntu-Touch=true
data/unity8.conf+1−7
@@ -1,7 +1,7 @@
1description "Unity Shell v8"
1description "Unity Shell v8"
2author "Ricardo Mendoza <ricmm@ubuntu.com>"
2author "Ricardo Mendoza <ricmm@ubuntu.com>"
3
3
4−emits scope-ui-starting indicator-services-start
4+emits indicator-services-start
5
5
6start on ((xsession SESSION=ubuntu-touch) or (xsession SESSION=ubuntu-touch-surfaceflinger)) and started dbus
6start on ((xsession SESSION=ubuntu-touch) or (xsession SESSION=ubuntu-touch-surfaceflinger)) and started dbus
7stop on desktop-end
7stop on desktop-end
@@ -34,11 +34,6 @@ pre-start script
34 gdbus call --session --dest org.freedesktop.DBus --object-path /org/freedesktop/DBus --method org.freedesktop.DBus.UpdateActivationEnvironment "@a{ss} {'MIR_SOCKET': '$MIR_SERVER_FILE'}"
34 gdbus call --session --dest org.freedesktop.DBus --object-path /org/freedesktop/DBus --method org.freedesktop.DBus.UpdateActivationEnvironment "@a{ss} {'MIR_SOCKET': '$MIR_SERVER_FILE'}"
35 fi
35 fi
36
36
37− if [ -z "$UNITY_SCOPES_LIST" ]; then
38− # FIXME: remove once we have this in dconf
39− initctl set-env UNITY_SCOPES_LIST="scopes;clickscope;musicaggregator;videoaggregator"
40− fi
41−
42 # Remove the socket if still there
37 # Remove the socket if still there
43 if [ -S "$MIR_SERVER_FILE" ]; then
38 if [ -S "$MIR_SERVER_FILE" ]; then
44 rm "$MIR_SERVER_FILE"
39 rm "$MIR_SERVER_FILE"
@@ -46,7 +41,6 @@ pre-start script
46
41
47 initctl set-env --global MIR_SERVER_PROMPT_FILE=1
42 initctl set-env --global MIR_SERVER_PROMPT_FILE=1
48
43
49− initctl emit scope-ui-starting
50 initctl emit --no-wait indicator-services-start
44 initctl emit --no-wait indicator-services-start
51end script
45end script
52
46
debian/changelog+58
@@ -1,3 +1,61 @@
1+unity8 (8.00+14.10.20140805-0ubuntu1) utopic; urgency=low
2+
3+ [ Michael Zanetti ]
4+ * Split the dash from the shell into a separate app (LP: #1232687)
5+
6+ [ Leo Arias ]
7+ * Update the autopilot tests to work with the new dash app.
8+
9+ [ Daniel d'Andrada ]
10+ * Split the dash from the shell into a separate app (LP: #1232687)
11+
12+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Tue, 05 Aug 2014 12:06:31 +0000
13+
14+unity8 (8.00+14.10.20140731.1-0ubuntu1) utopic; urgency=low
15+
16+ [ Gerry Boland ]
17+ * Fix the run.sh script - pretend to be running with qtmir and emit
18+ SIGSTOP at the right time
19+
20+ [ Ying-Chun Liu ]
21+ * Implement Attribute UI. (LP: #1282460)
22+
23+ [ Albert Astals ]
24+ * Hide search history popup as soon as you start typing As discussed
25+ with Mike and Saviq
26+ * Compile with for scopes-v3 unity-api
27+ * PageHeader: Unfocus search field when search entry is selected
28+ * Show search field if the search query changes
29+ * Test: Add a condition for art.height being > 0 means stuff has
30+ already been layouted a bit without it it can happen that we get 0
31+ for everything at startup and tests still pass
32+ * Remove leftover in test of an old headerless implementation
33+
34+ [ Michael Zanetti ]
35+ * Drop Recent apps category from Dash (LP: #1281092)
36+ * update launcher count emblems to match new spec (LP: #1338984)
37+
38+ [ Bill Filler ]
39+ * disable predictive text for dash search field (LP: #1340409)
40+
41+ [ CI bot ]
42+ * Resync trunk
43+
44+ [ Antti Kaijanmäki ]
45+ * DefaultIndicatorPage: use Loader status to determine the visible
46+ property. (LP: #1350555)
47+
48+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Thu, 31 Jul 2014 16:51:01 +0000
49+
50+unity8 (8.00+14.10.20140729.1-0ubuntu1) utopic; urgency=low
51+
52+ [ Michael Terry ]
53+ * Check user's pin/password using PAM, instead of a plaintext keyfile.
54+ New build dependency: libpam0g-dev for phone unlock with PAM (LP:
55+ #1234983)
56+
57+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Tue, 29 Jul 2014 23:36:30 +0000
58+
1unity8 (8.00+14.10.20140729-0ubuntu1) utopic; urgency=medium
59unity8 (8.00+14.10.20140729-0ubuntu1) utopic; urgency=medium
2
60
3 [ Gerry Boland ]
61 [ Gerry Boland ]
debian/control+2−1
@@ -19,11 +19,12 @@ Build-Depends: cmake,
19 libgsettings-qt-dev,
19 libgsettings-qt-dev,
20 libhardware-dev,
20 libhardware-dev,
21 libhud-client2-dev,
21 libhud-client2-dev,
22+ libpam0g-dev,
22 libpay2-dev,
23 libpay2-dev,
23 libpulse-dev,
24 libpulse-dev,
24 libqmenumodel-dev (>= 0.2.8),
25 libqmenumodel-dev (>= 0.2.8),
25 libqt5xmlpatterns5-dev,
26 libqt5xmlpatterns5-dev,
26− libunity-api-dev (>= 7.85),
27+ libunity-api-dev (>= 7.87),
27 libusermetricsoutput1-dev,
28 libusermetricsoutput1-dev,
28 libxcb1-dev,
29 libxcb1-dev,
29 pkg-config,
30 pkg-config,
debian/unity8.install+3
@@ -1,6 +1,9 @@
1data/unity8.conf usr/share/upstart/sessions/
1data/unity8.conf usr/share/upstart/sessions/
2+data/unity8-dash.conf usr/share/upstart/sessions/
2usr/bin/unity8
3usr/bin/unity8
4+usr/bin/unity8-dash
3usr/share/applications/unity8.desktop
5usr/share/applications/unity8.desktop
6+usr/share/applications/unity8-dash.desktop
4usr/share/unity8/Greeter
7usr/share/unity8/Greeter
5usr/share/unity8/Hud
8usr/share/unity8/Hud
6usr/share/unity8/Launcher
9usr/share/unity8/Launcher
plugins/AccountsService/AccountsService.cpp+21−1
@@ -26,7 +26,8 @@ AccountsService::AccountsService(QObject* parent)
26 m_service(new AccountsServiceDBusAdaptor(this)),
26 m_service(new AccountsServiceDBusAdaptor(this)),
27 m_user(qgetenv("USER")),
27 m_user(qgetenv("USER")),
28 m_demoEdges(false),
28 m_demoEdges(false),
29− m_statsWelcomeScreen(false)
29+ m_statsWelcomeScreen(false),
30+ m_passwordDisplayHint(Keyboard)
30{
31{
31 connect(m_service, SIGNAL(propertiesChanged(const QString &, const QString &, const QStringList &)),
32 connect(m_service, SIGNAL(propertiesChanged(const QString &, const QString &, const QStringList &)),
32 this, SLOT(propertiesChanged(const QString &, const QString &, const QStringList &)));
33 this, SLOT(propertiesChanged(const QString &, const QString &, const QStringList &)));
@@ -47,6 +48,7 @@ void AccountsService::setUser(const QString &user)
47 updateDemoEdges();
48 updateDemoEdges();
48 updateBackgroundFile();
49 updateBackgroundFile();
49 updateStatsWelcomeScreen();
50 updateStatsWelcomeScreen();
51+ updatePasswordDisplayHint();
50}
52}
51
53
52bool AccountsService::demoEdges() const
54bool AccountsService::demoEdges() const
@@ -70,6 +72,11 @@ bool AccountsService::statsWelcomeScreen() const
70 return m_statsWelcomeScreen;
72 return m_statsWelcomeScreen;
71}
73}
72
74
75+AccountsService::PasswordDisplayHint AccountsService::passwordDisplayHint() const
76+{
77+ return m_passwordDisplayHint;
78+}
79+
73void AccountsService::updateDemoEdges()
80void AccountsService::updateDemoEdges()
74{
81{
75 auto demoEdges = m_service->getUserProperty(m_user, "com.canonical.unity.AccountsService", "demo-edges").toBool();
82 auto demoEdges = m_service->getUserProperty(m_user, "com.canonical.unity.AccountsService", "demo-edges").toBool();
@@ -97,6 +104,15 @@ void AccountsService::updateStatsWelcomeScreen()
97 }
104 }
98}
105}
99
106
107+void AccountsService::updatePasswordDisplayHint()
108+{
109+ PasswordDisplayHint passwordDisplayHint = (PasswordDisplayHint)m_service->getUserProperty(m_user, "com.ubuntu.AccountsService.SecurityPrivacy", "PasswordDisplayHint").toInt();
110+ if (m_passwordDisplayHint != passwordDisplayHint) {
111+ m_passwordDisplayHint = passwordDisplayHint;
112+ Q_EMIT passwordDisplayHintChanged();
113+ }
114+}
115+
100void AccountsService::propertiesChanged(const QString &user, const QString &interface, const QStringList &changed)
116void AccountsService::propertiesChanged(const QString &user, const QString &interface, const QStringList &changed)
101{
117{
102 if (m_user != user) {
118 if (m_user != user) {
@@ -111,6 +127,10 @@ void AccountsService::propertiesChanged(const QString &user, const QString &inte
111 if (changed.contains("StatsWelcomeScreen")) {
127 if (changed.contains("StatsWelcomeScreen")) {
112 updateStatsWelcomeScreen();
128 updateStatsWelcomeScreen();
113 }
129 }
130+ } else if (interface == "com.ubuntu.AccountsService.SecurityPrivacy") {
131+ if (changed.contains("PasswordDisplayHint")) {
132+ updatePasswordDisplayHint();
133+ }
114 }
134 }
115}
135}
116
136
plugins/AccountsService/AccountsService.h+13
@@ -27,6 +27,7 @@ class AccountsServiceDBusAdaptor;
27class AccountsService: public QObject
27class AccountsService: public QObject
28{
28{
29 Q_OBJECT
29 Q_OBJECT
30+ Q_ENUMS(PasswordDisplayHint)
30 Q_PROPERTY (QString user
31 Q_PROPERTY (QString user
31 READ user
32 READ user
32 WRITE setUser
33 WRITE setUser
@@ -41,8 +42,16 @@ class AccountsService: public QObject
41 Q_PROPERTY (bool statsWelcomeScreen
42 Q_PROPERTY (bool statsWelcomeScreen
42 READ statsWelcomeScreen
43 READ statsWelcomeScreen
43 NOTIFY statsWelcomeScreenChanged)
44 NOTIFY statsWelcomeScreenChanged)
45+ Q_PROPERTY (PasswordDisplayHint passwordDisplayHint
46+ READ passwordDisplayHint
47+ NOTIFY passwordDisplayHintChanged)
44
48
45public:
49public:
50+ enum PasswordDisplayHint {
51+ Keyboard,
52+ Numeric,
53+ };
54+
46 explicit AccountsService(QObject *parent = 0);
55 explicit AccountsService(QObject *parent = 0);
47
56
48 QString user() const;
57 QString user() const;
@@ -51,12 +60,14 @@ public:
51 void setDemoEdges(bool demoEdges);
60 void setDemoEdges(bool demoEdges);
52 QString backgroundFile() const;
61 QString backgroundFile() const;
53 bool statsWelcomeScreen() const;
62 bool statsWelcomeScreen() const;
63+ PasswordDisplayHint passwordDisplayHint() const;
54
64
55Q_SIGNALS:
65Q_SIGNALS:
56 void userChanged();
66 void userChanged();
57 void demoEdgesChanged();
67 void demoEdgesChanged();
58 void backgroundFileChanged();
68 void backgroundFileChanged();
59 void statsWelcomeScreenChanged();
69 void statsWelcomeScreenChanged();
70+ void passwordDisplayHintChanged();
60
71
61private Q_SLOTS:
72private Q_SLOTS:
62 void propertiesChanged(const QString &user, const QString &interface, const QStringList &changed);
73 void propertiesChanged(const QString &user, const QString &interface, const QStringList &changed);
@@ -66,12 +77,14 @@ private:
66 void updateDemoEdges();
77 void updateDemoEdges();
67 void updateBackgroundFile();
78 void updateBackgroundFile();
68 void updateStatsWelcomeScreen();
79 void updateStatsWelcomeScreen();
80+ void updatePasswordDisplayHint();
69
81
70 AccountsServiceDBusAdaptor *m_service;
82 AccountsServiceDBusAdaptor *m_service;
71 QString m_user;
83 QString m_user;
72 bool m_demoEdges;
84 bool m_demoEdges;
73 QString m_backgroundFile;
85 QString m_backgroundFile;
74 bool m_statsWelcomeScreen;
86 bool m_statsWelcomeScreen;
87+ PasswordDisplayHint m_passwordDisplayHint;
75};
88};
76
89
77#endif
90#endif
plugins/AccountsService/plugin.cpp+1
@@ -33,5 +33,6 @@ void AccountsServicePlugin::registerTypes(const char *uri)
33{
33{
34 Q_ASSERT(uri == QLatin1String("AccountsService"));
34 Q_ASSERT(uri == QLatin1String("AccountsService"));
35 qDBusRegisterMetaType<QList<QVariantMap>>();
35 qDBusRegisterMetaType<QList<QVariantMap>>();
36+ qRegisterMetaType<AccountsService::PasswordDisplayHint>("AccountsService::PasswordDisplayHint");
36 qmlRegisterSingletonType<AccountsService>(uri, 0, 1, "AccountsService", service_provider);
37 qmlRegisterSingletonType<AccountsService>(uri, 0, 1, "AccountsService", service_provider);
37}
38}
plugins/Dash/CardAttributes.qmlnew file+65
@@ -0,0 +1,65 @@
1+/*
2+ * Copyright 2014 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.2
18+import QtQuick.Layouts 1.1
19+import Ubuntu.Components 1.1
20+import Ubuntu.Settings.Components 0.1
21+
22+GridLayout {
23+ id: grid
24+ anchors {
25+ left: parent.left;
26+ right: parent.right;
27+ }
28+ columns: 2 + repeater.count % 2
29+ property alias model: repeater.model
30+ property var color
31+
32+ Repeater {
33+ id: repeater
34+ delegate: Row {
35+ spacing: units.gu(0.5)
36+ readonly property int column: index % grid.columns;
37+ Layout.alignment: {
38+ if (column == 0) return Qt.AlignLeft;
39+ if (column == grid.columns - 1 || index == repeater.count - 1) return Qt.AlignRight;
40+ if (column == 1) return Qt.AlignHCenter;
41+ }
42+ Layout.columnSpan: index == repeater.count - 1 && grid.columns == 3 && column == 1 ? 2 : 1
43+ Layout.maximumWidth: Math.max(icon.width, label.x + label.implicitWidth)
44+ Layout.fillWidth: true
45+ StatusIcon {
46+ id: icon
47+ height: units.gu(2)
48+ sets: ["actions", "status", "apps"]
49+ source: "icon" in modelData ? modelData["icon"] : ""
50+ }
51+ Label {
52+ id: label
53+ width: parent.width - x
54+ anchors.verticalCenter: parent.verticalCenter
55+ text: "value" in modelData ? modelData["value"] : "";
56+ elide: Text.ElideRight
57+ maximumLineCount: 1
58+ font.weight: "style" in modelData && modelData["style"] == "highlighted" ? Font.DemiBold : Font.Light
59+ fontSize: "small"
60+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale)
61+ color: grid.color
62+ }
63+ }
64+ }
65+}
plugins/Dash/CardCreator.js+76−7
@@ -193,6 +193,21 @@ var kHeaderColumnCode = 'Column { \n\
193 ] \n\
193 ] \n\
194 }\n';
194 }\n';
195
195
196+// multiple column version of kHeaderColumnCode.
197+function kHeaderColumnCodeGenerator() {
198+ var headerColumnCodeTemplate = 'Column { \n\
199+ anchors.verticalCenter: parent.verticalCenter; \n\
200+ spacing: units.dp(2); \n\
201+ width: parent.width - x;\n\
202+ data: [ \n\
203+ %1 \n\
204+ ]\n\
205+ }\n';
206+ var args = Array.prototype.slice.call(arguments);
207+ var code = headerColumnCodeTemplate.arg(args.join(',\n'));
208+ return code;
209+}
210+
196// %1 is used as anchors of mascotShapeLoader
211// %1 is used as anchors of mascotShapeLoader
197var kMascotShapeLoaderCode = 'Loader { \n\
212var kMascotShapeLoaderCode = 'Loader { \n\
198 id: mascotShapeLoader; \n\
213 id: mascotShapeLoader; \n\
@@ -224,7 +239,7 @@ var kMascotImageCode = 'Image { \n\
224 }\n';
239 }\n';
225
240
226// %1 is used as anchors of titleLabel
241// %1 is used as anchors of titleLabel
227−// %1 is used as color of titleLabel
242+// %2 is used as color of titleLabel
228// %3 is used as extra condition for visible of titleLabel
243// %3 is used as extra condition for visible of titleLabel
229var kTitleLabelCode = 'Label { \n\
244var kTitleLabelCode = 'Label { \n\
230 id: titleLabel; \n\
245 id: titleLabel; \n\
@@ -258,6 +273,15 @@ var kSubtitleLabelCode = 'Label { \n\
258 horizontalAlignment: root.headerAlignment; \n\
273 horizontalAlignment: root.headerAlignment; \n\
259 }\n';
274 }\n';
260
275
276+// %1 is used as anchors of attributesRow
277+var kAttributesRowCode = 'CardAttributes { \n\
278+ id: attributesRow; \n\
279+ objectName: "attributesRow"; \n\
280+ anchors { %1 } \n\
281+ color: %2; \n\
282+ model: cardData && cardData["attributes"] || undefined; \n\
283+ }\n';
284+
261// %1 is used as top anchor of summary
285// %1 is used as top anchor of summary
262// %2 is used as topMargin anchor of summary
286// %2 is used as topMargin anchor of summary
263// %3 is used as color of summary
287// %3 is used as color of summary
@@ -308,6 +332,7 @@ function cardString(template, components) {
308 var headerAsOverlay = hasArt && template && template["overlay"] === true && (hasTitle || hasMascot);
332 var headerAsOverlay = hasArt && template && template["overlay"] === true && (hasTitle || hasMascot);
309 var hasSubtitle = hasTitle && components["subtitle"] || false;
333 var hasSubtitle = hasTitle && components["subtitle"] || false;
310 var hasHeaderRow = hasMascot && hasTitle;
334 var hasHeaderRow = hasMascot && hasTitle;
335+ var hasAttributes = hasTitle && components["attributes"] || false;
311
336
312 if (hasBackground) {
337 if (hasBackground) {
313 code += kBackgroundLoaderCode;
338 code += kBackgroundLoaderCode;
@@ -377,6 +402,14 @@ function cardString(template, components) {
377 code += 'readonly property int headerHeight: row.height;\n'
402 code += 'readonly property int headerHeight: row.height;\n'
378 } else if (hasMascot) {
403 } else if (hasMascot) {
379 code += 'readonly property int headerHeight: mascotImage.height;\n'
404 code += 'readonly property int headerHeight: mascotImage.height;\n'
405+ } else if (hasAttributes) {
406+ if (hasTitle && hasSubtitle) {
407+ code += 'readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin + attributesRow.height + attributesRow.anchors.topMargin;\n'
408+ } else if (hasTitle) {
409+ code += 'readonly property int headerHeight: titleLabel.height + attributesRow.height + attributesRow.anchors.topMargin;\n'
410+ } else {
411+ code += 'readonly property int headerHeight: attributesRow.height;\n'
412+ }
380 } else if (hasSubtitle) {
413 } else if (hasSubtitle) {
381 code += 'readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;\n'
414 code += 'readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;\n'
382 } else if (hasTitle) {
415 } else if (hasTitle) {
@@ -425,10 +458,12 @@ function cardString(template, components) {
425
458
426 var titleAnchors;
459 var titleAnchors;
427 var subtitleAnchors;
460 var subtitleAnchors;
461+ var attributesAnchors;
428− if (hasMascot && hasSubtitle) {
462+ if (hasMascot && (hasSubtitle || hasAttributes)) {
429 // Using row + column
463 // Using row + column
430 titleAnchors = 'left: parent.left; right: parent.right';
464 titleAnchors = 'left: parent.left; right: parent.right';
431 subtitleAnchors = titleAnchors;
465 subtitleAnchors = titleAnchors;
466+ attributesAnchors = subtitleAnchors;
432 } else if (hasMascot) {
467 } else if (hasMascot) {
433 // Using row + label
468 // Using row + label
434 titleAnchors = 'verticalCenter: parent.verticalCenter;\n'
469 titleAnchors = 'verticalCenter: parent.verticalCenter;\n'
@@ -438,11 +473,13 @@ function cardString(template, components) {
438 titleAnchors = 'left: parent.left; \n\
473 titleAnchors = 'left: parent.left; \n\
439 leftMargin: units.gu(1); \n\
474 leftMargin: units.gu(1); \n\
440 right: parent.right; \n\
475 right: parent.right; \n\
476+ rightMargin: units.gu(1); \n\
441 top: overlayLoader.top; \n\
477 top: overlayLoader.top; \n\
442 topMargin: units.gu(1);\n';
478 topMargin: units.gu(1);\n';
443 } else {
479 } else {
444 // Using anchors to the mascot/parent
480 // Using anchors to the mascot/parent
445− titleAnchors = "right: parent.right;";
481+ titleAnchors = "right: parent.right;\n";
482+ titleAnchors += "rightMargin: units.gu(1);\n";
446 titleAnchors += headerLeftAnchor;
483 titleAnchors += headerLeftAnchor;
447 titleAnchors += headerVerticalAnchors;
484 titleAnchors += headerVerticalAnchors;
448 if (!headerLeftAnchorHasMargin) {
485 if (!headerLeftAnchorHasMargin) {
@@ -452,8 +489,19 @@ function cardString(template, components) {
452 subtitleAnchors = 'left: titleLabel.left; \n\
489 subtitleAnchors = 'left: titleLabel.left; \n\
453 leftMargin: titleLabel.leftMargin; \n\
490 leftMargin: titleLabel.leftMargin; \n\
454 right: titleLabel.right; \n\
491 right: titleLabel.right; \n\
492+ rightMargin: titleLabel.rightMargin; \n\
455 top: titleLabel.bottom; \n\
493 top: titleLabel.bottom; \n\
456 topMargin: units.dp(2);\n';
494 topMargin: units.dp(2);\n';
495+ if (hasSubtitle) {
496+ attributesAnchors = 'left: subtitleLabel.left; \n\
497+ leftMargin: subtitleLabel.leftMargin; \n\
498+ right: subtitleLabel.right; \n\
499+ rightMargin: subtitleLabel.rightMargin; \n\
500+ top: subtitleLabel.bottom; \n\
501+ topMargin: units.dp(2);\n';
502+ } else {
503+ attributesAnchors = subtitleAnchors;
504+ }
457 }
505 }
458
506
459 var titleLabelVisibleExtra = (headerAsOverlay ? '&& overlayLoader.active': '');
507 var titleLabelVisibleExtra = (headerAsOverlay ? '&& overlayLoader.active': '');
@@ -463,11 +511,27 @@ function cardString(template, components) {
463 subtitleCode += kSubtitleLabelCode.arg(subtitleAnchors).arg(color);
511 subtitleCode += kSubtitleLabelCode.arg(subtitleAnchors).arg(color);
464 }
512 }
465
513
466− if (hasMascot && hasSubtitle) {
514+ if (hasMascot && (hasSubtitle || hasAttributes)) {
467 // If using row + column wrap the code in the column
515 // If using row + column wrap the code in the column
468 titleSubtitleCode = kHeaderColumnCode.arg(titleCode).arg(subtitleCode);
516 titleSubtitleCode = kHeaderColumnCode.arg(titleCode).arg(subtitleCode);
517+ if (hasSubtitle && hasAttributes) {
518+ var attributesCode = kAttributesRowCode.arg(attributesAnchors).arg(color);
519+ titleSubtitleCode = kHeaderColumnCodeGenerator(titleCode, subtitleCode, attributesCode);
520+ } else if (hasSubtitle) {
521+ titleSubtitleCode = kHeaderColumnCode.arg(titleCode).arg(subtitleCode);
522+ } else if (hasAttributes) {
523+ var attributesCode = kAttributesRowCode.arg(attributesAnchors).arg(color);
524+ titleSubtitleCode = kHeaderColumnCode.arg(titleCode).arg(attributesCode);
525+ }
469 } else {
526 } else {
470− titleSubtitleCode = titleCode + subtitleCode;
527+ titleSubtitleCode = titleCode;
528+ if (hasSubtitle) {
529+ titleSubtitleCode = titleSubtitleCode + subtitleCode;
530+ }
531+ if (hasAttributes) {
532+ var attributesCode = kAttributesRowCode.arg(attributesAnchors).arg(color);
533+ titleSubtitleCode = titleSubtitleCode + attributesCode;
534+ }
471 }
535 }
472 }
536 }
473
537
@@ -487,6 +551,7 @@ function cardString(template, components) {
487 else if (headerAsOverlay && hasArt) summaryTopAnchor = "artShapeHolder.bottom";
551 else if (headerAsOverlay && hasArt) summaryTopAnchor = "artShapeHolder.bottom";
488 else if (hasHeaderRow) summaryTopAnchor = "row.bottom";
552 else if (hasHeaderRow) summaryTopAnchor = "row.bottom";
489 else if (hasMascot) summaryTopAnchor = "mascotImage.bottom";
553 else if (hasMascot) summaryTopAnchor = "mascotImage.bottom";
554+ else if (hasAttributes) summaryTopAnchor = "attributesRow.bottom";
490 else if (hasSubtitle) summaryTopAnchor = "subtitleLabel.bottom";
555 else if (hasSubtitle) summaryTopAnchor = "subtitleLabel.bottom";
491 else if (hasTitle) summaryTopAnchor = "titleLabel.bottom";
556 else if (hasTitle) summaryTopAnchor = "titleLabel.bottom";
492 else if (hasArt) summaryTopAnchor = "artShapeHolder.bottom";
557 else if (hasArt) summaryTopAnchor = "artShapeHolder.bottom";
@@ -499,7 +564,7 @@ function cardString(template, components) {
499 color = 'root.scopeStyle ? root.scopeStyle.foreground : "grey"';
564 color = 'root.scopeStyle ? root.scopeStyle.foreground : "grey"';
500 }
565 }
501
566
502− var summaryTopMargin = (hasMascot || hasSubtitle ? 'anchors.margins' : '0');
567+ var summaryTopMargin = (hasMascot || hasSubtitle || hasAttributes ? 'anchors.margins' : '0');
503
568
504 code += kSummaryLabelCode.arg(summaryTopAnchor).arg(summaryTopMargin).arg(color);
569 code += kSummaryLabelCode.arg(summaryTopAnchor).arg(summaryTopMargin).arg(color);
505 }
570 }
@@ -510,6 +575,8 @@ function cardString(template, components) {
510 code += 'implicitHeight: row.y + row.height + units.gu(1);\n';
575 code += 'implicitHeight: row.y + row.height + units.gu(1);\n';
511 } else if (hasMascot) {
576 } else if (hasMascot) {
512 code += 'implicitHeight: mascotImage.y + mascotImage.height;\n';
577 code += 'implicitHeight: mascotImage.y + mascotImage.height;\n';
578+ } else if (hasAttributes) {
579+ code += 'implicitHeight: attributesRow.y + attributesRow.height + units.gu(1);\n';
513 } else if (hasSubtitle) {
580 } else if (hasSubtitle) {
514 code += 'implicitHeight: subtitleLabel.y + subtitleLabel.height + units.gu(1);\n';
581 code += 'implicitHeight: subtitleLabel.y + subtitleLabel.height + units.gu(1);\n';
515 } else if (hasTitle) {
582 } else if (hasTitle) {
@@ -526,8 +593,10 @@ function cardString(template, components) {
526function createCardComponent(parent, template, components) {
593function createCardComponent(parent, template, components) {
527 var imports = 'import QtQuick 2.2; \n\
594 var imports = 'import QtQuick 2.2; \n\
528 import Ubuntu.Components 0.1; \n\
595 import Ubuntu.Components 0.1; \n\
529− import Ubuntu.Thumbnailer 0.1;\n';
596+ import Ubuntu.Thumbnailer 0.1;\n\
597+ import Dash 0.1;\n';
530 var card = cardString(template, components);
598 var card = cardString(template, components);
531 var code = imports + 'Component {\n' + card + '}\n';
599 var code = imports + 'Component {\n' + card + '}\n';
600+
532 return Qt.createQmlObject(code, parent, "createCardComponent");
601 return Qt.createQmlObject(code, parent, "createCardComponent");
533}
602}
plugins/Dash/qmldir+1
@@ -3,3 +3,4 @@ plugin Dash-qml
3typeinfo Dash.qmltypes
3typeinfo Dash.qmltypes
4singleton CardCreatorCache 0.1 CardCreatorCache.qml
4singleton CardCreatorCache 0.1 CardCreatorCache.qml
5ScopeStyle 0.1 ScopeStyle.qml
5ScopeStyle 0.1 ScopeStyle.qml
6+CardAttributes 0.1 CardAttributes.qml
plugins/LightDM/plugin.cpp+2
@@ -69,6 +69,8 @@ void LightDMPlugin::registerTypes(const char *uri)
69 qmlRegisterType<UserMetricsOutput::ColorTheme>();
69 qmlRegisterType<UserMetricsOutput::ColorTheme>();
70
70
71 Q_ASSERT(uri == QLatin1String("LightDM"));
71 Q_ASSERT(uri == QLatin1String("LightDM"));
72+ qRegisterMetaType<QLightDM::Greeter::MessageType>("QLightDM::Greeter::MessageType");
73+ qRegisterMetaType<QLightDM::Greeter::PromptType>("QLightDM::Greeter::PromptType");
72 qmlRegisterSingletonType<Greeter>(uri, 0, 1, "Greeter", greeter_provider);
74 qmlRegisterSingletonType<Greeter>(uri, 0, 1, "Greeter", greeter_provider);
73 qmlRegisterSingletonType<UsersModel>(uri, 0, 1, "Users", users_provider);
75 qmlRegisterSingletonType<UsersModel>(uri, 0, 1, "Users", users_provider);
74 qmlRegisterUncreatableType<QLightDM::UsersModel>(uri, 0, 1, "UserRoles", "Type is not instantiable");
76 qmlRegisterUncreatableType<QLightDM::UsersModel>(uri, 0, 1, "UserRoles", "Type is not instantiable");
plugins/Unity/CMakeLists.txt+1
@@ -1,3 +1,4 @@
1add_subdirectory(Indicators)
1add_subdirectory(Indicators)
2add_subdirectory(Launcher)
2add_subdirectory(Launcher)
3add_subdirectory(Session)
3add_subdirectory(Session)
4+add_subdirectory(DashCommunicator)
plugins/Unity/DashCommunicator/CMakeLists.txtnew file+19
@@ -0,0 +1,19 @@
1+include_directories(
2+ ${CMAKE_CURRENT_SOURCE_DIR}
3+)
4+
5+set(QMLDASHCOMMUNICATORPLUGIN_SRC
6+ plugin.cpp
7+ dbusdashcommunicatorservice.cpp
8+ dashcommunicatorservice.cpp
9+ dashcommunicator.cpp
10+ )
11+
12+add_library(DashCommunicator-qml MODULE
13+ ${QMLDASHCOMMUNICATORPLUGIN_SRC}
14+ )
15+
16+qt5_use_modules(DashCommunicator-qml DBus Qml)
17+
18+# export the qmldir and qmltypes files
19+add_unity8_plugin(Unity.DashCommunicator 0.1 Unity/DashCommunicator TARGETS DashCommunicator-qml)
plugins/Unity/DashCommunicator/dashcommunicator.cppnew file+43
@@ -0,0 +1,43 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it under
5+ * the terms of the GNU Lesser General Public License version 3, as published by
6+ * the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but WITHOUT
9+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+ * Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "dashcommunicator.h"
18+
19+#include <QObject>
20+#include <QDBusConnection>
21+#include <QDBusInterface>
22+#include <QDebug>
23+
24+DashCommunicator::DashCommunicator(QObject *parent):
25+ QObject(parent)
26+{
27+}
28+
29+DashCommunicator::~DashCommunicator()
30+{
31+}
32+
33+
34+void DashCommunicator::setCurrentScope(const QString &scopeId, bool animate, bool reset)
35+{
36+ QDBusConnection connection = QDBusConnection::sessionBus();
37+ QDBusInterface dashIface ("com.canonical.UnityDash",
38+ "/com/canonical/UnityDash",
39+ "",
40+ connection);
41+
42+ dashIface.call("SetCurrentScope", scopeId, animate, reset);
43+}
plugins/Unity/DashCommunicator/dashcommunicator.hnew file+35
@@ -0,0 +1,35 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it under
5+ * the terms of the GNU Lesser General Public License version 3, as published by
6+ * the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but WITHOUT
9+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+ * Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef DASHCOMMUNICATOR_H
18+#define DASHCOMMUNICATOR_H
19+
20+#include <QObject>
21+
22+class DashCommunicator: public QObject
23+{
24+ Q_OBJECT
25+ Q_CLASSINFO("D-Bus Interface", "com.canonical.Unity.DashCommunicator")
26+
27+public:
28+ DashCommunicator(QObject *parent = 0);
29+ ~DashCommunicator();
30+
31+public Q_SLOTS:
32+ void setCurrentScope(const QString &scopeId, bool animate, bool reset);
33+};
34+
35+#endif
plugins/Unity/DashCommunicator/dashcommunicatorservice.cppnew file+30
@@ -0,0 +1,30 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it under
5+ * the terms of the GNU Lesser General Public License version 3, as published by
6+ * the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but WITHOUT
9+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+ * Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "dashcommunicatorservice.h"
18+
19+DashCommunicatorService::DashCommunicatorService(QObject *parent):
20+ QObject(parent),
21+ m_dbusService(new DBusDashCommunicatorService(this))
22+{
23+ connect(m_dbusService, &DBusDashCommunicatorService::setCurrentScopeRequested, this, &DashCommunicatorService::setCurrentScopeRequested);
24+}
25+
26+
27+DashCommunicatorService::~DashCommunicatorService()
28+{
29+
30+}
plugins/Unity/DashCommunicator/dashcommunicatorservice.hnew file+38
@@ -0,0 +1,38 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it under
5+ * the terms of the GNU Lesser General Public License version 3, as published by
6+ * the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but WITHOUT
9+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+ * Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef DASHCOMMUNICATORSERVICE_H
18+#define DASHCOMMUNICATORSERVICE_H
19+
20+#include "dbusdashcommunicatorservice.h"
21+
22+#include <QObject>
23+
24+class DashCommunicatorService: public QObject
25+{
26+ Q_OBJECT
27+public:
28+ DashCommunicatorService(QObject *parent = 0);
29+ ~DashCommunicatorService();
30+
31+Q_SIGNALS:
32+ void setCurrentScopeRequested(const QString &scopeId, bool animate, bool reset);
33+
34+private:
35+ DBusDashCommunicatorService *m_dbusService;
36+};
37+
38+#endif // DBUSUNITYSESSIONSERVICE_H
plugins/Unity/DashCommunicator/dbusdashcommunicatorservice.cppnew file+39
@@ -0,0 +1,39 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it under
5+ * the terms of the GNU Lesser General Public License version 3, as published by
6+ * the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but WITHOUT
9+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+ * Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "dbusdashcommunicatorservice.h"
18+
19+#include <QDBusConnection>
20+#include <QDBusInterface>
21+#include <QDebug>
22+
23+DBusDashCommunicatorService::DBusDashCommunicatorService(QObject *parent):
24+ QObject(parent)
25+{
26+ QDBusConnection connection = QDBusConnection::sessionBus();
27+
28+ connection.registerService("com.canonical.UnityDash");
29+ connection.registerObject("/com/canonical/UnityDash", this, QDBusConnection::ExportScriptableSlots);
30+}
31+
32+DBusDashCommunicatorService::~DBusDashCommunicatorService()
33+{
34+}
35+
36+void DBusDashCommunicatorService::SetCurrentScope(const QString &scopeId, bool animate, bool reset)
37+{
38+ Q_EMIT setCurrentScopeRequested(scopeId, animate, reset);
39+}
plugins/Unity/DashCommunicator/dbusdashcommunicatorservice.hnew file+39
@@ -0,0 +1,39 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it under
5+ * the terms of the GNU Lesser General Public License version 3, as published by
6+ * the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but WITHOUT
9+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+ * Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef DBUSDASHCOMMUNICATORSERVICE_H
18+#define DBUSDASHCOMMUNICATORSERVICE_H
19+
20+#include <QObject>
21+
22+class DBusDashCommunicatorService: public QObject
23+{
24+ Q_OBJECT
25+ Q_CLASSINFO("D-Bus Interface", "com.canonical.Unity.DashCommunicator")
26+
27+public:
28+ DBusDashCommunicatorService(QObject *parent = 0);
29+ ~DBusDashCommunicatorService();
30+
31+Q_SIGNALS:
32+ void setCurrentScopeRequested(const QString &scopeId, bool animate, bool reset);
33+
34+public Q_SLOTS:
35+ Q_SCRIPTABLE void SetCurrentScope(const QString &scopeId, bool animate, bool reset);
36+
37+};
38+
39+#endif // DBUSUNITYSESSIONSERVICE_H
plugins/Unity/DashCommunicator/plugin.cppnew file+31
@@ -0,0 +1,31 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Authors: Michael Zanetti <michael.zanetti@canonical.com>
17+ */
18+
19+#include "plugin.h"
20+#include "dashcommunicator.h"
21+#include "dashcommunicatorservice.h"
22+
23+#include <QDBusConnection>
24+#include <QtQml/qqml.h>
25+
26+void DashCommunicatorPlugin::registerTypes(const char *uri)
27+{
28+ Q_ASSERT(uri == QStringLiteral("Unity.DashCommunicator"));
29+ qmlRegisterType<DashCommunicatorService>(uri, 0, 1, "DashCommunicatorService");
30+ qmlRegisterType<DashCommunicator>(uri, 0, 1, "DashCommunicator");
31+}
plugins/Unity/DashCommunicator/plugin.hnew file+34
@@ -0,0 +1,34 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Authors: Michael Zanetti <michael.zanetti@canonical.com>
17+ */
18+
19+#ifndef DASHCOMMUNICATOR_PLUGIN_H
20+#define DASHCOMMUNICATOR_PLUGIN_H
21+
22+#include <QtQml/QQmlEngine>
23+#include <QtQml/QQmlExtensionPlugin>
24+
25+class DashCommunicatorPlugin : public QQmlExtensionPlugin
26+{
27+ Q_OBJECT
28+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
29+
30+public:
31+ void registerTypes(const char *uri);
32+};
33+
34+#endif
plugins/Unity/DashCommunicator/qmldirnew file+3
@@ -0,0 +1,3 @@
1+module Unity.DashCommunicator
2+plugin DashCommunicator-qml
3+typeinfo DashCommunicator.qmltypes
plugins/Unity/Launcher/launchermodel.cpp+5
@@ -317,6 +317,11 @@ void LauncherModel::applicationAdded(const QModelIndex &parent, int row)
317 return;
317 return;
318 }
318 }
319
319
320+ if (app->appId() == "unity8-dash") {
321+ // Not adding the dash app
322+ return;
323+ }
324+
320 bool found = false;
325 bool found = false;
321 Q_FOREACH(LauncherItem *item, m_list) {
326 Q_FOREACH(LauncherItem *item, m_list) {
322 if (app->appId() == item->appId()) {
327 if (app->appId() == item->appId()) {
po/am.po+10−10
@@ -8,14 +8,14 @@ msgstr ""
8"Project-Id-Version: unity8\n"
8"Project-Id-Version: unity8\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2014-07-11 14:59+0200\n"
10"POT-Creation-Date: 2014-07-11 14:59+0200\n"
11−"PO-Revision-Date: 2014-07-27 01:25+0000\n"
11+"PO-Revision-Date: 2014-08-04 16:30+0000\n"
12"Last-Translator: samson <Unknown>\n"
12"Last-Translator: samson <Unknown>\n"
13"Language-Team: Amharic <am@li.org>\n"
13"Language-Team: Amharic <am@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-07-28 06:44+0000\n"
17+"X-Launchpad-Export-Date: 2014-08-05 06:45+0000\n"
18−"X-Generator: Launchpad (build 17131)\n"
18+"X-Generator: Launchpad (build 17147)\n"
19
19
20#: plugins/Unity/Launcher/launcheritem.cpp:43
20#: plugins/Unity/Launcher/launcheritem.cpp:43
21#: plugins/Unity/Launcher/launcheritem.cpp:73
21#: plugins/Unity/Launcher/launcheritem.cpp:73
@@ -36,7 +36,7 @@ msgstr "የቀኝ ጠርዝ"
36
36
37#: qml/Components/EdgeDemo.qml:114
37#: qml/Components/EdgeDemo.qml:114
38msgid "Try swiping from the right edge to unlock the phone"
38msgid "Try swiping from the right edge to unlock the phone"
39−msgstr ""
39+msgstr "ስልኩን ለመክፈት ከ ቀኝ ጠርዝ በኩል ወደ ግራ ይጥረጉ"
40
40
41#: qml/Components/EdgeDemo.qml:145
41#: qml/Components/EdgeDemo.qml:145
42msgid "Top edge"
42msgid "Top edge"
@@ -44,7 +44,7 @@ msgstr "የላይ ጠርዝ"
44
44
45#: qml/Components/EdgeDemo.qml:146
45#: qml/Components/EdgeDemo.qml:146
46msgid "Try swiping from the top edge to access the indicators"
46msgid "Try swiping from the top edge to access the indicators"
47−msgstr ""
47+msgstr "ጠቋሚዎች ጋር ለመድረስ ከ ላይ ጠርዝ በኩል ወደ ታች ይጥረጉ"
48
48
49#: qml/Components/EdgeDemo.qml:171
49#: qml/Components/EdgeDemo.qml:171
50msgid "Close"
50msgid "Close"
@@ -52,7 +52,7 @@ msgstr "መዝጊያ"
52
52
53#: qml/Components/EdgeDemo.qml:172
53#: qml/Components/EdgeDemo.qml:172
54msgid "Swipe up again to close the settings screen"
54msgid "Swipe up again to close the settings screen"
55−msgstr ""
55+msgstr "የማሰናጃውን መመልከቻ ለመዝጋት እንደገና ወደ ላይ ይጥረጉ"
56
56
57#: qml/Components/EdgeDemo.qml:199
57#: qml/Components/EdgeDemo.qml:199
58msgid "Left edge"
58msgid "Left edge"
@@ -60,11 +60,11 @@ msgstr "የግራ ጠርዝ"
60
60
61#: qml/Components/EdgeDemo.qml:200
61#: qml/Components/EdgeDemo.qml:200
62msgid "Swipe from the left to reveal the launcher for quick access to apps"
62msgid "Swipe from the left to reveal the launcher for quick access to apps"
63−msgstr ""
63+msgstr "ከ ግራ በኩል ወደ ቀኝ ይጥረጉ በፍጥነት መተግበሪያዎች ጋር ለመድረስ እና ለማስጀመር"
64
64
65#: qml/Components/EdgeDemo.qml:227
65#: qml/Components/EdgeDemo.qml:227
66msgid "Well done"
66msgid "Well done"
67−msgstr ""
67+msgstr "ጥሩ አድረገዋል"
68
68
69#: qml/Components/EdgeDemo.qml:228
69#: qml/Components/EdgeDemo.qml:228
70msgid ""
70msgid ""
@@ -123,7 +123,7 @@ msgstr "አነስተኛ..."
123
123
124#: qml/Dash/Previews/PreviewRatingInput.qml:81
124#: qml/Dash/Previews/PreviewRatingInput.qml:81
125msgid "Rate this"
125msgid "Rate this"
126−msgstr ""
126+msgstr "ለዚህ ደረጃ ይስጡ"
127
127
128#: qml/Dash/Previews/PreviewRatingInput.qml:126
128#: qml/Dash/Previews/PreviewRatingInput.qml:126
129msgid "Add a review"
129msgid "Add a review"
@@ -135,7 +135,7 @@ msgstr "መላኪያ"
135
135
136#: qml/Greeter/Greeter.qml:157
136#: qml/Greeter/Greeter.qml:157
137msgid "Swipe to unlock"
137msgid "Swipe to unlock"
138−msgstr ""
138+msgstr "ለመክፈት ይጥረጉ"
139
139
140#: qml/Hud/HudParametrizedActionsPage.qml:132
140#: qml/Hud/HudParametrizedActionsPage.qml:132
141msgid "Confirm"
141msgid "Confirm"
po/da.po+7−7
@@ -9,13 +9,13 @@ msgstr ""
9"Project-Id-Version: unity8\n"
9"Project-Id-Version: unity8\n"
10"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
11"POT-Creation-Date: 2014-07-11 14:59+0200\n"
11"POT-Creation-Date: 2014-07-11 14:59+0200\n"
12−"PO-Revision-Date: 2014-07-22 15:46+0000\n"
12+"PO-Revision-Date: 2014-07-30 12:37+0000\n"
13−"Last-Translator: Kenneth Ramsussen <kentora@kentora.dk>\n"
13+"Last-Translator: Ask Hjorth Larsen <asklarsen@gmail.com>\n"
14"Language-Team: Danish <dansk@dansk-gruppen.dk>\n"
14"Language-Team: Danish <dansk@dansk-gruppen.dk>\n"
15"MIME-Version: 1.0\n"
15"MIME-Version: 1.0\n"
16"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Type: text/plain; charset=UTF-8\n"
17"Content-Transfer-Encoding: 8bit\n"
17"Content-Transfer-Encoding: 8bit\n"
18−"X-Launchpad-Export-Date: 2014-07-26 07:16+0000\n"
18+"X-Launchpad-Export-Date: 2014-07-31 07:15+0000\n"
19"X-Generator: Launchpad (build 17131)\n"
19"X-Generator: Launchpad (build 17131)\n"
20
20
21#: plugins/Unity/Launcher/launcheritem.cpp:43
21#: plugins/Unity/Launcher/launcheritem.cpp:43
@@ -72,7 +72,7 @@ msgid ""
72"You have now mastered the edge gestures and can start using the "
72"You have now mastered the edge gestures and can start using the "
73"phone<br><br>Tap on the screen to start"
73"phone<br><br>Tap on the screen to start"
74msgstr ""
74msgstr ""
75−"Du mestrer nu kantgestusserne og kan begynde at bruge telefonen<br><br>Tryk "
75+"Du mestrer nu kantbevægelserne og kan begynde at bruge telefonen<br><br>Tryk "
76"på skærmen for at starte"
76"på skærmen for at starte"
77
77
78#: qml/Components/Lockscreen.qml:214
78#: qml/Components/Lockscreen.qml:214
@@ -114,7 +114,7 @@ msgstr "Seneste"
114
114
115#: qml/Dash/GenericScopeView.qml:330
115#: qml/Dash/GenericScopeView.qml:330
116msgid "See all"
116msgid "See all"
117−msgstr ""
117+msgstr "Se alle"
118
118
119#: qml/Dash/Previews/PreviewActionCombo.qml:34
119#: qml/Dash/Previews/PreviewActionCombo.qml:34
120msgid "More..."
120msgid "More..."
@@ -170,11 +170,11 @@ msgstr "Indtast venligst PIN for SIM"
170
170
171#: qml/Panel/ActiveCallHint.qml:77
171#: qml/Panel/ActiveCallHint.qml:77
172msgid "Tap to return to call..."
172msgid "Tap to return to call..."
173−msgstr ""
173+msgstr "Tryk for at besvare opkaldet..."
174
174
175#: qml/Panel/ActiveCallHint.qml:90
175#: qml/Panel/ActiveCallHint.qml:90
176msgid "Conference"
176msgid "Conference"
177−msgstr ""
177+msgstr "Konference"
178
178
179#: qml/Panel/Indicators/MenuItemFactory.qml:583
179#: qml/Panel/Indicators/MenuItemFactory.qml:583
180msgid "In queue…"
180msgid "In queue…"
po/el.po+6−6
@@ -8,14 +8,14 @@ msgstr ""
8"Project-Id-Version: unity\n"
8"Project-Id-Version: unity\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2014-07-11 14:59+0200\n"
10"POT-Creation-Date: 2014-07-11 14:59+0200\n"
11−"PO-Revision-Date: 2014-07-28 16:06+0000\n"
11+"PO-Revision-Date: 2014-08-04 18:44+0000\n"
12−"Last-Translator: Chris Taklis (aka Kailor) <ctaklis@gmail.com>\n"
12+"Last-Translator: madvinegar <madvinegar@hotmail.com>\n"
13"Language-Team: Greek <el@li.org>\n"
13"Language-Team: Greek <el@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-07-29 06:54+0000\n"
17+"X-Launchpad-Export-Date: 2014-08-05 06:45+0000\n"
18−"X-Generator: Launchpad (build 17131)\n"
18+"X-Generator: Launchpad (build 17147)\n"
19
19
20#: plugins/Unity/Launcher/launcheritem.cpp:43
20#: plugins/Unity/Launcher/launcheritem.cpp:43
21#: plugins/Unity/Launcher/launcheritem.cpp:73
21#: plugins/Unity/Launcher/launcheritem.cpp:73
@@ -171,7 +171,7 @@ msgstr "Παρακαλώ εισάγετε το PIN της κάρτας SIM"
171
171
172#: qml/Panel/ActiveCallHint.qml:77
172#: qml/Panel/ActiveCallHint.qml:77
173msgid "Tap to return to call..."
173msgid "Tap to return to call..."
174−msgstr ""
174+msgstr "Πατήστε για να γυρίσετε στην κλήση..."
175
175
176#: qml/Panel/ActiveCallHint.qml:90
176#: qml/Panel/ActiveCallHint.qml:90
177msgid "Conference"
177msgid "Conference"
@@ -199,7 +199,7 @@ msgstr "Ολοκληρώθηκε"
199
199
200#: qml/Panel/Indicators/MenuItemFactory.qml:595
200#: qml/Panel/Indicators/MenuItemFactory.qml:595
201msgid "Failed, tap to retry"
201msgid "Failed, tap to retry"
202−msgstr ""
202+msgstr "Αποτυχία, πατήστε για να ξαναπροσπαθήσετε"
203
203
204#: qml/Panel/SearchIndicator.qml:27
204#: qml/Panel/SearchIndicator.qml:27
205msgid "Search"
205msgid "Search"
po/fa.po+51−49
@@ -8,200 +8,202 @@ msgstr ""
8"Project-Id-Version: unity8\n"
8"Project-Id-Version: unity8\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2014-07-11 14:59+0200\n"
10"POT-Creation-Date: 2014-07-11 14:59+0200\n"
11−"PO-Revision-Date: 2013-10-31 19:02+0000\n"
11+"PO-Revision-Date: 2014-08-03 13:00+0000\n"
12−"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
12+"Last-Translator: Arash Badie Modiri <arashbm@gmail.com>\n"
13"Language-Team: Persian <fa@li.org>\n"
13"Language-Team: Persian <fa@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-07-26 07:17+0000\n"
17+"X-Launchpad-Export-Date: 2014-08-04 06:32+0000\n"
18−"X-Generator: Launchpad (build 17131)\n"
18+"X-Generator: Launchpad (build 17147)\n"
19
19
20#: plugins/Unity/Launcher/launcheritem.cpp:43
20#: plugins/Unity/Launcher/launcheritem.cpp:43
21#: plugins/Unity/Launcher/launcheritem.cpp:73
21#: plugins/Unity/Launcher/launcheritem.cpp:73
22msgid "Pin shortcut"
22msgid "Pin shortcut"
23−msgstr ""
23+msgstr "سنجاق کردن میانبر"
24
24
25#: plugins/Unity/Launcher/launcheritem.cpp:73
25#: plugins/Unity/Launcher/launcheritem.cpp:73
26msgid "Unpin shortcut"
26msgid "Unpin shortcut"
27−msgstr ""
27+msgstr "برداشتن سنجاق میانبر"
28
28
29#: qml/Components/EdgeDemoOverlay.qml:151
29#: qml/Components/EdgeDemoOverlay.qml:151
30msgid "Skip intro"
30msgid "Skip intro"
31−msgstr ""
31+msgstr "پرش از مقدّمه"
32
32
33#: qml/Components/EdgeDemo.qml:113
33#: qml/Components/EdgeDemo.qml:113
34msgid "Right edge"
34msgid "Right edge"
35−msgstr ""
35+msgstr "لبه‌ی راست"
36
36
37#: qml/Components/EdgeDemo.qml:114
37#: qml/Components/EdgeDemo.qml:114
38msgid "Try swiping from the right edge to unlock the phone"
38msgid "Try swiping from the right edge to unlock the phone"
39−msgstr ""
39+msgstr "برای باز کردن قفل تلفن، از لبه‌ی راست بکشید"
40
40
41#: qml/Components/EdgeDemo.qml:145
41#: qml/Components/EdgeDemo.qml:145
42msgid "Top edge"
42msgid "Top edge"
43−msgstr ""
43+msgstr "لبه‌ی بالا"
44
44
45#: qml/Components/EdgeDemo.qml:146
45#: qml/Components/EdgeDemo.qml:146
46msgid "Try swiping from the top edge to access the indicators"
46msgid "Try swiping from the top edge to access the indicators"
47−msgstr ""
47+msgstr "برای دست‌رسی به نشانگرها، از لبه‌ی بالا بکشید"
48
48
49#: qml/Components/EdgeDemo.qml:171
49#: qml/Components/EdgeDemo.qml:171
50msgid "Close"
50msgid "Close"
51−msgstr ""
51+msgstr "بستن"
52
52
53#: qml/Components/EdgeDemo.qml:172
53#: qml/Components/EdgeDemo.qml:172
54msgid "Swipe up again to close the settings screen"
54msgid "Swipe up again to close the settings screen"
55−msgstr ""
55+msgstr "برای بستن صفحه‌ی تنظیمات، دوباره به بالا بکشپد"
56
56
57#: qml/Components/EdgeDemo.qml:199
57#: qml/Components/EdgeDemo.qml:199
58msgid "Left edge"
58msgid "Left edge"
59−msgstr ""
59+msgstr "لبه‌ی چپ"
60
60
61#: qml/Components/EdgeDemo.qml:200
61#: qml/Components/EdgeDemo.qml:200
62msgid "Swipe from the left to reveal the launcher for quick access to apps"
62msgid "Swipe from the left to reveal the launcher for quick access to apps"
63−msgstr ""
63+msgstr "برای آشکار شدن اجراگر به منظور دست‌رسی سریع به اپ‌ها از سمت چپ بکشید"
64
64
65#: qml/Components/EdgeDemo.qml:227
65#: qml/Components/EdgeDemo.qml:227
66msgid "Well done"
66msgid "Well done"
67−msgstr ""
67+msgstr "عالی بود"
68
68
69#: qml/Components/EdgeDemo.qml:228
69#: qml/Components/EdgeDemo.qml:228
70msgid ""
70msgid ""
71"You have now mastered the edge gestures and can start using the "
71"You have now mastered the edge gestures and can start using the "
72"phone<br><br>Tap on the screen to start"
72"phone<br><br>Tap on the screen to start"
73msgstr ""
73msgstr ""
74+"هم اکنون در استفاده از ژست های حرکتی مهارت لازم را دارید و می‌توانید استفاده "
75+"از تلفن را آغاز کنید<br><br>برای شروع صفحه را لمس کنید"
74
76
75#: qml/Components/Lockscreen.qml:214
77#: qml/Components/Lockscreen.qml:214
76msgid "Emergency Call"
78msgid "Emergency Call"
77−msgstr ""
79+msgstr "تماس اضطراری"
78
80
79#: qml/Components/Lockscreen.qml:231
81#: qml/Components/Lockscreen.qml:231
80msgid "OK"
82msgid "OK"
81−msgstr ""
83+msgstr "قبول"
82
84
83#: qml/Components/PassphraseLockscreen.qml:60
85#: qml/Components/PassphraseLockscreen.qml:60
84#, qt-format
86#, qt-format
85msgid "Hello %1"
87msgid "Hello %1"
86−msgstr ""
88+msgstr "سلام %1"
87
89
88#: qml/Components/PassphraseLockscreen.qml:60
90#: qml/Components/PassphraseLockscreen.qml:60
89msgid "Hello"
91msgid "Hello"
90−msgstr ""
92+msgstr "سلام"
91
93
92#: qml/Components/PinLockscreen.qml:198
94#: qml/Components/PinLockscreen.qml:198
93msgid "CANCEL"
95msgid "CANCEL"
94−msgstr ""
96+msgstr "لغو"
95
97
96#: qml/Components/PinLockscreen.qml:216
98#: qml/Components/PinLockscreen.qml:216
97msgid "DONE"
99msgid "DONE"
98−msgstr ""
100+msgstr "انجام شد"
99
101
100#: qml/Components/SeeMore.qml:35
102#: qml/Components/SeeMore.qml:35
101msgid "See more"
103msgid "See more"
102−msgstr ""
104+msgstr "بیش‌تر ببینید"
103
105
104#: qml/Components/SeeMore.qml:58 qml/Dash/GenericScopeView.qml:330
106#: qml/Components/SeeMore.qml:58 qml/Dash/GenericScopeView.qml:330
105msgid "See less"
107msgid "See less"
106−msgstr ""
108+msgstr "کم‌تر ببینید"
107
109
108#: qml/Dash/DashApps.qml:37
110#: qml/Dash/DashApps.qml:37
109msgid "Recent"
111msgid "Recent"
110−msgstr ""
112+msgstr "موارد اخیر"
111
113
112#: qml/Dash/GenericScopeView.qml:330
114#: qml/Dash/GenericScopeView.qml:330
113msgid "See all"
115msgid "See all"
114−msgstr ""
116+msgstr "دیدن همه"
115
117
116#: qml/Dash/Previews/PreviewActionCombo.qml:34
118#: qml/Dash/Previews/PreviewActionCombo.qml:34
117msgid "More..."
119msgid "More..."
118−msgstr ""
120+msgstr "بیش‌تر…"
119
121
120#: qml/Dash/Previews/PreviewActionCombo.qml:34
122#: qml/Dash/Previews/PreviewActionCombo.qml:34
121msgid "Less..."
123msgid "Less..."
122−msgstr ""
124+msgstr "کم‌تر…"
123
125
124#: qml/Dash/Previews/PreviewRatingInput.qml:81
126#: qml/Dash/Previews/PreviewRatingInput.qml:81
125msgid "Rate this"
127msgid "Rate this"
126−msgstr ""
128+msgstr "امتیاز دادن"
127
129
128#: qml/Dash/Previews/PreviewRatingInput.qml:126
130#: qml/Dash/Previews/PreviewRatingInput.qml:126
129msgid "Add a review"
131msgid "Add a review"
130−msgstr ""
132+msgstr "افزودن یک بازبینی"
131
133
132#: qml/Dash/Previews/PreviewRatingInput.qml:166
134#: qml/Dash/Previews/PreviewRatingInput.qml:166
133msgid "Send"
135msgid "Send"
134−msgstr ""
136+msgstr "ارسال"
135
137
136#: qml/Greeter/Greeter.qml:157
138#: qml/Greeter/Greeter.qml:157
137msgid "Swipe to unlock"
139msgid "Swipe to unlock"
138−msgstr ""
140+msgstr "برای باز کردن قفل بکشد"
139
141
140#: qml/Hud/HudParametrizedActionsPage.qml:132
142#: qml/Hud/HudParametrizedActionsPage.qml:132
141msgid "Confirm"
143msgid "Confirm"
142−msgstr ""
144+msgstr "تأیید"
143
145
144#: qml/Hud/Hud.qml:61 qml/Hud/Hud.qml:99 qml/Hud/Hud.qml:372
146#: qml/Hud/Hud.qml:61 qml/Hud/Hud.qml:99 qml/Hud/Hud.qml:372
145msgid "Type or say a command"
147msgid "Type or say a command"
146−msgstr ""
148+msgstr "دستوری را بنویسید یا بگویید"
147
149
148#: qml/Hud/Hud.qml:84
150#: qml/Hud/Hud.qml:84
149msgid "Loading. Please Wait..."
151msgid "Loading. Please Wait..."
150−msgstr ""
152+msgstr "در حال بار کردن. لطفاً صبر کنید…"
151
153
152#: qml/Hud/Hud.qml:89
154#: qml/Hud/Hud.qml:89
153msgid "Speak Now..."
155msgid "Speak Now..."
154−msgstr ""
156+msgstr "اکنون صحبت کنید…"
155
157
156#: qml/Hud/Hud.qml:94
158#: qml/Hud/Hud.qml:94
157msgid "Speaking..."
159msgid "Speaking..."
158−msgstr ""
160+msgstr "در حال صحبت کردن…"
159
161
160#: qml/Notifications/NotificationMenuItemFactory.qml:91
162#: qml/Notifications/NotificationMenuItemFactory.qml:91
161msgid "Show password"
163msgid "Show password"
162−msgstr ""
164+msgstr "نمایش گذرواژه"
163
165
164#: qml/Notifications/NotificationMenuItemFactory.qml:103
166#: qml/Notifications/NotificationMenuItemFactory.qml:103
165msgid "Please enter SIM PIN"
167msgid "Please enter SIM PIN"
166−msgstr ""
168+msgstr "لطفاً PIN سیم‌کارت را وارد کنید"
167
169
168#: qml/Panel/ActiveCallHint.qml:77
170#: qml/Panel/ActiveCallHint.qml:77
169msgid "Tap to return to call..."
171msgid "Tap to return to call..."
170−msgstr ""
172+msgstr "برای بازگشت به تماس ضربه بزنید…"
171
173
172#: qml/Panel/ActiveCallHint.qml:90
174#: qml/Panel/ActiveCallHint.qml:90
173msgid "Conference"
175msgid "Conference"
174−msgstr ""
176+msgstr "کنفرانس"
175
177
176#: qml/Panel/Indicators/MenuItemFactory.qml:583
178#: qml/Panel/Indicators/MenuItemFactory.qml:583
177msgid "In queue…"
179msgid "In queue…"
178−msgstr ""
180+msgstr "در صف…"
179
181
180#: qml/Panel/Indicators/MenuItemFactory.qml:587
182#: qml/Panel/Indicators/MenuItemFactory.qml:587
181msgid "Downloading"
183msgid "Downloading"
182−msgstr ""
184+msgstr "در حال بارگیری"
183
185
184#: qml/Panel/Indicators/MenuItemFactory.qml:589
186#: qml/Panel/Indicators/MenuItemFactory.qml:589
185msgid "Paused, tap to resume"
187msgid "Paused, tap to resume"
186−msgstr ""
188+msgstr "مکث شد، برای از سر گیری ضربه بزنید"
187
189
188#: qml/Panel/Indicators/MenuItemFactory.qml:591
190#: qml/Panel/Indicators/MenuItemFactory.qml:591
189msgid "Canceled"
191msgid "Canceled"
190−msgstr ""
192+msgstr "لغو شد"
191
193
192#: qml/Panel/Indicators/MenuItemFactory.qml:593
194#: qml/Panel/Indicators/MenuItemFactory.qml:593
193msgid "Finished"
195msgid "Finished"
194−msgstr ""
196+msgstr "پایان یافت"
195
197
196#: qml/Panel/Indicators/MenuItemFactory.qml:595
198#: qml/Panel/Indicators/MenuItemFactory.qml:595
197msgid "Failed, tap to retry"
199msgid "Failed, tap to retry"
198−msgstr ""
200+msgstr "شکست خورد، برای تلاش دوباره ضربه بزنید"
199
201
200#: qml/Panel/SearchIndicator.qml:27
202#: qml/Panel/SearchIndicator.qml:27
201msgid "Search"
203msgid "Search"
202−msgstr ""
204+msgstr "جست‌وجو"
203
205
204#: qml/Shell.qml:359
206#: qml/Shell.qml:359
205#, qt-format
207#, qt-format
206msgid "Please enter %1"
208msgid "Please enter %1"
207−msgstr ""
209+msgstr "لطفا %1 را وارد کنید"
po/hu.po+8−8
@@ -8,14 +8,14 @@ msgstr ""
8"Project-Id-Version: unity\n"
8"Project-Id-Version: unity\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2014-07-11 14:59+0200\n"
10"POT-Creation-Date: 2014-07-11 14:59+0200\n"
11−"PO-Revision-Date: 2014-07-27 15:04+0000\n"
11+"PO-Revision-Date: 2014-08-04 09:10+0000\n"
12"Last-Translator: Richard Somlói <ricsipontaz@gmail.com>\n"
12"Last-Translator: Richard Somlói <ricsipontaz@gmail.com>\n"
13"Language-Team: Hungarian <hu@li.org>\n"
13"Language-Team: Hungarian <hu@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-07-28 06:44+0000\n"
17+"X-Launchpad-Export-Date: 2014-08-05 06:45+0000\n"
18−"X-Generator: Launchpad (build 17131)\n"
18+"X-Generator: Launchpad (build 17147)\n"
19
19
20#: plugins/Unity/Launcher/launcheritem.cpp:43
20#: plugins/Unity/Launcher/launcheritem.cpp:43
21#: plugins/Unity/Launcher/launcheritem.cpp:73
21#: plugins/Unity/Launcher/launcheritem.cpp:73
@@ -44,7 +44,7 @@ msgstr "Felső szél"
44
44
45#: qml/Components/EdgeDemo.qml:146
45#: qml/Components/EdgeDemo.qml:146
46msgid "Try swiping from the top edge to access the indicators"
46msgid "Try swiping from the top edge to access the indicators"
47−msgstr "A jelzők eléréséhez csúsztassa ujját a felső szélről"
47+msgstr "Az állapotmenük eléréséhez csúsztassa ujját a felső szélről"
48
48
49#: qml/Components/EdgeDemo.qml:171
49#: qml/Components/EdgeDemo.qml:171
50msgid "Close"
50msgid "Close"
@@ -82,16 +82,16 @@ msgstr "Vészhívás"
82
82
83#: qml/Components/Lockscreen.qml:231
83#: qml/Components/Lockscreen.qml:231
84msgid "OK"
84msgid "OK"
85−msgstr "OK"
85+msgstr "Ok"
86
86
87#: qml/Components/PassphraseLockscreen.qml:60
87#: qml/Components/PassphraseLockscreen.qml:60
88#, qt-format
88#, qt-format
89msgid "Hello %1"
89msgid "Hello %1"
90−msgstr "Szia %1"
90+msgstr "Üdv %1"
91
91
92#: qml/Components/PassphraseLockscreen.qml:60
92#: qml/Components/PassphraseLockscreen.qml:60
93msgid "Hello"
93msgid "Hello"
94−msgstr "Szia"
94+msgstr "Üdv"
95
95
96#: qml/Components/PinLockscreen.qml:198
96#: qml/Components/PinLockscreen.qml:198
97msgid "CANCEL"
97msgid "CANCEL"
@@ -127,7 +127,7 @@ msgstr "Kevesebb…"
127
127
128#: qml/Dash/Previews/PreviewRatingInput.qml:81
128#: qml/Dash/Previews/PreviewRatingInput.qml:81
129msgid "Rate this"
129msgid "Rate this"
130−msgstr "Osztályzás"
130+msgstr "Értékelje"
131
131
132#: qml/Dash/Previews/PreviewRatingInput.qml:126
132#: qml/Dash/Previews/PreviewRatingInput.qml:126
133msgid "Add a review"
133msgid "Add a review"
po/is.po+6−6
@@ -10,14 +10,14 @@ msgstr ""
10"Project-Id-Version: po_unity8-is\n"
10"Project-Id-Version: po_unity8-is\n"
11"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
11"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
12"POT-Creation-Date: 2014-07-11 14:59+0200\n"
12"POT-Creation-Date: 2014-07-11 14:59+0200\n"
13−"PO-Revision-Date: 2014-07-23 05:57+0000\n"
13+"PO-Revision-Date: 2014-07-31 16:10+0000\n"
14"Last-Translator: Sveinn í Felli <sv1@fellsnet.is>\n"
14"Last-Translator: Sveinn í Felli <sv1@fellsnet.is>\n"
15"Language-Team: Icelandic <translation-team-is@lists.sourceforge.net>\n"
15"Language-Team: Icelandic <translation-team-is@lists.sourceforge.net>\n"
16"MIME-Version: 1.0\n"
16"MIME-Version: 1.0\n"
17"Content-Type: text/plain; charset=UTF-8\n"
17"Content-Type: text/plain; charset=UTF-8\n"
18"Content-Transfer-Encoding: 8bit\n"
18"Content-Transfer-Encoding: 8bit\n"
19−"X-Launchpad-Export-Date: 2014-07-26 07:16+0000\n"
19+"X-Launchpad-Export-Date: 2014-08-01 07:14+0000\n"
20−"X-Generator: Launchpad (build 17131)\n"
20+"X-Generator: Launchpad (build 17141)\n"
21"Language: is\n"
21"Language: is\n"
22
22
23#: plugins/Unity/Launcher/launcheritem.cpp:43
23#: plugins/Unity/Launcher/launcheritem.cpp:43
@@ -117,7 +117,7 @@ msgstr "Nýlegt"
117
117
118#: qml/Dash/GenericScopeView.qml:330
118#: qml/Dash/GenericScopeView.qml:330
119msgid "See all"
119msgid "See all"
120−msgstr ""
120+msgstr "Sjá allt"
121
121
122#: qml/Dash/Previews/PreviewActionCombo.qml:34
122#: qml/Dash/Previews/PreviewActionCombo.qml:34
123msgid "More..."
123msgid "More..."
@@ -173,11 +173,11 @@ msgstr "Settu inn PIN fyrir SIM"
173
173
174#: qml/Panel/ActiveCallHint.qml:77
174#: qml/Panel/ActiveCallHint.qml:77
175msgid "Tap to return to call..."
175msgid "Tap to return to call..."
176−msgstr ""
176+msgstr "Slá til að svara símtali..."
177
177
178#: qml/Panel/ActiveCallHint.qml:90
178#: qml/Panel/ActiveCallHint.qml:90
179msgid "Conference"
179msgid "Conference"
180−msgstr ""
180+msgstr "Ráðstefna"
181
181
182#: qml/Panel/Indicators/MenuItemFactory.qml:583
182#: qml/Panel/Indicators/MenuItemFactory.qml:583
183msgid "In queue…"
183msgid "In queue…"
po/km.po+7−7
@@ -8,14 +8,14 @@ msgstr ""
8"Project-Id-Version: unity8\n"
8"Project-Id-Version: unity8\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2014-07-11 14:59+0200\n"
10"POT-Creation-Date: 2014-07-11 14:59+0200\n"
11−"PO-Revision-Date: 2014-07-16 01:37+0000\n"
11+"PO-Revision-Date: 2014-08-01 04:02+0000\n"
12−"Last-Translator: Khoem Sokhem <khoemsokhem@khmeros.info>\n"
12+"Last-Translator: Sok Sophea <sksophea@gmail.com>\n"
13"Language-Team: Khmer <km@li.org>\n"
13"Language-Team: Khmer <km@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-07-26 07:16+0000\n"
17+"X-Launchpad-Export-Date: 2014-08-02 06:39+0000\n"
18−"X-Generator: Launchpad (build 17131)\n"
18+"X-Generator: Launchpad (build 17147)\n"
19
19
20#: plugins/Unity/Launcher/launcheritem.cpp:43
20#: plugins/Unity/Launcher/launcheritem.cpp:43
21#: plugins/Unity/Launcher/launcheritem.cpp:73
21#: plugins/Unity/Launcher/launcheritem.cpp:73
@@ -115,7 +115,7 @@ msgstr "បច្ចុប្បន្ន"
115
115
116#: qml/Dash/GenericScopeView.qml:330
116#: qml/Dash/GenericScopeView.qml:330
117msgid "See all"
117msgid "See all"
118−msgstr ""
118+msgstr "មើល​ទាំងអស់"
119
119
120#: qml/Dash/Previews/PreviewActionCombo.qml:34
120#: qml/Dash/Previews/PreviewActionCombo.qml:34
121msgid "More..."
121msgid "More..."
@@ -171,11 +171,11 @@ msgstr "បញ្ចូល​កូដ PIN របស់​ស៊ីមកាត"
171
171
172#: qml/Panel/ActiveCallHint.qml:77
172#: qml/Panel/ActiveCallHint.qml:77
173msgid "Tap to return to call..."
173msgid "Tap to return to call..."
174−msgstr ""
174+msgstr "ប៉ះ​ដើម្បី​ត្រឡប់​ទៅ​ការ​ហៅ..."
175
175
176#: qml/Panel/ActiveCallHint.qml:90
176#: qml/Panel/ActiveCallHint.qml:90
177msgid "Conference"
177msgid "Conference"
178−msgstr ""
178+msgstr "សន្និសីទ"
179
179
180#: qml/Panel/Indicators/MenuItemFactory.qml:583
180#: qml/Panel/Indicators/MenuItemFactory.qml:583
181msgid "In queue…"
181msgid "In queue…"
po/pl.po+7−7
@@ -8,14 +8,14 @@ msgstr ""
8"Project-Id-Version: unity\n"
8"Project-Id-Version: unity\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2014-07-11 14:59+0200\n"
10"POT-Creation-Date: 2014-07-11 14:59+0200\n"
11−"PO-Revision-Date: 2014-07-19 14:10+0000\n"
11+"PO-Revision-Date: 2014-07-31 13:51+0000\n"
12−"Last-Translator: GTriderXC <gtriderxc@yahoo.com>\n"
12+"Last-Translator: Michał Radwański <radwanski.michal@outlook.com>\n"
13"Language-Team: Polish <pl@li.org>\n"
13"Language-Team: Polish <pl@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-07-26 07:17+0000\n"
17+"X-Launchpad-Export-Date: 2014-08-01 07:14+0000\n"
18−"X-Generator: Launchpad (build 17131)\n"
18+"X-Generator: Launchpad (build 17141)\n"
19
19
20#: plugins/Unity/Launcher/launcheritem.cpp:43
20#: plugins/Unity/Launcher/launcheritem.cpp:43
21#: plugins/Unity/Launcher/launcheritem.cpp:73
21#: plugins/Unity/Launcher/launcheritem.cpp:73
@@ -115,7 +115,7 @@ msgstr "Ostatnio używane"
115
115
116#: qml/Dash/GenericScopeView.qml:330
116#: qml/Dash/GenericScopeView.qml:330
117msgid "See all"
117msgid "See all"
118−msgstr ""
118+msgstr "Zobacz wszystko"
119
119
120#: qml/Dash/Previews/PreviewActionCombo.qml:34
120#: qml/Dash/Previews/PreviewActionCombo.qml:34
121msgid "More..."
121msgid "More..."
@@ -171,11 +171,11 @@ msgstr "Wprowadź kod PIN karty SIM"
171
171
172#: qml/Panel/ActiveCallHint.qml:77
172#: qml/Panel/ActiveCallHint.qml:77
173msgid "Tap to return to call..."
173msgid "Tap to return to call..."
174−msgstr ""
174+msgstr "Dotknij aby powrócić do połączenia..."
175
175
176#: qml/Panel/ActiveCallHint.qml:90
176#: qml/Panel/ActiveCallHint.qml:90
177msgid "Conference"
177msgid "Conference"
178−msgstr ""
178+msgstr "Konferencja"
179
179
180#: qml/Panel/Indicators/MenuItemFactory.qml:583
180#: qml/Panel/Indicators/MenuItemFactory.qml:583
181msgid "In queue…"
181msgid "In queue…"
po/pt_BR.po+11−11
@@ -8,14 +8,14 @@ msgstr ""
8"Project-Id-Version: unity\n"
8"Project-Id-Version: unity\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2014-07-11 14:59+0200\n"
10"POT-Creation-Date: 2014-07-11 14:59+0200\n"
11−"PO-Revision-Date: 2014-07-17 05:46+0000\n"
11+"PO-Revision-Date: 2014-08-04 18:33+0000\n"
12−"Last-Translator: Tiago Hillebrandt <tiagohillebrandt@gmail.com>\n"
12+"Last-Translator: Rafael Neri <Unknown>\n"
13"Language-Team: Brazilian Portuguese <pt_BR@li.org>\n"
13"Language-Team: Brazilian Portuguese <pt_BR@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-07-26 07:17+0000\n"
17+"X-Launchpad-Export-Date: 2014-08-05 06:45+0000\n"
18−"X-Generator: Launchpad (build 17131)\n"
18+"X-Generator: Launchpad (build 17147)\n"
19
19
20#: plugins/Unity/Launcher/launcheritem.cpp:43
20#: plugins/Unity/Launcher/launcheritem.cpp:43
21#: plugins/Unity/Launcher/launcheritem.cpp:73
21#: plugins/Unity/Launcher/launcheritem.cpp:73
@@ -99,7 +99,7 @@ msgstr "CANCELAR"
99
99
100#: qml/Components/PinLockscreen.qml:216
100#: qml/Components/PinLockscreen.qml:216
101msgid "DONE"
101msgid "DONE"
102−msgstr ""
102+msgstr "FEITO"
103
103
104#: qml/Components/SeeMore.qml:35
104#: qml/Components/SeeMore.qml:35
105msgid "See more"
105msgid "See more"
@@ -115,7 +115,7 @@ msgstr "Recentes"
115
115
116#: qml/Dash/GenericScopeView.qml:330
116#: qml/Dash/GenericScopeView.qml:330
117msgid "See all"
117msgid "See all"
118−msgstr ""
118+msgstr "Ver todos"
119
119
120#: qml/Dash/Previews/PreviewActionCombo.qml:34
120#: qml/Dash/Previews/PreviewActionCombo.qml:34
121msgid "More..."
121msgid "More..."
@@ -171,11 +171,11 @@ msgstr "Por favor, digite o PIN do cartão SIM"
171
171
172#: qml/Panel/ActiveCallHint.qml:77
172#: qml/Panel/ActiveCallHint.qml:77
173msgid "Tap to return to call..."
173msgid "Tap to return to call..."
174−msgstr ""
174+msgstr "Toque para retornar para a chamada..."
175
175
176#: qml/Panel/ActiveCallHint.qml:90
176#: qml/Panel/ActiveCallHint.qml:90
177msgid "Conference"
177msgid "Conference"
178−msgstr ""
178+msgstr "Conferência"
179
179
180#: qml/Panel/Indicators/MenuItemFactory.qml:583
180#: qml/Panel/Indicators/MenuItemFactory.qml:583
181msgid "In queue…"
181msgid "In queue…"
@@ -187,7 +187,7 @@ msgstr "Baixando"
187
187
188#: qml/Panel/Indicators/MenuItemFactory.qml:589
188#: qml/Panel/Indicators/MenuItemFactory.qml:589
189msgid "Paused, tap to resume"
189msgid "Paused, tap to resume"
190−msgstr ""
190+msgstr "Em pausa, toque para continuar"
191
191
192#: qml/Panel/Indicators/MenuItemFactory.qml:591
192#: qml/Panel/Indicators/MenuItemFactory.qml:591
193msgid "Canceled"
193msgid "Canceled"
@@ -195,11 +195,11 @@ msgstr "Cancelado"
195
195
196#: qml/Panel/Indicators/MenuItemFactory.qml:593
196#: qml/Panel/Indicators/MenuItemFactory.qml:593
197msgid "Finished"
197msgid "Finished"
198−msgstr ""
198+msgstr "Finalizado"
199
199
200#: qml/Panel/Indicators/MenuItemFactory.qml:595
200#: qml/Panel/Indicators/MenuItemFactory.qml:595
201msgid "Failed, tap to retry"
201msgid "Failed, tap to retry"
202−msgstr ""
202+msgstr "Falhou, toque para tentar novamente"
203
203
204#: qml/Panel/SearchIndicator.qml:27
204#: qml/Panel/SearchIndicator.qml:27
205msgid "Search"
205msgid "Search"
po/ru.po+18−18
@@ -8,23 +8,23 @@ msgstr ""
8"Project-Id-Version: unity\n"
8"Project-Id-Version: unity\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2014-07-11 14:59+0200\n"
10"POT-Creation-Date: 2014-07-11 14:59+0200\n"
11−"PO-Revision-Date: 2014-05-15 12:57+0000\n"
11+"PO-Revision-Date: 2014-07-31 09:39+0000\n"
12−"Last-Translator: Zonov Roman <Unknown>\n"
12+"Last-Translator: Jay ZDLin <Unknown>\n"
13"Language-Team: Russian <ru@li.org>\n"
13"Language-Team: Russian <ru@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-07-26 07:17+0000\n"
17+"X-Launchpad-Export-Date: 2014-08-01 07:14+0000\n"
18−"X-Generator: Launchpad (build 17131)\n"
18+"X-Generator: Launchpad (build 17141)\n"
19
19
20#: plugins/Unity/Launcher/launcheritem.cpp:43
20#: plugins/Unity/Launcher/launcheritem.cpp:43
21#: plugins/Unity/Launcher/launcheritem.cpp:73
21#: plugins/Unity/Launcher/launcheritem.cpp:73
22msgid "Pin shortcut"
22msgid "Pin shortcut"
23−msgstr ""
23+msgstr "Закрепить ярлык"
24
24
25#: plugins/Unity/Launcher/launcheritem.cpp:73
25#: plugins/Unity/Launcher/launcheritem.cpp:73
26msgid "Unpin shortcut"
26msgid "Unpin shortcut"
27−msgstr ""
27+msgstr "Изъять ярлык"
28
28
29#: qml/Components/EdgeDemoOverlay.qml:151
29#: qml/Components/EdgeDemoOverlay.qml:151
30msgid "Skip intro"
30msgid "Skip intro"
@@ -85,7 +85,7 @@ msgstr "Экстренный вызов"
85
85
86#: qml/Components/Lockscreen.qml:231
86#: qml/Components/Lockscreen.qml:231
87msgid "OK"
87msgid "OK"
88−msgstr ""
88+msgstr "ОК"
89
89
90#: qml/Components/PassphraseLockscreen.qml:60
90#: qml/Components/PassphraseLockscreen.qml:60
91#, qt-format
91#, qt-format
@@ -98,11 +98,11 @@ msgstr "Привет"
98
98
99#: qml/Components/PinLockscreen.qml:198
99#: qml/Components/PinLockscreen.qml:198
100msgid "CANCEL"
100msgid "CANCEL"
101−msgstr ""
101+msgstr "ОТМЕНИТЬ"
102
102
103#: qml/Components/PinLockscreen.qml:216
103#: qml/Components/PinLockscreen.qml:216
104msgid "DONE"
104msgid "DONE"
105−msgstr ""
105+msgstr "ГОТОВО"
106
106
107#: qml/Components/SeeMore.qml:35
107#: qml/Components/SeeMore.qml:35
108msgid "See more"
108msgid "See more"
@@ -118,7 +118,7 @@ msgstr "Недавние"
118
118
119#: qml/Dash/GenericScopeView.qml:330
119#: qml/Dash/GenericScopeView.qml:330
120msgid "See all"
120msgid "See all"
121−msgstr ""
121+msgstr "Показать всё"
122
122
123#: qml/Dash/Previews/PreviewActionCombo.qml:34
123#: qml/Dash/Previews/PreviewActionCombo.qml:34
124msgid "More..."
124msgid "More..."
@@ -174,35 +174,35 @@ msgstr "Введите PIN код"
174
174
175#: qml/Panel/ActiveCallHint.qml:77
175#: qml/Panel/ActiveCallHint.qml:77
176msgid "Tap to return to call..."
176msgid "Tap to return to call..."
177−msgstr ""
177+msgstr "Коснитесь для возврата к вызову..."
178
178
179#: qml/Panel/ActiveCallHint.qml:90
179#: qml/Panel/ActiveCallHint.qml:90
180msgid "Conference"
180msgid "Conference"
181−msgstr ""
181+msgstr "Конференция"
182
182
183#: qml/Panel/Indicators/MenuItemFactory.qml:583
183#: qml/Panel/Indicators/MenuItemFactory.qml:583
184msgid "In queue…"
184msgid "In queue…"
185−msgstr ""
185+msgstr "В очереди..."
186
186
187#: qml/Panel/Indicators/MenuItemFactory.qml:587
187#: qml/Panel/Indicators/MenuItemFactory.qml:587
188msgid "Downloading"
188msgid "Downloading"
189−msgstr ""
189+msgstr "Загрузка"
190
190
191#: qml/Panel/Indicators/MenuItemFactory.qml:589
191#: qml/Panel/Indicators/MenuItemFactory.qml:589
192msgid "Paused, tap to resume"
192msgid "Paused, tap to resume"
193−msgstr ""
193+msgstr "Приостановлено, коснитесь для возобновления"
194
194
195#: qml/Panel/Indicators/MenuItemFactory.qml:591
195#: qml/Panel/Indicators/MenuItemFactory.qml:591
196msgid "Canceled"
196msgid "Canceled"
197−msgstr ""
197+msgstr "Отменено"
198
198
199#: qml/Panel/Indicators/MenuItemFactory.qml:593
199#: qml/Panel/Indicators/MenuItemFactory.qml:593
200msgid "Finished"
200msgid "Finished"
201−msgstr ""
201+msgstr "Завершено"
202
202
203#: qml/Panel/Indicators/MenuItemFactory.qml:595
203#: qml/Panel/Indicators/MenuItemFactory.qml:595
204msgid "Failed, tap to retry"
204msgid "Failed, tap to retry"
205−msgstr ""
205+msgstr "Сбой, коснитесь для повтора"
206
206
207#: qml/Panel/SearchIndicator.qml:27
207#: qml/Panel/SearchIndicator.qml:27
208msgid "Search"
208msgid "Search"
po/sv.po+6−6
@@ -8,14 +8,14 @@ msgstr ""
8"Project-Id-Version: unity\n"
8"Project-Id-Version: unity\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2014-07-11 14:59+0200\n"
10"POT-Creation-Date: 2014-07-11 14:59+0200\n"
11−"PO-Revision-Date: 2014-07-25 14:27+0000\n"
11+"PO-Revision-Date: 2014-07-31 08:10+0000\n"
12−"Last-Translator: Josef Andersson <Unknown>\n"
12+"Last-Translator: Peter Hultqvist <phq@silentorbit.com>\n"
13"Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n"
13"Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-07-26 07:17+0000\n"
17+"X-Launchpad-Export-Date: 2014-08-01 07:14+0000\n"
18−"X-Generator: Launchpad (build 17131)\n"
18+"X-Generator: Launchpad (build 17141)\n"
19"Language: sv\n"
19"Language: sv\n"
20
20
21#: plugins/Unity/Launcher/launcheritem.cpp:43
21#: plugins/Unity/Launcher/launcheritem.cpp:43
@@ -102,11 +102,11 @@ msgstr "KLAR"
102
102
103#: qml/Components/SeeMore.qml:35
103#: qml/Components/SeeMore.qml:35
104msgid "See more"
104msgid "See more"
105−msgstr "Se mer"
105+msgstr "Visa mer"
106
106
107#: qml/Components/SeeMore.qml:58 qml/Dash/GenericScopeView.qml:330
107#: qml/Components/SeeMore.qml:58 qml/Dash/GenericScopeView.qml:330
108msgid "See less"
108msgid "See less"
109−msgstr "Se mindre"
109+msgstr "Visa mindre"
110
110
111#: qml/Dash/DashApps.qml:37
111#: qml/Dash/DashApps.qml:37
112msgid "Recent"
112msgid "Recent"
po/ug.po+1−1
@@ -14,7 +14,7 @@ msgstr ""
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-07-29 06:54+0000\n"
17+"X-Launchpad-Export-Date: 2014-07-31 07:15+0000\n"
18"X-Generator: Launchpad (build 17131)\n"
18"X-Generator: Launchpad (build 17131)\n"
19
19
20#: plugins/Unity/Launcher/launcheritem.cpp:43
20#: plugins/Unity/Launcher/launcheritem.cpp:43
po/uk.po+4−4
@@ -8,13 +8,13 @@ msgstr ""
8"Project-Id-Version: unity\n"
8"Project-Id-Version: unity\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2014-07-11 14:59+0200\n"
10"POT-Creation-Date: 2014-07-11 14:59+0200\n"
11−"PO-Revision-Date: 2014-07-28 21:35+0000\n"
11+"PO-Revision-Date: 2014-07-29 07:53+0000\n"
12−"Last-Translator: Ярослав Смук <Smuk.Slavko@yandex.ua>\n"
12+"Last-Translator: Yuri Chornoivan <yurchor@gmail.com>\n"
13"Language-Team: Ukrainian <uk@li.org>\n"
13"Language-Team: Ukrainian <uk@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-07-29 06:54+0000\n"
17+"X-Launchpad-Export-Date: 2014-07-31 07:15+0000\n"
18"X-Generator: Launchpad (build 17131)\n"
18"X-Generator: Launchpad (build 17131)\n"
19
19
20#: plugins/Unity/Launcher/launcheritem.cpp:43
20#: plugins/Unity/Launcher/launcheritem.cpp:43
@@ -172,7 +172,7 @@ msgstr "Введіть PIN-код SIM-картки"
172
172
173#: qml/Panel/ActiveCallHint.qml:77
173#: qml/Panel/ActiveCallHint.qml:77
174msgid "Tap to return to call..."
174msgid "Tap to return to call..."
175−msgstr ""
175+msgstr "Торкніться, щоб повернутися до дзвінка…"
176
176
177#: qml/Panel/ActiveCallHint.qml:90
177#: qml/Panel/ActiveCallHint.qml:90
178msgid "Conference"
178msgid "Conference"
po/zh_CN.po+1−1
@@ -14,7 +14,7 @@ msgstr ""
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17−"X-Launchpad-Export-Date: 2014-07-29 06:54+0000\n"
17+"X-Launchpad-Export-Date: 2014-07-31 07:15+0000\n"
18"X-Generator: Launchpad (build 17131)\n"
18"X-Generator: Launchpad (build 17131)\n"
19
19
20#: plugins/Unity/Launcher/launcheritem.cpp:43
20#: plugins/Unity/Launcher/launcheritem.cpp:43
qml/Components/EdgeDemo.qml+5−6
@@ -22,7 +22,6 @@ Item {
22 id: demo
22 id: demo
23
23
24 property Item greeter
24 property Item greeter
25− property Item dash
26 property Item launcher
25 property Item launcher
27 property Item indicators
26 property Item indicators
28 property Item underlay
27 property Item underlay
@@ -144,7 +143,7 @@ Item {
144 "edge": "top",
143 "edge": "top",
145 "title": i18n.tr("Top edge"),
144 "title": i18n.tr("Top edge"),
146 "text": i18n.tr("Try swiping from the top edge to access the indicators"),
145 "text": i18n.tr("Try swiping from the top edge to access the indicators"),
147− "anchors.fill": demo.dash,
146+ "anchors.fill": demo.underlay,
148 });
147 });
149 }
148 }
150 if (d.topEdgeDemo) {
149 if (d.topEdgeDemo) {
@@ -193,12 +192,12 @@ Item {
193 function startLeftEdgeDemo() {
192 function startLeftEdgeDemo() {
194 demo.panelEnabled = false;
193 demo.panelEnabled = false;
195 demo.launcherEnabled = true;
194 demo.launcherEnabled = true;
196− if (demo.dash && demo.underlay) {
195+ if (demo.underlay) {
197 d.leftEdgeDemo = d.overlay.createObject(demo.underlay, {
196 d.leftEdgeDemo = d.overlay.createObject(demo.underlay, {
198 "edge": "left",
197 "edge": "left",
199 "title": i18n.tr("Left edge"),
198 "title": i18n.tr("Left edge"),
200 "text": i18n.tr("Swipe from the left to reveal the launcher for quick access to apps"),
199 "text": i18n.tr("Swipe from the left to reveal the launcher for quick access to apps"),
201− "anchors.fill": demo.dash,
200+ "anchors.fill": demo.underlay,
202 });
201 });
203 }
202 }
204 if (d.leftEdgeDemo) {
203 if (d.leftEdgeDemo) {
@@ -221,12 +220,12 @@ Item {
221
220
222 function startFinalEdgeDemo() {
221 function startFinalEdgeDemo() {
223 demo.launcherEnabled = false;
222 demo.launcherEnabled = false;
224− if (demo.dash && demo.underlay) {
223+ if (demo.underlay) {
225 d.finalEdgeDemo = d.overlay.createObject(demo.underlay, {
224 d.finalEdgeDemo = d.overlay.createObject(demo.underlay, {
226 "edge": "none",
225 "edge": "none",
227 "title": i18n.tr("Well done"),
226 "title": i18n.tr("Well done"),
228 "text": i18n.tr("You have now mastered the edge gestures and can start using the phone<br><br>Tap on the screen to start"),
227 "text": i18n.tr("You have now mastered the edge gestures and can start using the phone<br><br>Tap on the screen to start"),
229− "anchors.fill": demo.dash,
228+ "anchors.fill": demo.underlay,
230 "showSkip": false,
229 "showSkip": false,
231 });
230 });
232 }
231 }
qml/Components/PageHeader.qml+24−7
@@ -43,6 +43,12 @@ Item {
43 signal backClicked()
43 signal backClicked()
44
44
45 onScopeStyleChanged: refreshLogo()
45 onScopeStyleChanged: refreshLogo()
46+ onSearchQueryChanged: {
47+ // Make sure we are at the search page if the search query changes behind our feet
48+ if (searchQuery) {
49+ headerContainer.showSearch = true;
50+ }
51+ }
46
52
47 function triggerSearch() {
53 function triggerSearch() {
48 if (searchEntryEnabled) {
54 if (searchEntryEnabled) {
@@ -51,6 +57,12 @@ Item {
51 }
57 }
52 }
58 }
53
59
60+ function closePopup() {
61+ if (headerContainer.popover != null) {
62+ PopupUtils.close(headerContainer.popover);
63+ }
64+ }
65+
54 function resetSearch(keepFocus) {
66 function resetSearch(keepFocus) {
55 if (searchHistory) {
67 if (searchHistory) {
56 searchHistory.addQuery(searchTextField.text);
68 searchHistory.addQuery(searchTextField.text);
@@ -59,9 +71,7 @@ Item {
59 unfocus();
71 unfocus();
60 }
72 }
61 searchTextField.text = "";
73 searchTextField.text = "";
62− if (headerContainer.popover != null) {
74+ closePopup();
63− PopupUtils.close(headerContainer.popover);
64− }
65 }
75 }
66
76
67 function unfocus() {
77 function unfocus() {
@@ -102,9 +112,7 @@ Item {
102 anchors { fill: parent; margins: units.gu(1); bottomMargin: units.gu(3) + bottomContainer.height }
112 anchors { fill: parent; margins: units.gu(1); bottomMargin: units.gu(3) + bottomContainer.height }
103 visible: headerContainer.showSearch
113 visible: headerContainer.showSearch
104 onPressed: {
114 onPressed: {
105− if (headerContainer.popover) {
115+ closePopup();
106− PopupUtils.close(headerContainer.popover);
107− }
108 if (!searchTextField.text) {
116 if (!searchTextField.text) {
109 headerContainer.showSearch = false;
117 headerContainer.showSearch = false;
110 }
118 }
@@ -171,6 +179,7 @@ Item {
171 property var contents: TextField {
179 property var contents: TextField {
172 id: searchTextField
180 id: searchTextField
173 objectName: "searchTextField"
181 objectName: "searchTextField"
182+ inputMethodHints: Qt.ImhNoPredictiveText
174 hasClearButton: false
183 hasClearButton: false
175 anchors {
184 anchors {
176 fill: parent
185 fill: parent
@@ -208,6 +217,12 @@ Item {
208 root.openSearchHistory();
217 root.openSearchHistory();
209 }
218 }
210 }
219 }
220+
221+ onTextChanged: {
222+ if (text != "") {
223+ closePopup();
224+ }
225+ }
211 }
226 }
212 }
227 }
213
228
@@ -285,6 +300,7 @@ Item {
285
300
286 Repeater {
301 Repeater {
287 id: recentSearches
302 id: recentSearches
303+ objectName: "recentSearches"
288 model: searchHistory
304 model: searchHistory
289
305
290 delegate: Standard {
306 delegate: Standard {
@@ -293,7 +309,8 @@ Item {
293 onClicked: {
309 onClicked: {
294 searchHistory.addQuery(text);
310 searchHistory.addQuery(text);
295 searchTextField.text = text;
311 searchTextField.text = text;
296− PopupUtils.close(popover);
312+ closePopup();
313+ unfocus();
297 }
314 }
298 }
315 }
299 }
316 }
qml/Components/PassphraseLockscreen.qml+3
@@ -31,8 +31,10 @@ Item {
31
31
32 function clear(playAnimation) {
32 function clear(playAnimation) {
33 pinentryField.text = "";
33 pinentryField.text = "";
34+ pinentryField.enabled = true
34 if (playAnimation) {
35 if (playAnimation) {
35 wrongPasswordAnimation.start();
36 wrongPasswordAnimation.start();
37+ pinentryField.forceActiveFocus();
36 } else {
38 } else {
37 pinentryField.focus = false
39 pinentryField.focus = false
38 }
40 }
@@ -78,6 +80,7 @@ Item {
78
80
79 onAccepted: {
81 onAccepted: {
80 if (pinentryField.text) {
82 if (pinentryField.text) {
83+ pinentryField.enabled = false;
81 root.entered(pinentryField.text);
84 root.entered(pinentryField.text);
82 }
85 }
83 }
86 }
qml/Dash/Apps/CloseIcon.qmldeleted−62
@@ -1,62 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−
19−Item {
20− id: root
21−
22− Image {
23− id: closeIcon
24− anchors.centerIn: parent
25− source: "graphics/close_btn.png"
26−
27− state: (root.enabled) ? "shown" : "hidden"
28−
29− states: [
30− State {
31− name: "shown"
32− PropertyChanges {
33− target: closeIcon
34− height: root.height
35− width: root.width
36− }
37− },
38− State {
39− name: "hidden"
40− PropertyChanges {
41− target: closeIcon
42− height: 0
43− width: 0
44− }
45− }
46−
47− ]
48− transitions: [
49− Transition {
50− to: "shown"
51− NumberAnimation {
52− properties: "width, height"; duration: 300
53− easing { type: Easing.OutBack; overshoot: 5 }
54− }
55− },
56− Transition {
57− to: "hidden"
58− NumberAnimation { properties: "width, height"; duration: 250; }
59− }
60− ]
61− }
62−}
qml/Dash/Apps/RunningApplicationTile.qmldeleted−136
@@ -1,136 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import Ubuntu.Components 0.1
19−import Unity.Application 0.1
20−import "../../Components/"
21−import "../../Components/ListItems"
22−
23−AbstractButton {
24− id: root
25− property var application
26−
27− signal requestedApplicationActivation(var application)
28− signal requestedApplicationTermination(var application)
29− signal requestedActivationMode()
30− signal requestedTerminationMode()
31−
32− width: thumbnail.width
33− height: thumbnail.height + labelContainer.height
34−
35− property bool terminationModeEnabled: false
36−
37− onClicked: {
38− if (!terminationModeEnabled)
39− requestedApplicationActivation(application)
40− }
41−
42− onPressAndHold: {
43− if (terminationModeEnabled) {
44− requestedActivationMode()
45− } else {
46− requestedTerminationMode()
47− }
48− }
49−
50− Item {
51− id: thumbnail
52−
53− width: shapedApplicationImage.width
54− height: shapedApplicationImage.height
55−
56− UbuntuShape {
57− id: shapedApplicationImage
58− x: 0
59− y: 0
60− height: applicationImage.height
61− width: applicationImage.width
62− radius: "medium"
63−
64− image: Image {
65− id: applicationImage
66− source: application.screenshot
67− // height : width = ss.height : ss.width
68−
69− property bool isLandscape: sourceSize.width > sourceSize.height
70− property real maxDimension: units.gu(17)
71−
72− width: isLandscape ? Math.min(sourceSize.width, maxDimension) : height * (sourceSize.width / sourceSize.height)
73− height: isLandscape ? width * (sourceSize.height / sourceSize.width) : Math.min(sourceSize.height, maxDimension)
74− fillMode: Image.Stretch
75− }
76−
77− }
78−
79− UbuntuShape {
80− id: borderPressed
81−
82− anchors.fill: shapedApplicationImage
83− radius: "medium"
84− borderSource: "radius_pressed.sci"
85− opacity: root.pressed ? 1.0 : 0.0
86− Behavior on opacity { NumberAnimation { duration: 200; easing.type: Easing.OutQuint } }
87− }
88− }
89−
90− // FIXME: label code duplicated with Tile
91− Item {
92− id: labelContainer
93− anchors {
94− left: thumbnail.left
95− right: thumbnail.right
96− top: thumbnail.bottom
97− }
98− height: units.gu(2)
99−
100− Label {
101− id: label
102− anchors {
103− baseline: parent.bottom
104− left: parent.left
105− right: parent.right
106− }
107− text: (application) ? application.name : ""
108−
109− // TODO karni: Update Ubuntu.Components.Themes.Palette and use theme color instead
110− color: "grey"
111− opacity: 0.9
112− fontSize: "small"
113− elide: Text.ElideMiddle
114− horizontalAlignment: Text.AlignHCenter
115− }
116− }
117−
118− CloseIcon {
119− objectName: "closeIcon " + model.name
120− anchors {
121− left: thumbnail.left
122− leftMargin: -units.gu(1)
123− top: thumbnail.top
124− topMargin: -units.gu(1)
125− }
126− height: units.gu(6)
127− width: units.gu(6)
128− id: closeIcon
129− enabled: root.terminationModeEnabled
130−
131− MouseArea {
132− anchors { fill: parent; margins: -units.gu(1) }
133− onClicked: requestedApplicationTermination(application)
134− }
135− }
136−}
qml/Dash/Apps/RunningApplicationsGrid.qmldeleted−105
@@ -1,105 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import "../../Components"
19−
20−import Ubuntu.Gestures 0.1
21−import Unity.Application 0.1
22−
23−ResponsiveFlowView {
24− id: root
25− clip: true
26−
27− signal updateScreenshots
28− property alias enableHeightBehavior: heightBehaviour.enabled
29− property bool enableHeightBehaviorOnNextCreation: model.count === 0
30−
31− Behavior on height {
32− id: heightBehaviour
33− enabled: false
34− NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
35− }
36−
37− Connections {
38− target: root.model
39− onCountChanged: {
40− heightBehaviour.enabled = true;
41− }
42− }
43−
44− property bool canEnableTerminationMode: true
45−
46− onCanEnableTerminationModeChanged: {
47− if (!canEnableTerminationMode)
48− terminationModeEnabled = false
49− }
50−
51− // when false, it means it's on activation mode
52− property bool terminationModeEnabled: false
53−
54− maximumNumberOfColumns: 10
55− minimumHorizontalSpacing: units.gu(2)
56− referenceDelegateWidth: units.gu(11)
57− verticalSpacing: units.gu(2)
58−
59− delegate: Item {
60− width: runningAppTile.width + root.horizontalSpacing
61− height: runningAppTile.height + root.verticalSpacing
62−
63− RunningApplicationTile {
64− id: runningAppTile
65− objectName: "runningAppTile " + model.name
66− anchors {
67− top: parent.top
68− horizontalCenter: parent.horizontalCenter
69− }
70− application: model
71− onRequestedActivationMode: { root.terminationModeEnabled = false }
72− onRequestedTerminationMode: {
73− if (canEnableTerminationMode)
74− root.terminationModeEnabled = true
75− }
76− onRequestedApplicationTermination: {
77− ApplicationManager.stopApplication(model.appId)
78− }
79− onRequestedApplicationActivation: {
80− ApplicationManager.requestFocusApplication(model.appId)
81− }
82−
83− terminationModeEnabled: root.terminationModeEnabled
84− }
85− }
86−
87− move: Transition {
88− NumberAnimation { properties: "x,y"; duration: 400; easing.type: Easing.OutCubic }
89− }
90−
91− MouseArea {
92− anchors.fill: parent
93− z: -1 // behind all RunningApplicationTiles
94− enabled: root.terminationModeEnabled
95− onPressed: { root.terminationModeEnabled = false; }
96− }
97−
98− PressedOutsideNotifier {
99− anchors.fill: parent
100− enabled: root.terminationModeEnabled
101− onPressedOutside: {
102− root.terminationModeEnabled = false;
103− }
104− }
105−}
qml/Dash/Apps/graphics/close_btn@9.pngdeleted

Binary file not shown.

qml/Dash/CardTool.qml+27−2
@@ -165,15 +165,40 @@ Item {
165 }
165 }
166 }
166 }
167
167
168+ Item {
169+ id: attributesModel
170+ property int numOfAttributes: 0
171+ property var model: []
172+ property bool hasAttributes: {
173+ var attributes = components["attributes"];
174+ var hasAttributesFlag = (attributes != undefined) && attributes["field"];
175+
176+ if (hasAttributesFlag) {
177+ if (attributes["max-count"]) {
178+ numOfAttributes = attributes["max-count"];
179+ }
180+ }
181+ return hasAttributesFlag
182+ }
183+
184+ onNumOfAttributesChanged: {
185+ model = []
186+ for (var i = 0; i < numOfAttributes; i++) {
187+ model.push( {"value":"text"+(i+1), "icon":"image://theme/ok" } );
188+ }
189+ }
190+ }
191+
168 Loader {
192 Loader {
169 id: cardLoader
193 id: cardLoader
170− property var fields: ["art", "mascot", "title", "subtitle", "summary"]
194+ property var fields: ["art", "mascot", "title", "subtitle", "summary", "attributes"]
171 property var maxData: {
195 property var maxData: {
172 "art": Qt.resolvedUrl("graphics/checkers.png"),
196 "art": Qt.resolvedUrl("graphics/checkers.png"),
173 "mascot": Qt.resolvedUrl("graphics/checkers.png"),
197 "mascot": Qt.resolvedUrl("graphics/checkers.png"),
174 "title": "—\n—",
198 "title": "—\n—",
175 "subtitle": "—",
199 "subtitle": "—",
176− "summary": "—\n—\n—\n—\n—"
200+ "summary": "—\n—\n—\n—\n—",
201+ "attributes": attributesModel.model
177 }
202 }
178 sourceComponent: cardTool.cardComponent
203 sourceComponent: cardTool.cardComponent
179 onLoaded: {
204 onLoaded: {
qml/Dash/Dash.qml+8
@@ -18,6 +18,7 @@ import QtQuick 2.2
18import Ubuntu.Components 0.1
18import Ubuntu.Components 0.1
19import Unity 0.2
19import Unity 0.2
20import Utils 0.1
20import Utils 0.1
21+import Unity.DashCommunicator 0.1
21import "../Components"
22import "../Components"
22
23
23Showable {
24Showable {
@@ -29,6 +30,13 @@ Showable {
29 property string showScopeOnLoaded: "clickscope"
30 property string showScopeOnLoaded: "clickscope"
30 property real contentScale: 1.0
31 property real contentScale: 1.0
31
32
33+ DashCommunicatorService {
34+ objectName: "dashCommunicatorService"
35+ onSetCurrentScopeRequested: {
36+ dash.setCurrentScope(scopeId, animate, reset)
37+ }
38+ }
39+
32 function setCurrentScope(scopeId, animate, reset) {
40 function setCurrentScope(scopeId, animate, reset) {
33 var scopeIndex = filteredScopes.findFirst(Scopes.RoleId, scopeId)
41 var scopeIndex = filteredScopes.findFirst(Scopes.RoleId, scopeId)
34
42
qml/Dash/DashApplication.qmlnew file+29
@@ -0,0 +1,29 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.2
18+import Ubuntu.Components 1.0
19+
20+MainView {
21+ width: units.gu(40)
22+ height: units.gu(71)
23+
24+ useDeprecatedToolbar: false
25+
26+ Dash {
27+ anchors.fill: parent
28+ }
29+}
qml/Dash/DashApps.qmldeleted−40
@@ -1,40 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import Ubuntu.Components 0.1
19−import Utils 0.1
20−import Unity.Application 0.1
21−import "../Components"
22−import "../Components/ListItems"
23−import "Apps"
24−
25−GenericScopeView {
26− id: scopeView
27− objectName: "DashApps"
28−
29− property var runningApps: ApplicationManager
30−
31− QtObject {
32− id: countObject
33− property int count: scopeView.scope.searchQuery.length == 0 ? scopeView.runningApps.count : 0
34− }
35−
36− onScopeChanged: {
37− scopeView.scope.categories.addSpecialCategory("running.apps.category", i18n.tr("Recent"), "", "{ \"template\": { \"category-layout\": \"running-apps\" } }", countObject);
38− enableHeightBehaviorOnNextCreation = scopeView.runningApps.count === 0
39− }
40−}
qml/Dash/DashContent.qml+1−3
@@ -128,9 +128,7 @@ Item {
128 return (xPositionRelativetoView > -width && xPositionRelativetoView < width) ? 1 : 0
128 return (xPositionRelativetoView > -width && xPositionRelativetoView < width) ? 1 : 0
129 }
129 }
130 asynchronous: true
130 asynchronous: true
131− // TODO This if will eventually go away since we're killing DashApps.qml
132− // once we move app closing to the spread
133− source: (scope.id == "clickscope") ? "DashApps.qml" : "GenericScopeView.qml"
131+ source: "GenericScopeView.qml"
134 objectName: scope.id + " loader"
132 objectName: scope.id + " loader"
135
133
136 readonly property bool moving: item ? item.moving : false
134 readonly property bool moving: item ? item.moving : false
qml/Dash/GenericScopeView.qml+2−11
@@ -18,7 +18,6 @@ import QtQuick 2.0
18import Ubuntu.Components 0.1
18import Ubuntu.Components 0.1
19import Utils 0.1
19import Utils 0.1
20import Unity 0.2
20import Unity 0.2
21−import Unity.Application 0.1
22import Dash 0.1
21import Dash 0.1
23import "../Components"
22import "../Components"
24import "../Components/ListItems" as ListItems
23import "../Components/ListItems" as ListItems
@@ -49,7 +48,7 @@ FocusScope {
49 }
48 }
50
49
51 function activateApp(appId) {
50 function activateApp(appId) {
52− shell.activateApplication(appId);
51+ Qt.openUrlExternally(appId);
53 }
52 }
54
53
55 function positionAtBeginning() {
54 function positionAtBeginning() {
@@ -196,7 +195,6 @@ FocusScope {
196 switch (cardTool.categoryLayout) {
195 switch (cardTool.categoryLayout) {
197 case "carousel": return "CardCarousel.qml";
196 case "carousel": return "CardCarousel.qml";
198 case "vertical-journal": return "CardVerticalJournal.qml";
197 case "vertical-journal": return "CardVerticalJournal.qml";
199− case "running-apps": return "Apps/RunningApplicationsGrid.qml";
200 case "grid":
198 case "grid":
201 default: return "CardGrid.qml";
199 default: return "CardGrid.qml";
202 }
200 }
@@ -207,14 +205,7 @@ FocusScope {
207 item.enableHeightBehavior = scopeView.enableHeightBehaviorOnNextCreation;
205 item.enableHeightBehavior = scopeView.enableHeightBehaviorOnNextCreation;
208 scopeView.enableHeightBehaviorOnNextCreation = false;
206 scopeView.enableHeightBehaviorOnNextCreation = false;
209 }
207 }
210− if (source.toString().indexOf("Apps/RunningApplicationsGrid.qml") != -1) {
211− // TODO: this is still a kludge :D Ideally add some kind of hook so that we
212− // can do this from DashApps.qml or think a better way that needs no special casing
213− item.model = Qt.binding(function() { return runningApps; })
214− item.canEnableTerminationMode = Qt.binding(function() { return scopeView.isCurrent })
215− } else {
216− item.model = Qt.binding(function() { return results })
208+ item.model = Qt.binding(function() { return results })
217− }
218 item.objectName = Qt.binding(function() { return categoryId })
209 item.objectName = Qt.binding(function() { return categoryId })
219 item.scopeStyle = scopeView.scopeStyle;
210 item.scopeStyle = scopeView.scopeStyle;
220 if (baseItem.expandable) {
211 if (baseItem.expandable) {
qml/Dash/ScopeListView.qml−5
@@ -20,9 +20,4 @@ import Dash 0.1
20ListViewWithPageHeader {
20ListViewWithPageHeader {
21 maximumFlickVelocity: height * 10
21 maximumFlickVelocity: height * 10
22 flickDeceleration: height * 2
22 flickDeceleration: height * 2
23−
24− Connections {
25− target: greeter
26− onShownChanged: if (greeter.shown) showHeader()
27− }
28}
23}
qml/Dash/graphics/phone/screenshots/unity8-dash@12.pngnew file

Binary file not shown.

qml/Launcher/Launcher.qml+2−3
@@ -29,8 +29,7 @@ Item {
29 property int dragAreaWidth: units.gu(1)
29 property int dragAreaWidth: units.gu(1)
30 property int minimizeDistance: units.gu(26)
30 property int minimizeDistance: units.gu(26)
31 property real progress: dragArea.dragging && dragArea.touchX > panelWidth ?
31 property real progress: dragArea.dragging && dragArea.touchX > panelWidth ?
32− (width * (dragArea.touchX-panelWidth) / (width - panelWidth)) :
32+ (width * (dragArea.touchX-panelWidth) / (width - panelWidth)) : 0
33− (dragArea.dragging ? 0.001 : 0)
34
33
35 readonly property bool shown: panel.x > -panel.width
34 readonly property bool shown: panel.x > -panel.width
36
35
@@ -245,7 +244,7 @@ Item {
245 if (!dragging) {
244 if (!dragging) {
246 if (distance > panel.width / 2) {
245 if (distance > panel.width / 2) {
247 if (distance > minimizeDistance) {
246 if (distance > minimizeDistance) {
248− root.dash()
247+ root.dash();
249 } else {
248 } else {
250 root.switchToNextState("visible")
249 root.switchToNextState("visible")
251 }
250 }
qml/Launcher/LauncherDelegate.qml+9−6
@@ -70,28 +70,31 @@ Item {
70 height: root.itemHeight - units.gu(1)
70 height: root.itemHeight - units.gu(1)
71 }
71 }
72
72
73− BorderImage {
73+ UbuntuShape {
74 objectName: "countEmblem"
74 objectName: "countEmblem"
75 anchors {
75 anchors {
76 right: parent.right
76 right: parent.right
77 top: parent.top
77 top: parent.top
78 margins: units.dp(3)
78 margins: units.dp(3)
79 }
79 }
80− width: Math.min(root.itemWidth, Math.max(units.gu(3), countLabel.implicitWidth + units.gu(2.5)))
80+ width: Math.min(root.itemWidth, Math.max(units.gu(2), countLabel.implicitWidth + units.gu(1)))
81− height: units.gu(3)
81+ height: units.gu(2)
82− source: "graphics/notification.sci"
82+ color: UbuntuColors.orange
83 visible: root.count > 0
83 visible: root.count > 0
84+ borderSource: "none"
84
85
85 Label {
86 Label {
86 id: countLabel
87 id: countLabel
87 text: root.count
88 text: root.count
88 anchors.centerIn: parent
89 anchors.centerIn: parent
90+ // FIXME: verticalCenter seems to be off wee bit and QML doesn't have a centerLine
91+ // property for Text: https://bugreports.qt-project.org/browse/QTBUG-40479
92+ anchors.verticalCenterOffset: -units.dp(.5)
89 width: root.itemWidth - units.gu(1)
93 width: root.itemWidth - units.gu(1)
90 horizontalAlignment: Text.AlignHCenter
94 horizontalAlignment: Text.AlignHCenter
91 elide: Text.ElideRight
95 elide: Text.ElideRight
92 color: "white"
96 color: "white"
93− fontSize: "small"
97+ fontSize: "x-small"
94− font.bold: true
95 }
98 }
96 }
99 }
97
100
qml/Launcher/graphics/notification@30.pngdeleted

Binary file not shown.

qml/Launcher/graphics/notification@30.scideleted−5
@@ -1,5 +0,0 @@
1−border.left: 36
2−border.top: 36
3−border.bottom: 36
4−border.right: 36
5−source: notification@30.png
qml/Panel/Indicators/DefaultIndicatorPage.qml+1−1
@@ -126,7 +126,7 @@ IndicatorBase {
126 id: loader
126 id: loader
127 objectName: "menuItem" + index
127 objectName: "menuItem" + index
128 asynchronous: false
128 asynchronous: false
129− visible: height > 0
129+ visible: status == Loader.Ready
130
130
131 property int modelIndex: index
131 property int modelIndex: index
132
132
qml/Shell.qml+53−153
@@ -25,7 +25,6 @@ import Utils 0.1
25import LightDM 0.1 as LightDM
25import LightDM 0.1 as LightDM
26import Powerd 0.1
26import Powerd 0.1
27import SessionBroadcast 0.1
27import SessionBroadcast 0.1
28−import "Dash"
29import "Greeter"
28import "Greeter"
30import "Launcher"
29import "Launcher"
31import "Panel"
30import "Panel"
@@ -34,6 +33,7 @@ import "Notifications"
34import "Stages"
33import "Stages"
35import Unity.Notifications 1.0 as NotificationBackend
34import Unity.Notifications 1.0 as NotificationBackend
36import Unity.Session 0.1
35import Unity.Session 0.1
36+import Unity.DashCommunicator 0.1
37
37
38Item {
38Item {
39 id: shell
39 id: shell
@@ -48,8 +48,6 @@ Item {
48 property url background
48 property url background
49 readonly property real panelHeight: panel.panelHeight
49 readonly property real panelHeight: panel.panelHeight
50
50
51− property bool dashShown: dash.shown && dash.available && underlay.visible
52−
53 property bool sideStageEnabled: shell.width >= units.gu(100)
51 property bool sideStageEnabled: shell.width >= units.gu(100)
54 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
52 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
55
53
@@ -59,7 +57,6 @@ Item {
59 } else {
57 } else {
60 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
58 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
61 ApplicationManager.startApplication(appId, execFlags);
59 ApplicationManager.startApplication(appId, execFlags);
62− stages.show();
63 }
60 }
64 }
61 }
65
62
@@ -86,6 +83,11 @@ Item {
86 id: volumeControl
83 id: volumeControl
87 }
84 }
88
85
86+ DashCommunicator {
87+ id: dash
88+ objectName: "dashCommunicator"
89+ }
90+
89 WindowKeysFilter {
91 WindowKeysFilter {
90 // Handle but do not filter out volume keys
92 // Handle but do not filter out volume keys
91 Keys.onVolumeUpPressed: { volumeControl.volumeUp(); event.accepted = false; }
93 Keys.onVolumeUpPressed: { volumeControl.volumeUp(); event.accepted = false; }
@@ -121,64 +123,12 @@ Item {
121 visible: !fullyCovered
123 visible: !fullyCovered
122
124
123 Image {
125 Image {
124− anchors.fill: dash
126+ anchors.fill: underlay
125 source: shell.width > shell.height ? "Dash/graphics/paper_landscape.png" : "Dash/graphics/paper_portrait.png"
127 source: shell.width > shell.height ? "Dash/graphics/paper_landscape.png" : "Dash/graphics/paper_portrait.png"
126 fillMode: Image.PreserveAspectCrop
128 fillMode: Image.PreserveAspectCrop
127 horizontalAlignment: Image.AlignRight
129 horizontalAlignment: Image.AlignRight
128 verticalAlignment: Image.AlignTop
130 verticalAlignment: Image.AlignTop
129 }
131 }
130−
131− Dash {
132− id: dash
133− objectName: "dash"
134−
135− available: !LightDM.Greeter.active
136− hides: [stages, launcher, panel.indicators]
137− shown: disappearingAnimationProgress !== 1.0 && greeterWrapper.showProgress !== 1.0 &&
138− !(panel.indicators.fullyOpened && !sideStageEnabled)
139− enabled: disappearingAnimationProgress === 0.0 && greeterWrapper.showProgress === 0.0 && edgeDemo.dashEnabled
140−
141− anchors {
142− fill: parent
143− topMargin: panel.panelHeight
144− }
145−
146− contentScale: 1.0 - 0.2 * disappearingAnimationProgress
147− opacity: 1.0 - disappearingAnimationProgress
148− property real disappearingAnimationProgress: stages.showProgress
149−
150− // FIXME: only necessary because stages.showProgress is not animated
151− Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
152− }
153− }
154−
155− EdgeDragArea {
156− id: stagesDragArea
157− direction: Direction.Leftwards
158−
159− anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
160− width: shell.edgeSize
161−
162− property real progress: stages.width
163−
164− onTouchXChanged: {
165− if (status == DirectionalDragArea.Recognized) {
166− if (ApplicationManager.empty) {
167− progress = Math.max(stages.width - stagesDragArea.width + touchX, stages.width * .3);
168− } else {
169− progress = stages.width - stagesDragArea.width + touchX;
170− }
171− }
172− }
173−
174− onDraggingChanged: {
175− if (!dragging) {
176− if (!ApplicationManager.empty && progress < stages.width - units.gu(10)) {
177− stages.show();
178− }
179− stagesDragArea.progress = Qt.binding(function () { return stages.width; });
180− }
181− }
182 }
132 }
183
133
184 Item {
134 Item {
@@ -186,55 +136,7 @@ Item {
186 objectName: "stages"
136 objectName: "stages"
187 width: parent.width
137 width: parent.width
188 height: parent.height
138 height: parent.height
189−
190− visible: !fullyHidden && !ApplicationManager.empty
139+ visible: !ApplicationManager.empty
191−
192− x: {
193− if (shown) {
194− if (locked || greeter.fakeActiveForApp !== "") {
195− return 0;
196− }
197− return launcher.progress;
198− } else {
199− return stagesDragArea.progress
200− }
201− }
202− Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.FastDuration } }
203−
204− property bool shown: false
205− onShownChanged: {
206− if (shown) {
207− if (ApplicationManager.count > 0) {
208− ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
209− }
210− } else {
211− if (ApplicationManager.focusedApplicationId) {
212− ApplicationManager.updateScreenshot(ApplicationManager.focusedApplicationId);
213− ApplicationManager.unfocusCurrentApplication();
214− }
215− }
216− }
217−
218− // Avoid a silent "divide by zero -> NaN" situation during init as shell.width will be
219− // zero. That breaks the property binding and the function won't be reevaluated once
220− // shell.width is set, with the NaN result staying there for good.
221− property real showProgress: shell.width ? MathUtils.clamp(1 - x / shell.width, 0, 1) : 0
222−
223− property bool fullyShown: x == 0
224− property bool fullyHidden: x == width
225−
226− property bool locked: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.locked : false
227−
228− // It might technically not be fullyShown but visually it just looks so.
229− property bool roughlyFullyShown: x >= 0 && x <= units.gu(1)
230−
231− function show() {
232− shown = true;
233− }
234−
235− function hide() {
236− shown = false;
237− }
238
140
239 Connections {
141 Connections {
240 target: ApplicationManager
142 target: ApplicationManager
@@ -243,7 +145,6 @@ Item {
243 lockscreen.show();
145 lockscreen.show();
244 }
146 }
245 greeter.hide();
147 greeter.hide();
246− stages.show();
247 }
148 }
248
149
249 onFocusedApplicationIdChanged: {
150 onFocusedApplicationIdChanged: {
@@ -254,18 +155,9 @@ Item {
254 }
155 }
255
156
256 onApplicationAdded: {
157 onApplicationAdded: {
257− if (greeter.shown) {
158+ if (greeter.shown && appId != "unity8-dash") {
258 greeter.hide();
159 greeter.hide();
259 }
160 }
260− if (!stages.shown) {
261− stages.show();
262− }
263− }
264−
265− onEmptyChanged: {
266− if (ApplicationManager.empty) {
267− stages.hide();
268− }
269 }
161 }
270 }
162 }
271
163
@@ -294,14 +186,18 @@ Item {
294 Binding {
186 Binding {
295 target: applicationsDisplayLoader.item
187 target: applicationsDisplayLoader.item
296 property: "interactive"
188 property: "interactive"
297− value: stages.roughlyFullyShown && !greeter.shown && !lockscreen.shown
298− && panel.indicators.fullyClosed
189+ value: !greeter.shown && !lockscreen.shown && panel.indicators.fullyClosed && launcher.progress == 0
299 }
190 }
300 Binding {
191 Binding {
301 target: applicationsDisplayLoader.item
192 target: applicationsDisplayLoader.item
302 property: "spreadEnabled"
193 property: "spreadEnabled"
303 value: greeter.fakeActiveForApp === "" // to support emergency dialer hack
194 value: greeter.fakeActiveForApp === "" // to support emergency dialer hack
304 }
195 }
196+ Binding {
197+ target: applicationsDisplayLoader.item
198+ property: "inverseProgress"
199+ value: launcher.progress
200+ }
305 }
201 }
306 }
202 }
307
203
@@ -348,6 +244,7 @@ Item {
348 width: parent.width
244 width: parent.width
349 height: parent.height - panel.panelHeight
245 height: parent.height - panel.panelHeight
350 background: shell.background
246 background: shell.background
247+ alphaNumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
351 minPinLength: 4
248 minPinLength: 4
352 maxPinLength: 4
249 maxPinLength: 4
353
250
@@ -362,7 +259,7 @@ Item {
362 onShownChanged: if (shown) greeter.fakeActiveForApp = ""
259 onShownChanged: if (shown) greeter.fakeActiveForApp = ""
363
260
364 Component.onCompleted: {
261 Component.onCompleted: {
365− if (LightDM.Users.count == 1) {
262+ if (greeter.narrowMode) {
366 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
263 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
367 }
264 }
368 }
265 }
@@ -374,14 +271,17 @@ Item {
374 onShowGreeter: greeter.show()
271 onShowGreeter: greeter.show()
375
272
376 onShowPrompt: {
273 onShowPrompt: {
377− if (LightDM.Users.count == 1) {
274+ if (greeter.narrowMode) {
378− // TODO: There's no better way for now to determine if its a PIN or a passphrase.
379− if (text == "PIN") {
380− lockscreen.alphaNumeric = false
381− } else {
382− lockscreen.alphaNumeric = true
383− }
384− lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
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();
385 lockscreen.show();
285 lockscreen.show();
386 }
286 }
387 }
287 }
@@ -395,6 +295,9 @@ Item {
395 greeter.login();
295 greeter.login();
396 } else {
296 } else {
397 lockscreen.clear(true);
297 lockscreen.clear(true);
298+ if (greeter.narrowMode) {
299+ LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
300+ }
398 }
301 }
399 }
302 }
400 }
303 }
@@ -449,25 +352,24 @@ Item {
449
352
450 function login() {
353 function login() {
451 enabled = false;
354 enabled = false;
452− LightDM.Greeter.startSessionSync();
355+ if (LightDM.Greeter.startSessionSync()) {
453− sessionStarted();
356+ sessionStarted();
454− greeter.hide();
357+ greeter.hide();
455− lockscreen.hide();
358+ lockscreen.hide();
456− launcher.hide();
359+ launcher.hide();
360+ }
457 enabled = true;
361 enabled = true;
458 }
362 }
459
363
460 onShownChanged: {
364 onShownChanged: {
461 if (shown) {
365 if (shown) {
462− lockscreen.reset();
366+ if (greeter.narrowMode) {
463− if (!LightDM.Greeter.promptless) {
464− lockscreen.show();
465− }
466− // If there is only one user, we start authenticating with that one here.
467− // If there are more users, the Greeter will handle that
468− if (LightDM.Users.count == 1) {
469 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
367 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
470 }
368 }
369+ if (!LightDM.Greeter.promptless) {
370+ lockscreen.reset();
371+ lockscreen.show();
372+ }
471 greeter.fakeActiveForApp = "";
373 greeter.fakeActiveForApp = "";
472 greeter.forceActiveFocus();
374 greeter.forceActiveFocus();
473 }
375 }
@@ -525,25 +427,20 @@ Item {
525
427
526 var animate = !LightDM.Greeter.active && !stages.shown
428 var animate = !LightDM.Greeter.active && !stages.shown
527 dash.setCurrentScope("clickscope", animate, false)
429 dash.setCurrentScope("clickscope", animate, false)
528− stages.hide()
430+ ApplicationManager.requestFocusApplication("unity8-dash")
529 }
431 }
530
432
531 function showDash() {
433 function showDash() {
532 if (LightDM.Greeter.active && !LightDM.Greeter.promptless) {
434 if (LightDM.Greeter.active && !LightDM.Greeter.promptless) {
533 return;
435 return;
534 }
436 }
535−
536− if (!stages.locked) {
537− stages.hide();
538− launcher.fadeOut();
539− } else {
540− launcher.switchToNextState("visible");
541− }
542−
543 if (greeter.shown) {
437 if (greeter.shown) {
544 greeter.hideRight();
438 greeter.hideRight();
545 launcher.fadeOut();
439 launcher.fadeOut();
546 }
440 }
441+
442+ ApplicationManager.requestFocusApplication("unity8-dash")
443+ launcher.fadeOut();
547 }
444 }
548
445
549 Item {
446 Item {
@@ -568,8 +465,8 @@ Item {
568 ApplicationManager.focusedApplicationId &&
465 ApplicationManager.focusedApplicationId &&
569 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
466 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
570
467
571− fullscreenMode: (stages.roughlyFullyShown && topmostApplicationIsFullscreen
468+ fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
572− && !LightDM.Greeter.active) || greeter.fakeActiveForApp !== ""
469+ || greeter.fakeActiveForApp !== ""
573 }
470 }
574
471
575 Launcher {
472 Launcher {
@@ -586,7 +483,11 @@ Item {
586
483
587 onShowDashHome: showHome()
484 onShowDashHome: showHome()
588 onDash: showDash()
485 onDash: showDash()
486+ onDashSwipeChanged: {
487+ if (dashSwipe && ApplicationManager.focusedApplicationId !== "unity8-dash") {
589− onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
488+ dash.setCurrentScope("clickscope", false, true)
489+ }
490+ }
590 onLauncherApplicationSelected: {
491 onLauncherApplicationSelected: {
591 if (greeter.fakeActiveForApp !== "") {
492 if (greeter.fakeActiveForApp !== "") {
592 lockscreen.show()
493 lockscreen.show()
@@ -678,7 +579,6 @@ Item {
678 z: alphaDisclaimerLabel.z + 10
579 z: alphaDisclaimerLabel.z + 10
679 greeter: greeter
580 greeter: greeter
680 launcher: launcher
581 launcher: launcher
681− dash: dash
682 indicators: panel.indicators
582 indicators: panel.indicators
683 underlay: underlay
583 underlay: underlay
684 }
584 }
qml/Stages/PhoneStage.qml+59−26
@@ -21,7 +21,7 @@ import Unity.Application 0.1
21import Utils 0.1
21import Utils 0.1
22import "../Components"
22import "../Components"
23
23
24−Item {
24+Rectangle {
25 id: root
25 id: root
26
26
27 // Controls to be set from outside
27 // Controls to be set from outside
@@ -29,9 +29,9 @@ Item {
29 property real maximizedAppTopMargin
29 property real maximizedAppTopMargin
30 property bool interactive
30 property bool interactive
31 property bool spreadEnabled: true // If false, animations and right edge will be disabled
31 property bool spreadEnabled: true // If false, animations and right edge will be disabled
32+ property real inverseProgress: 0 // This is the progress for left edge drags, in pixels.
32
33
33− // State information propagated to the outside
34+ color: "black"
34− readonly property bool locked: spreadView.phase == 2
35
35
36 function select(appId) {
36 function select(appId) {
37 spreadView.snapTo(priv.indexOf(appId));
37 spreadView.snapTo(priv.indexOf(appId));
@@ -43,6 +43,16 @@ Item {
43 spreadView.contentX = -spreadView.shift;
43 spreadView.contentX = -spreadView.shift;
44 }
44 }
45
45
46+ onInverseProgressChanged: {
47+ if (inverseProgress == 0 && priv.oldInverseProgress > 0) {
48+ // left edge drag released. Minimum distance is given by design.
49+ if (priv.oldInverseProgress > units.gu(22)) {
50+ ApplicationManager.focusApplication("unity8-dash");
51+ }
52+ }
53+ priv.oldInverseProgress = inverseProgress;
54+ }
55+
46 Connections {
56 Connections {
47 target: ApplicationManager
57 target: ApplicationManager
48
58
@@ -50,7 +60,7 @@ Item {
50 if (spreadView.phase > 0) {
60 if (spreadView.phase > 0) {
51 spreadView.snapTo(priv.indexOf(appId));
61 spreadView.snapTo(priv.indexOf(appId));
52 } else {
62 } else {
53− priv.switchToApp(appId);
63+ ApplicationManager.focusApplication(appId);
54 }
64 }
55 }
65 }
56
66
@@ -60,7 +70,7 @@ Item {
60 } else {
70 } else {
61 spreadView.phase = 0;
71 spreadView.phase = 0;
62 spreadView.contentX = -spreadView.shift;
72 spreadView.contentX = -spreadView.shift;
63− priv.switchToApp(appId);
73+ ApplicationManager.focusApplication(appId);
64 }
74 }
65 }
75 }
66
76
@@ -79,15 +89,11 @@ Item {
79
89
80 property string focusedAppId: ApplicationManager.focusedApplicationId
90 property string focusedAppId: ApplicationManager.focusedApplicationId
81 property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
91 property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
92+ property var focusedAppDelegate: null
82
93
83− function switchToApp(appId) {
94+ property real oldInverseProgress: 0
84− if (priv.focusedAppId) {
95+
85− spreadView.focusChanging = true;
96+ onFocusedAppIdChanged: focusedAppDelegate = spreadRepeater.itemAt(0);
86− ApplicationManager.focusApplication(appId);
87− } else {
88− ApplicationManager.focusApplication(appId);
89− }
90− }
91
97
92 function indexOf(appId) {
98 function indexOf(appId) {
93 for (var i = 0; i < ApplicationManager.count; i++) {
99 for (var i = 0; i < ApplicationManager.count; i++) {
@@ -100,14 +106,6 @@ Item {
100
106
101 }
107 }
102
108
103− Rectangle {
104− id: coverFlipBackground
105− anchors.fill: parent
106− color: "black"
107− visible: spreadView.visible
108− }
109−
110−
111 Flickable {
109 Flickable {
112 id: spreadView
110 id: spreadView
113 objectName: "spreadView"
111 objectName: "spreadView"
@@ -116,6 +114,10 @@ Item {
116 contentWidth: spreadRow.width - shift
114 contentWidth: spreadRow.width - shift
117 contentX: -shift
115 contentX: -shift
118
116
117+ // This indicates when the spreadView is active. That means, all the animations
118+ // are activated and tiles need to line up for the spread.
119+ readonly property bool active: shiftedContentX > 0 || spreadDragArea.dragging
120+
119 // The flickable needs to fill the screen in order to get touch events all over.
121 // The flickable needs to fill the screen in order to get touch events all over.
120 // However, we don't want to the user to be able to scroll back all the way. For
122 // However, we don't want to the user to be able to scroll back all the way. For
121 // that, the beginning of the gesture starts with a negative value for contentX
123 // that, the beginning of the gesture starts with a negative value for contentX
@@ -198,6 +200,12 @@ Item {
198 snapAnimation.start();
200 snapAnimation.start();
199 }
201 }
200
202
203+ // In case the ApplicationManager already holds an app when starting up we're missing animations
204+ // Make sure we end up in the same state
205+ Component.onCompleted: {
206+ spreadView.contentX = -spreadView.shift
207+ }
208+
201 SequentialAnimation {
209 SequentialAnimation {
202 id: snapAnimation
210 id: snapAnimation
203 property int targetContentX: -spreadView.shift
211 property int targetContentX: -spreadView.shift
@@ -260,14 +268,33 @@ Item {
260 && spreadView.shiftedContentX === 0 && root.interactive && index === 0
268 && spreadView.shiftedContentX === 0 && root.interactive && index === 0
261 swipeToCloseEnabled: spreadView.interactive
269 swipeToCloseEnabled: spreadView.interactive
262 maximizedAppTopMargin: root.maximizedAppTopMargin
270 maximizedAppTopMargin: root.maximizedAppTopMargin
263− dropShadow: spreadView.shiftedContentX > 0 || spreadDragArea.status == DirectionalDragArea.Undecided
271+ dropShadow: spreadView.active ||
272+ priv.focusedAppDelegate.x !== 0
273+
274+ readonly property bool isDash: model.appId == "unity8-dash"
275+
276+ z: isDash && !spreadView.active ? -1 : behavioredIndex
264
277
265− z: behavioredIndex
278+ x: {
279+ // focused app is always positioned at 0 except when following left edge drag
280+ if (index == 0) {
281+ if (!isDash && root.inverseProgress > 0) {
282+ return root.inverseProgress;
283+ }
284+ return 0;
285+ }
286+ if (isDash && !spreadView.active && !spreadDragArea.dragging) {
287+ return 0;
288+ }
289+
290+ // Otherwise line up for the spread
266− x: index == 0 ? 0 : spreadView.width + (index - 1) * spreadView.tileDistance
291+ return spreadView.width + (index - 1) * spreadView.tileDistance;
292+ }
267 property real behavioredIndex: index
293 property real behavioredIndex: index
268 Behavior on behavioredIndex {
294 Behavior on behavioredIndex {
269 enabled: spreadView.closingIndex >= 0
295 enabled: spreadView.closingIndex >= 0
270 UbuntuNumberAnimation {
296 UbuntuNumberAnimation {
297+ id: appXAnimation
271 onRunningChanged: {
298 onRunningChanged: {
272 if (!running) {
299 if (!running) {
273 spreadView.closingIndex = -1;
300 spreadView.closingIndex = -1;
@@ -277,11 +304,13 @@ Item {
277 }
304 }
278
305
279 Behavior on x {
306 Behavior on x {
280− enabled: spreadView.focusChanging && index == 0 && root.spreadEnabled
307+ enabled: root.spreadEnabled &&
308+ !spreadView.active &&
309+ !snapAnimation.running
281 UbuntuNumberAnimation {
310 UbuntuNumberAnimation {
282 duration: UbuntuAnimation.FastDuration
311 duration: UbuntuAnimation.FastDuration
283 onRunningChanged: {
312 onRunningChanged: {
284− if (!running) {
313+ if (!running && root.inverseProgress == 0) {
285 spreadView.focusChanging = false;
314 spreadView.focusChanging = false;
286 }
315 }
287 }
316 }
@@ -322,6 +351,10 @@ Item {
322 return progress;
351 return progress;
323 }
352 }
324
353
354+ // Hiding tiles when their progress is negative or reached the maximum
355+ visible: (progress >= 0 && progress < 1.7) ||
356+ (isDash && priv.focusedAppDelegate.x !== 0)
357+
325 EasingCurve {
358 EasingCurve {
326 id: snappingCurve
359 id: snappingCurve
327 type: EasingCurve.Linear
360 type: EasingCurve.Linear
qml/Stages/SpreadDelegate.qml+6
@@ -37,6 +37,7 @@ Item {
37
37
38 SurfaceContainer {
38 SurfaceContainer {
39 id: surfaceContainer
39 id: surfaceContainer
40+ objectName: "surfaceContainer"
40 anchors.fill: parent
41 anchors.fill: parent
41 surface: model.surface
42 surface: model.surface
42 property bool appHasCreatedASurface: false
43 property bool appHasCreatedASurface: false
@@ -172,6 +173,11 @@ Item {
172 }
173 }
173
174
174 onDragEnd: {
175 onDragEnd: {
176+ if (model.appId == "unity8-dash") {
177+ animation.animate("center")
178+ return;
179+ }
180+
175 // velocity and distance values specified by design prototype
181 // velocity and distance values specified by design prototype
176 if ((dragVelocity < -units.gu(40) && distance < -units.gu(8)) || distance < -root.height / 2) {
182 if ((dragVelocity < -units.gu(40) && distance < -units.gu(8)) || distance < -root.height / 2) {
177 animation.animate("up")
183 animation.animate("up")
qml/Stages/TabletStage.qml+85−41
@@ -21,10 +21,11 @@ import Unity.Application 0.1
21import Utils 0.1
21import Utils 0.1
22import "../Components"
22import "../Components"
23
23
24−Item {
24+Rectangle {
25 id: root
25 id: root
26 objectName: "stages"
26 objectName: "stages"
27 anchors.fill: parent
27 anchors.fill: parent
28+ color: "black"
28
29
29 // Controls to be set from outside
30 // Controls to be set from outside
30 property bool shown: false
31 property bool shown: false
@@ -32,9 +33,17 @@ Item {
32 property int dragAreaWidth
33 property int dragAreaWidth
33 property real maximizedAppTopMargin
34 property real maximizedAppTopMargin
34 property bool interactive
35 property bool interactive
36+ property real inverseProgress: 0 // This is the progress for left edge drags, in pixels.
35
37
36− // State information propagated to the outside
38+ onInverseProgressChanged: {
37− readonly property bool locked: spreadView.phase == 2
39+ if (inverseProgress == 0 && priv.oldInverseProgress > 0) {
40+ // left edge drag released. Minimum distance is given by design.
41+ if (priv.oldInverseProgress > units.gu(22)) {
42+ ApplicationManager.focusApplication("unity8-dash");
43+ }
44+ }
45+ priv.oldInverseProgress = inverseProgress;
46+ }
38
47
39 QtObject {
48 QtObject {
40 id: priv
49 id: priv
@@ -49,6 +58,8 @@ Item {
49 property string appId0
58 property string appId0
50 property string appId1
59 property string appId1
51
60
61+ property int oldInverseProgress: 0
62+
52 onFocusedAppIdChanged: {
63 onFocusedAppIdChanged: {
53 if (priv.focusedAppId.length > 0) {
64 if (priv.focusedAppId.length > 0) {
54 var focusedApp = ApplicationManager.findApplication(focusedAppId);
65 var focusedApp = ApplicationManager.findApplication(focusedAppId);
@@ -107,16 +118,26 @@ Item {
107 }
118 }
108 }
119 }
109
120
121+ onApplicationAdded: {
122+ if (spreadView.phase == 2) {
123+ spreadView.snapTo(ApplicationManager.count - 1);
124+ } else {
125+ spreadView.phase = 0;
126+ spreadView.contentX = -spreadView.shift;
127+ ApplicationManager.focusApplication(appId);
128+ }
129+ }
130+
110 onApplicationRemoved: {
131 onApplicationRemoved: {
111 if (priv.mainStageAppId == appId) {
132 if (priv.mainStageAppId == appId) {
112− priv.mainStageAppId = "";
133+ ApplicationManager.focusApplication("unity8-dash")
113 }
134 }
114 if (priv.sideStageAppId == appId) {
135 if (priv.sideStageAppId == appId) {
115 priv.sideStageAppId = "";
136 priv.sideStageAppId = "";
116 }
137 }
117 if (ApplicationManager.count == 0) {
138 if (ApplicationManager.count == 0) {
118 spreadView.phase = 0;
139 spreadView.phase = 0;
119− spreadView.contentX = 0;
140+ spreadView.contentX = -spreadView.shift;
120 }
141 }
121 }
142 }
122 }
143 }
@@ -124,13 +145,26 @@ Item {
124 Flickable {
145 Flickable {
125 id: spreadView
146 id: spreadView
126 anchors.fill: parent
147 anchors.fill: parent
127− contentWidth: spreadRow.width
128 interactive: (spreadDragArea.status == DirectionalDragArea.Recognized || phase > 1) && draggedIndex == -1
148 interactive: (spreadDragArea.status == DirectionalDragArea.Recognized || phase > 1) && draggedIndex == -1
149+ contentWidth: spreadRow.width - shift
150+ contentX: -shift
129
151
130 property int tileDistance: units.gu(20)
152 property int tileDistance: units.gu(20)
131 property int sideStageWidth: units.gu(40)
153 property int sideStageWidth: units.gu(40)
132 property bool sideStageVisible: priv.sideStageAppId
154 property bool sideStageVisible: priv.sideStageAppId
133
155
156+ // This indicates when the spreadView is active. That means, all the animations
157+ // are activated and tiles need to line up for the spread.
158+ readonly property bool active: shiftedContentX > 0 || spreadDragArea.dragging
159+
160+ // The flickable needs to fill the screen in order to get touch events all over.
161+ // However, we don't want to the user to be able to scroll back all the way. For
162+ // that, the beginning of the gesture starts with a negative value for contentX
163+ // so the flickable wants to pull it into the view already. "shift" tunes the
164+ // distance where to "lock" the content.
165+ readonly property real shift: width / 2
166+ readonly property real shiftedContentX: contentX + shift
167+
134 // Phase of the animation:
168 // Phase of the animation:
135 // 0: Starting from right edge, a new app (index 1) comes in from the right
169 // 0: Starting from right edge, a new app (index 1) comes in from the right
136 // 1: The app has reached the first snap position.
170 // 1: The app has reached the first snap position.
@@ -168,6 +202,12 @@ Item {
168 }
202 }
169 }
203 }
170
204
205+ // In case the ApplicationManager already holds an app when starting up we're missing animations
206+ // Make sure we end up in the same state
207+ Component.onCompleted: {
208+ spreadView.contentX = -spreadView.shift
209+ }
210+
171 property int nextInStack: {
211 property int nextInStack: {
172 switch (state) {
212 switch (state) {
173 case "main":
213 case "main":
@@ -224,31 +264,31 @@ Item {
224 return "empty";
264 return "empty";
225 }
265 }
226
266
227− onContentXChanged: {
267+ onShiftedContentXChanged: {
228− if (spreadView.phase == 0 && spreadView.contentX > spreadView.width * spreadView.positionMarker2) {
268+ if (spreadView.phase == 0 && spreadView.shiftedContentX > spreadView.width * spreadView.positionMarker2) {
229 spreadView.phase = 1;
269 spreadView.phase = 1;
230− } else if (spreadView.phase == 1 && spreadView.contentX > spreadView.width * spreadView.positionMarker4) {
270+ } else if (spreadView.phase == 1 && spreadView.shiftedContentX > spreadView.width * spreadView.positionMarker4) {
231 spreadView.phase = 2;
271 spreadView.phase = 2;
232− } else if (spreadView.phase == 1 && spreadView.contentX < spreadView.width * spreadView.positionMarker2) {
272+ } else if (spreadView.phase == 1 && spreadView.shiftedContentX < spreadView.width * spreadView.positionMarker2) {
233 spreadView.phase = 0;
273 spreadView.phase = 0;
234 }
274 }
235 }
275 }
236
276
237 function snap() {
277 function snap() {
238− if (contentX < phase0Width) {
278+ if (shiftedContentX < phase0Width) {
239− snapAnimation.targetContentX = 0;
279+ snapAnimation.targetContentX = -shift;
240 snapAnimation.start();
280 snapAnimation.start();
241− } else if (contentX < phase1Width) {
281+ } else if (shiftedContentX < phase1Width) {
242 snapTo(1);
282 snapTo(1);
243 } else {
283 } else {
244 // Add 1 pixel to make sure we definitely hit positionMarker4 even with rounding errors of the animation.
284 // Add 1 pixel to make sure we definitely hit positionMarker4 even with rounding errors of the animation.
245− snapAnimation.targetContentX = spreadView.width * spreadView.positionMarker4 + 1;
285+ snapAnimation.targetContentX = spreadView.width * spreadView.positionMarker4 + 1 - shift;
246 snapAnimation.start();
286 snapAnimation.start();
247 }
287 }
248 }
288 }
249 function snapTo(index) {
289 function snapTo(index) {
250 spreadView.selectedIndex = index;
290 spreadView.selectedIndex = index;
251− snapAnimation.targetContentX = 0;
291+ snapAnimation.targetContentX = -shift;
252 snapAnimation.start();
292 snapAnimation.start();
253 }
293 }
254
294
@@ -261,12 +301,12 @@ Item {
261 return index;
301 return index;
262 }
302 }
263
303
264− var isActive = app.appId == priv.mainStageAppId || app.appId == priv.sideStageAppId;
304+ var active = app.appId == priv.mainStageAppId || app.appId == priv.sideStageAppId;
265− if (isActive && app.stage == ApplicationInfoInterface.MainStage) {
305+ if (active && app.stage == ApplicationInfoInterface.MainStage) {
266 // if this app is active, and its the MainStage, always put it to index 0
306 // if this app is active, and its the MainStage, always put it to index 0
267 return 0;
307 return 0;
268 }
308 }
269− if (isActive && app.stage == ApplicationInfoInterface.SideStage) {
309+ if (active && app.stage == ApplicationInfoInterface.SideStage) {
270 if (!priv.mainStageAppId) {
310 if (!priv.mainStageAppId) {
271 // Only have SS apps running. Put the active one at 0
311 // Only have SS apps running. Put the active one at 0
272 return 0;
312 return 0;
@@ -309,7 +349,7 @@ Item {
309
349
310 SequentialAnimation {
350 SequentialAnimation {
311 id: snapAnimation
351 id: snapAnimation
312− property int targetContentX: 0
352+ property int targetContentX: -spreadView.shift
313
353
314 UbuntuNumberAnimation {
354 UbuntuNumberAnimation {
315 target: spreadView
355 target: spreadView
@@ -325,15 +365,14 @@ Item {
325 spreadView.selectedIndex = -1;
365 spreadView.selectedIndex = -1;
326 ApplicationManager.focusApplication(ApplicationManager.get(newIndex).appId);
366 ApplicationManager.focusApplication(ApplicationManager.get(newIndex).appId);
327 spreadView.phase = 0;
367 spreadView.phase = 0;
328− spreadView.contentX = 0;
368+ spreadView.contentX = -spreadView.shift;
329 }
369 }
330 }
370 }
331 }
371 }
332 }
372 }
333
373
334− Rectangle {
374+ Item {
335 id: spreadRow
375 id: spreadRow
336− color: "black"
337 x: spreadView.contentX
376 x: spreadView.contentX
338 height: root.height
377 height: root.height
339 width: spreadView.width + Math.max(spreadView.width, ApplicationManager.count * spreadView.tileDistance)
378 width: spreadView.width + Math.max(spreadView.width, ApplicationManager.count * spreadView.tileDistance)
@@ -380,7 +419,7 @@ Item {
380 MouseArea {
419 MouseArea {
381 id: sideStageDragHandleMouseArea
420 id: sideStageDragHandleMouseArea
382 anchors.fill: parent
421 anchors.fill: parent
383− enabled: spreadView.contentX == 0
422+ enabled: spreadView.shiftedContentX == 0
384 property int startX
423 property int startX
385 property var gesturePoints: new Array()
424 property var gesturePoints: new Array()
386
425
@@ -413,7 +452,7 @@ Item {
413
452
414 onRunningChanged: {
453 onRunningChanged: {
415 if (!running) {
454 if (!running) {
416− sideStageDragHandle.dragging = false;;
455+ sideStageDragHandle.dragging = false;
417 }
456 }
418 }
457 }
419 }
458 }
@@ -427,17 +466,26 @@ Item {
427 id: spreadTile
466 id: spreadTile
428 height: spreadView.height
467 height: spreadView.height
429 width: model.stage == ApplicationInfoInterface.MainStage ? spreadView.width : spreadView.sideStageWidth
468 width: model.stage == ApplicationInfoInterface.MainStage ? spreadView.width : spreadView.sideStageWidth
430− x: spreadView.width
431− z: spreadView.indexToZIndex(index)
432 active: model.appId == priv.mainStageAppId || model.appId == priv.sideStageAppId
469 active: model.appId == priv.mainStageAppId || model.appId == priv.sideStageAppId
433− zIndex: z
470+ zIndex: spreadView.indexToZIndex(index)
434 selected: spreadView.selectedIndex == index
471 selected: spreadView.selectedIndex == index
435 otherSelected: spreadView.selectedIndex >= 0 && !selected
472 otherSelected: spreadView.selectedIndex >= 0 && !selected
436 isInSideStage: priv.sideStageAppId == model.appId
473 isInSideStage: priv.sideStageAppId == model.appId
437 interactive: !spreadView.interactive && spreadView.phase === 0 && root.interactive
474 interactive: !spreadView.interactive && spreadView.phase === 0 && root.interactive
438 swipeToCloseEnabled: spreadView.interactive
475 swipeToCloseEnabled: spreadView.interactive
439 maximizedAppTopMargin: root.maximizedAppTopMargin
476 maximizedAppTopMargin: root.maximizedAppTopMargin
440− dropShadow: spreadView.contentX > 0 || spreadDragArea.status == DirectionalDragArea.Undecided
477+ dragOffset: !isDash && model.appId == priv.mainStageAppId && root.inverseProgress > 0 ? root.inverseProgress : 0
478+
479+ readonly property bool isDash: model.appId == "unity8-dash"
480+
481+ // FIXME: A regular binding doesn't update any more after closing an app.
482+ // Using a Binding for now.
483+ Binding {
484+ target: spreadTile
485+ property: "z"
486+ value: (!spreadView.active && isDash && !active) ? -1 : spreadTile.zIndex
487+ }
488+ x: spreadView.width
441
489
442 property real behavioredZIndex: zIndex
490 property real behavioredZIndex: zIndex
443 Behavior on behavioredZIndex {
491 Behavior on behavioredZIndex {
@@ -458,7 +506,7 @@ Item {
458 }
506 }
459
507
460 progress: {
508 progress: {
461− var tileProgress = (spreadView.contentX - behavioredZIndex * spreadView.tileDistance) / spreadView.width;
509+ var tileProgress = (spreadView.shiftedContentX - behavioredZIndex * spreadView.tileDistance) / spreadView.width;
462 // Some tiles (nextInStack, active) need to move directly from the beginning, normalize progress to immediately start at 0
510 // Some tiles (nextInStack, active) need to move directly from the beginning, normalize progress to immediately start at 0
463 if ((index == spreadView.nextInStack && spreadView.phase < 2) || (active && spreadView.phase < 1)) {
511 if ((index == spreadView.nextInStack && spreadView.phase < 2) || (active && spreadView.phase < 1)) {
464 tileProgress += behavioredZIndex * spreadView.tileDistance / spreadView.width;
512 tileProgress += behavioredZIndex * spreadView.tileDistance / spreadView.width;
@@ -481,11 +529,7 @@ Item {
481
529
482 onClicked: {
530 onClicked: {
483 if (spreadView.phase == 2) {
531 if (spreadView.phase == 2) {
484− if (ApplicationManager.focusedApplicationId == ApplicationManager.get(index).appId) {
485− spreadView.snapTo(index);
532+ spreadView.snapTo(index);
486− } else {
487− ApplicationManager.requestFocusApplication(ApplicationManager.get(index).appId);
488− }
489 }
533 }
490 }
534 }
491
535
@@ -518,12 +562,12 @@ Item {
518 onTouchXChanged: {
562 onTouchXChanged: {
519 if (!dragging) {
563 if (!dragging) {
520 spreadView.phase = 0;
564 spreadView.phase = 0;
521− spreadView.contentX = 0;
565+ spreadView.contentX = -spreadView.shift;
522 }
566 }
523
567
524− if (attachedToView) {
568+ if (dragging && attachedToView) {
525− spreadView.contentX = -touchX + spreadDragArea.width;
569+ spreadView.contentX = -touchX + spreadDragArea.width - spreadView.shift;
526− if (spreadView.contentX > spreadView.phase0Width + spreadView.phase1Width / 2) {
570+ if (spreadView.shiftedContentX > spreadView.phase0Width + spreadView.phase1Width / 2) {
527 attachedToView = false;
571 attachedToView = false;
528 spreadView.snap();
572 spreadView.snap();
529 }
573 }
@@ -548,14 +592,14 @@ Item {
548 var oneWayFlick = priv.evaluateOneWayFlick(gesturePoints);
592 var oneWayFlick = priv.evaluateOneWayFlick(gesturePoints);
549 gesturePoints = [];
593 gesturePoints = [];
550
594
551− if (oneWayFlick && spreadView.contentX < spreadView.positionMarker1 * spreadView.width) {
595+ if (oneWayFlick && spreadView.shiftedContentX < spreadView.positionMarker1 * spreadView.width) {
552 // If it was a short one-way movement, do the Alt+Tab switch
596 // If it was a short one-way movement, do the Alt+Tab switch
553 // no matter if we didn't cross positionMarker1 yet.
597 // no matter if we didn't cross positionMarker1 yet.
554 spreadView.snapTo(spreadView.nextInStack);
598 spreadView.snapTo(spreadView.nextInStack);
555 } else if (!dragging && attachedToView) {
599 } else if (!dragging && attachedToView) {
556− if (spreadView.contentX < spreadView.width * spreadView.positionMarker1) {
600+ if (spreadView.shiftedContentX < spreadView.width * spreadView.positionMarker1) {
557 spreadView.snap();
601 spreadView.snap();
558− } else if (spreadView.contentX < spreadView.width * spreadView.positionMarker2) {
602+ } else if (spreadView.shiftedContentX < spreadView.width * spreadView.positionMarker2) {
559 spreadView.snapTo(spreadView.nextInStack);
603 spreadView.snapTo(spreadView.nextInStack);
560 } else {
604 } else {
561 // otherwise snap to the closest snap position we can find
605 // otherwise snap to the closest snap position we can find
qml/Stages/TransformedSpreadDelegate.qml+6−5
@@ -46,9 +46,6 @@ SpreadDelegate {
46 property real startDistance: units.gu(5)
46 property real startDistance: units.gu(5)
47 property real endDistance: units.gu(.5)
47 property real endDistance: units.gu(.5)
48
48
49− // Hiding tiles when their progress is negative or reached the maximum
50− visible: progress >= 0 && progress < 1.7
51−
52 onSelectedChanged: {
49 onSelectedChanged: {
53 if (selected) {
50 if (selected) {
54 priv.snapshot();
51 priv.snapshot();
@@ -132,6 +129,10 @@ SpreadDelegate {
132 // the selected tile, which is animated from the snapshotted position to be fullscreen.
129 // the selected tile, which is animated from the snapshotted position to be fullscreen.
133
130
134 readonly property real xTranslate: {
131 readonly property real xTranslate: {
132+ if (!spreadView.active) {
133+ return 0;
134+ }
135+
135 if (otherSelected) {
136 if (otherSelected) {
136 if (spreadView.phase < 2 && index == 0) {
137 if (spreadView.phase < 2 && index == 0) {
137 return linearAnimation(selectedProgress, 0, selectedXTranslate,
138 return linearAnimation(selectedProgress, 0, selectedXTranslate,
@@ -183,7 +184,7 @@ SpreadDelegate {
183 }
184 }
184
185
185 readonly property real angle: {
186 readonly property real angle: {
186− if (spreadView.focusChanging) {
187+ if (!spreadView.active) {
187 return 0;
188 return 0;
188 }
189 }
189
190
@@ -216,7 +217,7 @@ SpreadDelegate {
216 }
217 }
217
218
218 readonly property real scale: {
219 readonly property real scale: {
219− if (spreadView.focusChanging) {
220+ if (!spreadView.active) {
220 return 1;
221 return 1;
221 }
222 }
222 if (priv.otherSelected) {
223 if (priv.otherSelected) {
qml/Stages/TransformedTabletSpreadDelegate.qml+28
@@ -47,6 +47,11 @@ SpreadDelegate {
47
47
48 property bool isInSideStage: false
48 property bool isInSideStage: false
49
49
50+ property int dragOffset: 0
51+
52+ dropShadow: spreadView.active ||
53+ (active && model.stage == ApplicationInfoInterface.MainStage && priv.xTranslate != 0)
54+
50 onSelectedChanged: {
55 onSelectedChanged: {
51 if (selected) {
56 if (selected) {
52 priv.snapshot();
57 priv.snapshot();
@@ -129,6 +134,16 @@ SpreadDelegate {
129 return helperEasingCurve.value * (endValue - startValue) + startValue;
134 return helperEasingCurve.value * (endValue - startValue) + startValue;
130 }
135 }
131
136
137+ Behavior on xTranslate {
138+ enabled: !spreadView.active &&
139+ !snapAnimation.running &&
140+ model.appId !== "unity8-dash" &&
141+ !spreadView.sideStageDragging
142+ UbuntuNumberAnimation {
143+ duration: UbuntuAnimation.FastDuration
144+ }
145+ }
146+
132 property real xTranslate: {
147 property real xTranslate: {
133 var newTranslate = 0;
148 var newTranslate = 0;
134
149
@@ -155,7 +170,12 @@ SpreadDelegate {
155 if (spreadView.phase == 0 && shouldMoveAway) {
170 if (spreadView.phase == 0 && shouldMoveAway) {
156 newTranslate += linearAnimation(0, spreadView.positionMarker2, 0, -units.gu(4), root.animatedProgress);
171 newTranslate += linearAnimation(0, spreadView.positionMarker2, 0, -units.gu(4), root.animatedProgress);
157 }
172 }
173+ newTranslate += root.dragOffset;
174+ }
175+ if (!spreadView.active && model.appId == "unity8-dash" && !root.active) {
176+ newTranslate -= root.width;
158 }
177 }
178+
159 if (nextInStack && spreadView.phase == 0) {
179 if (nextInStack && spreadView.phase == 0) {
160 if (model.stage == ApplicationInfoInterface.MainStage) {
180 if (model.stage == ApplicationInfoInterface.MainStage) {
161 if (spreadView.sideStageVisible && root.progress > 0) {
181 if (spreadView.sideStageVisible && root.progress > 0) {
@@ -205,6 +225,10 @@ SpreadDelegate {
205 }
225 }
206
226
207 property real scale: {
227 property real scale: {
228+ if (!spreadView.active) {
229+ return 1;
230+ }
231+
208 if (otherSelected) {
232 if (otherSelected) {
209 return selectedScale;
233 return selectedScale;
210 }
234 }
@@ -249,6 +273,10 @@ SpreadDelegate {
249 }
273 }
250
274
251 property real angle: {
275 property real angle: {
276+ if (!spreadView.active) {
277+ return 0;
278+ }
279+
252 if (otherSelected) {
280 if (otherSelected) {
253 return selectedAngle;
281 return selectedAngle;
254 }
282 }
run.sh+15−6
@@ -8,6 +8,7 @@ GDB=false
8FAKE=false
8FAKE=false
9PINLOCK=false
9PINLOCK=false
10KEYLOCK=false
10KEYLOCK=false
11+USE_MOCKS=false
11MOUSE_TOUCH=true
12MOUSE_TOUCH=true
12
13
13usage() {
14usage() {
@@ -31,9 +32,9 @@ eval set -- "$ARGS"
31while [ $# -gt 0 ]
32while [ $# -gt 0 ]
32do
33do
33 case "$1" in
34 case "$1" in
34− -f|--fake) FAKE=true;;
35+ -f|--fake) FAKE=true; USE_MOCKS=true;;
35− -p|--pinlock) PINLOCK=true;;
36+ -p|--pinlock) PINLOCK=true; USE_MOCKS=true;;
36− -k|--keylock) KEYLOCK=true;;
37+ -k|--keylock) KEYLOCK=true; USE_MOCKS=true;;
37 -g|--gdb) GDB=true;;
38 -g|--gdb) GDB=true;;
38 -h|--help) usage;;
39 -h|--help) usage;;
39 -m|--nomousetouch) MOUSE_TOUCH=false;;
40 -m|--nomousetouch) MOUSE_TOUCH=false;;
@@ -43,20 +44,28 @@ do
43done
44done
44
45
45if $FAKE; then
46if $FAKE; then
46− export QML2_IMPORT_PATH=$QML2_IMPORT_PATH:$PWD/builddir/tests/mocks:$PWD/builddir/plugins:$PWD/builddir/modules
47 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/builddir/tests/mocks/libusermetrics:$PWD/builddir/tests/mocks/LightDM/single
47 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/builddir/tests/mocks/libusermetrics:$PWD/builddir/tests/mocks/LightDM/single
48fi
48fi
49
49
50if $PINLOCK; then
50if $PINLOCK; then
51− export QML2_IMPORT_PATH=$QML2_IMPORT_PATH:$PWD/builddir/tests/mocks:$PWD/builddir/plugins:$PWD/builddir/modules
52 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/builddir/tests/mocks/libusermetrics:$PWD/builddir/tests/mocks/LightDM/single-pin
51 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/builddir/tests/mocks/libusermetrics:$PWD/builddir/tests/mocks/LightDM/single-pin
53fi
52fi
54
53
55if $KEYLOCK; then
54if $KEYLOCK; then
56− export QML2_IMPORT_PATH=$QML2_IMPORT_PATH:$PWD/builddir/tests/mocks:$PWD/builddir/plugins:$PWD/builddir/modules
57 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/builddir/tests/mocks/libusermetrics:$PWD/builddir/tests/mocks/LightDM/single-passphrase
55 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/builddir/tests/mocks/libusermetrics:$PWD/builddir/tests/mocks/LightDM/single-passphrase
58fi
56fi
59
57
58+if $USE_MOCKS; then
59+ rm -f $PWD/builddir/nonmirplugins/LightDM # undo symlink (from below) for cleanliness
60+ export QML2_IMPORT_PATH=$QML2_IMPORT_PATH:$PWD/builddir/tests/mocks:$PWD/builddir/plugins:$PWD/builddir/modules
61+else
62+ # Still fake no-login user for convenience (it's annoying to be prompted for your password when testing)
63+ # And in particular, just link our LightDM mock into the nonmirplugins folder. We don't want the rest of
64+ # our plugins to be used.
65+ ln -s $PWD/builddir/tests/mocks/LightDM $PWD/builddir/nonmirplugins/
66+ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/builddir/tests/mocks/LightDM/single
67+fi
68+
60QML_PHONE_SHELL_ARGS=""
69QML_PHONE_SHELL_ARGS=""
61if $MOUSE_TOUCH; then
70if $MOUSE_TOUCH; then
62 QML_PHONE_SHELL_ARGS="$QML_PHONE_SHELL_ARGS -mousetouch"
71 QML_PHONE_SHELL_ARGS="$QML_PHONE_SHELL_ARGS -mousetouch"
src/CMakeLists.txt+1
@@ -35,3 +35,4 @@ install(TARGETS ${SHELL_APP}
35 )
35 )
36
36
37add_subdirectory(Panel)
37add_subdirectory(Panel)
38+add_subdirectory(Dash)
src/Dash/CMakeLists.txtnew file+12
@@ -0,0 +1,12 @@
1+set(DASH_SRCS
2+ main.cpp
3+)
4+
5+add_executable(unity8-dash ${DASH_SRCS})
6+
7+qt5_use_modules(unity8-dash Gui Qml Quick Test)
8+
9+# install binaries
10+install(TARGETS ${DASH_APP}
11+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
12+)
src/Dash/main.cppnew file+74
@@ -0,0 +1,74 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * Authors:
5+ * Michael Zanetti <michael.zanetti@canonical.com>
6+ *
7+ * This program is free software; you can redistribute it and/or modify
8+ * it under the terms of the GNU General Public License as published by
9+ * the Free Software Foundation; version 3.
10+ *
11+ * This program is distributed in the hope that it will be useful,
12+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+ * GNU General Public License for more details.
15+ *
16+ * You should have received a copy of the GNU General Public License
17+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
18+ */
19+
20+#include <QtQuick/QQuickView>
21+#include <QtGui/QGuiApplication>
22+#include <QtQml/QQmlEngine>
23+#include <QtQml/QQmlContext>
24+#include <QDebug>
25+#include <QCommandLineParser>
26+#include <QLibrary>
27+
28+#include <paths.h>
29+
30+int main(int argc, const char *argv[])
31+{
32+ QGuiApplication *application = new QGuiApplication(argc, (char**)argv);
33+
34+ QCommandLineParser parser;
35+ parser.setApplicationDescription("Description: Unity 8 Shell Dash");
36+ parser.addHelpOption();
37+
38+ QCommandLineOption testabilityOption("testability",
39+ "Load the testability driver (Alternatively export QT_LOAD_TESTABILITY");
40+ parser.addOption(testabilityOption);
41+
42+ if (parser.isSet(testabilityOption) || getenv("QT_LOAD_TESTABILITY")) {
43+ QLibrary testLib(QLatin1String("qttestability"));
44+ if (testLib.load()) {
45+ typedef void (*TasInitialize)(void);
46+ TasInitialize initFunction = (TasInitialize)testLib.resolve("qt_testability_init");
47+ if (initFunction) {
48+ initFunction();
49+ } else {
50+ qCritical("Library qttestability resolve failed!");
51+ }
52+ } else {
53+ qCritical("Library qttestability load failed!");
54+ }
55+ }
56+
57+ QQuickView* view = new QQuickView();
58+ view->setResizeMode(QQuickView::SizeRootObjectToView);
59+ view->setTitle("Unity Dash");
60+
61+ QUrl source(::qmlDirectory()+"Dash/DashApplication.qml");
62+ prependImportPaths(view->engine(), ::overrideImportPaths());
63+ appendImportPaths(view->engine(), ::fallbackImportPaths());
64+
65+ view->setSource(source);
66+ view->show();
67+
68+ int result = application->exec();
69+
70+ delete view;
71+ delete application;
72+
73+ return result;
74+}
src/main.cpp+7
@@ -25,6 +25,7 @@
25#include <QtQml/QQmlContext>
25#include <QtQml/QQmlContext>
26#include <QLibrary>
26#include <QLibrary>
27#include <QDebug>
27#include <QDebug>
28+#include <csignal>
28#include <libintl.h>
29#include <libintl.h>
29
30
30// local
31// local
@@ -140,6 +141,12 @@ Load the testability driver");
140 view->setSource(source);
141 view->setSource(source);
141 QObject::connect(view->engine(), SIGNAL(quit()), application, SLOT(quit()));
142 QObject::connect(view->engine(), SIGNAL(quit()), application, SLOT(quit()));
142
143
144+ if (!isMirServer && qEnvironmentVariableIsSet("UNITY_MIR_EMITS_SIGSTOP")) {
145+ // Emit SIGSTOP as expected by upstart, under Mir it's qtmir that will raise it.
146+ // see http://upstart.ubuntu.com/cookbook/#expect-stop
147+ raise(SIGSTOP);
148+ }
149+
143 if (isMirServer || parser.isSet(fullscreenOption)) {
150 if (isMirServer || parser.isSet(fullscreenOption)) {
144 view->showFullScreen();
151 view->showFullScreen();
145 } else {
152 } else {
tests/autopilot/unity8/application_lifecycle/tests/test_application_lifecycle.py+1−1
@@ -106,7 +106,7 @@ class ApplicationLifecycleTests(tests.ApplicationLifeCycleTestCase):
106 self.assert_current_focused_application(application_name)
106 self.assert_current_focused_application(application_name)
107
107
108 self.main_window.show_dash_swiping()
108 self.main_window.show_dash_swiping()
109− self.assert_current_focused_application('')
109+ self.assert_current_focused_application('unity8-dash')
110
110
111 process_helpers.lock_unity()
111 process_helpers.lock_unity()
112 greeter = self.main_window.get_greeter()
112 greeter = self.main_window.get_greeter()
tests/autopilot/unity8/application_lifecycle/tests/test_url_dispatcher.py+3−2
@@ -39,7 +39,8 @@ class URLDispatcherTestCase(tests.ApplicationLifeCycleTestCase):
39 desktop_file_name = os.path.basename(desktop_file_path)
39 desktop_file_name = os.path.basename(desktop_file_path)
40 application_name, _ = os.path.splitext(desktop_file_name)
40 application_name, _ = os.path.splitext(desktop_file_name)
41
41
42+ self.assertEqual(
42− self.assertEqual('', self.main_window.get_current_focused_app_id())
43+ 'unity8-dash', self.main_window.get_current_focused_app_id())
43 self.addCleanup(os.system, 'pkill qmlscene')
44 self.addCleanup(os.system, 'pkill qmlscene')
44
45
45 subprocess.check_call(
46 subprocess.check_call(
@@ -47,4 +48,4 @@ class URLDispatcherTestCase(tests.ApplicationLifeCycleTestCase):
47 self.assert_current_focused_application(application_name)
48 self.assert_current_focused_application(application_name)
48
49
49 self.main_window.show_dash_swiping()
50 self.main_window.show_dash_swiping()
50− self.assert_current_focused_application('')
51+ self.assert_current_focused_application('unity8-dash')
tests/autopilot/unity8/fixture_setup.pynew file+64
@@ -0,0 +1,64 @@
1+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2+#
3+# Unity Autopilot Test Suite
4+# Copyright (C) 2014 Canonical
5+#
6+# This program is free software: you can redistribute it and/or modify
7+# it under the terms of the GNU General Public License as published by
8+# the Free Software Foundation, either version 3 of the License, or
9+# (at your option) any later version.
10+#
11+# This program is distributed in the hope that it will be useful,
12+# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+# GNU General Public License for more details.
15+#
16+# You should have received a copy of the GNU General Public License
17+# along with this program. If not, see <http://www.gnu.org/licenses/>.
18+#
19+
20+import fixtures
21+from autopilot import introspection
22+
23+from unity8 import process_helpers
24+from unity8.shell import emulators
25+
26+
27+class LaunchDashApp(fixtures.Fixture):
28+
29+ """Fixture to launch the Dash app."""
30+
31+ def __init__(self, binary_path, variables):
32+ """Initialize an instance.
33+
34+ :param str binary_path: The path to the Dash app binary.
35+ :param variables: The variables to use when launching the app.
36+ :type variables: A dictionary.
37+
38+ """
39+ super(LaunchDashApp, self).__init__()
40+ self.binary_path = binary_path
41+ self.variables = variables
42+
43+ def setUp(self):
44+ """Launch the dash app when the fixture is used."""
45+ super(LaunchDashApp, self).setUp()
46+ self.addCleanup(self.stop_application)
47+ self.application_proxy = self.launch_application()
48+
49+ def launch_application(self):
50+ binary_arg = 'BINARY={}'.format(self.binary_path)
51+ testability_arg = 'QT_LOAD_TESTABILITY={}'.format(1)
52+ env_args = [
53+ '{}={}'.format(key, value) for key, value in self.variables.items()
54+ ]
55+ all_args = [binary_arg, testability_arg] + env_args
56+
57+ pid = process_helpers.start_job('unity8-dash', *all_args)
58+ return introspection.get_proxy_object_for_existing_process(
59+ pid=pid,
60+ emulator_base=emulators.UnityEmulatorBase,
61+ )
62+
63+ def stop_application(self):
64+ process_helpers.stop_job('unity8-dash')
tests/autopilot/unity8/process_helpers.py+89−20
@@ -36,6 +36,10 @@ from unity8.shell.emulators import main_window as main_window_emulator
36logger = logging.getLogger(__name__)
36logger = logging.getLogger(__name__)
37
37
38
38
39+class JobError(Exception):
40+ pass
41+
42+
39class CannotAccessUnity(Exception):
43class CannotAccessUnity(Exception):
40 pass
44 pass
41
45
@@ -134,48 +138,113 @@ def restart_unity(*args):
134 """
138 """
135 status = _get_unity_status()
139 status = _get_unity_status()
136 if "start/" in status:
140 if "start/" in status:
137− try:
141+ stop_job('unity8')
138− output = subprocess.check_output(
142+
139− ['/sbin/initctl', 'stop', 'unity8'])
143+ pid = start_job('unity8', *args)
140− logger.info(output)
144+ return _get_unity_proxy_object(pid)
141− except subprocess.CalledProcessError as e:
142− e.args += (
143− "Failed to stop running instance of unity8: %s" % e.output,
144− )
145− raise
146
145
146+
147+def start_job(name, *args):
148+ """Start a job.
149+
150+ :param str name: The name of the job.
151+ :param args: The arguments to be used when starting the job.
152+ :return: The process id of the started job.
153+ :raises CalledProcessError: if the job failed to start.
154+
155+ """
156+ logger.info('Starting job {} with arguments {}.'.format(name, args))
157+ command = ['/sbin/initctl', 'start', name] + list(args)
147 try:
158 try:
148− command = ['/sbin/initctl', 'start', 'unity8'] + list(args)
149 output = subprocess.check_output(
159 output = subprocess.check_output(
150 command,
160 command,
151 stderr=subprocess.STDOUT,
161 stderr=subprocess.STDOUT,
152 universal_newlines=True,
162 universal_newlines=True,
153 )
163 )
154 logger.info(output)
164 logger.info(output)
155− pid = _get_unity_pid()
165+ pid = get_job_pid(name)
156 except subprocess.CalledProcessError as e:
166 except subprocess.CalledProcessError as e:
157− e.args += ("Failed to start unity8: %s" % e.output,)
167+ e.args += ('Failed to start {}: {}.'.format(name, e.output),)
158 raise
168 raise
159 else:
169 else:
160− return _get_unity_proxy_object(pid)
170+ return pid
161
171
162
172
163−def _get_unity_status():
173+def get_job_pid(name):
174+ """Return the process id of a running job.
175+
176+ :param str name: The name of the job.
177+ :raises JobError: if the job is not running.
178+
179+ """
180+ status = get_job_status(name)
181+ if "start/" not in status:
182+ raise JobError('{} is not in the running state.'.format(name))
183+ return int(status.split()[-1])
184+
185+
186+def get_job_status(name):
187+ """Return the status of a job.
188+
189+ :param str name: The name of the job.
190+ :raises JobError: if it's not possible to get the status of the job.
191+
192+ """
164 try:
193 try:
165 return subprocess.check_output([
194 return subprocess.check_output([
166 '/sbin/initctl',
195 '/sbin/initctl',
167 'status',
196 'status',
168− 'unity8'
197+ name
169 ], universal_newlines=True)
198 ], universal_newlines=True)
199+ except subprocess.CalledProcessError as error:
200+ raise JobError(
201+ "Unable to get {}'s status: {}".format(name, error)
202+ )
203+
204+
205+def stop_job(name):
206+ """Stop a job.
207+
208+ :param str name: The name of the job.
209+ :raises CalledProcessError: if the job failed to stop.
210+
211+ """
212+ logger.info('Stoping job {}.'.format(name))
213+ command = ['/sbin/initctl', 'stop', name]
214+ try:
215+ output = subprocess.check_output(
216+ command,
217+ stderr=subprocess.STDOUT,
218+ universal_newlines=True,
219+ )
220+ logger.info(output)
170 except subprocess.CalledProcessError as e:
221 except subprocess.CalledProcessError as e:
171− raise CannotAccessUnity("Unable to get unity's status: %s" % str(e))
222+ e.args += ('Failed to stop {}: {}.'.format(name, e.output),)
223+ raise
224+
225+
226+def is_job_running(name):
227+ """Return True if the job is running. Otherwise, False.
228+
229+ :param str name: The name of the job.
230+ :raises JobError: if it's not possible to get the status of the job.
231+
232+ """
233+ return 'start/' in get_job_status(name)
234+
235+
236+def _get_unity_status():
237+ try:
238+ return get_job_status('unity8')
239+ except JobError as error:
240+ raise CannotAccessUnity(str(error))
172
241
173
242
174def _get_unity_pid():
243def _get_unity_pid():
175− status = _get_unity_status()
244+ try:
176− if "start/" not in status:
245+ return get_job_pid('unity8')
246+ except JobError as error:
177− raise CannotAccessUnity("Unity is not in the running state.")
247+ raise CannotAccessUnity(str(error))
178− return int(status.split()[-1])
179
248
180
249
181def _get_unity_proxy_object(pid):
250def _get_unity_proxy_object(pid):
tests/autopilot/unity8/shell/emulators/dash.py+21−12
@@ -31,6 +31,17 @@ from ubuntuuitoolkit import emulators as toolkit_emulators
31logger = logging.getLogger(__name__)
31logger = logging.getLogger(__name__)
32
32
33
33
34+class DashApp(object):
35+
36+ """Autopilot helper for the Dash app."""
37+
38+ def __init__(self, app_proxy):
39+ self.app_proxy = app_proxy
40+ self.main_view = self.app_proxy.select_single(
41+ toolkit_emulators.MainView)
42+ self.dash = self.main_view.select_single(Dash)
43+
44+
34class Dash(emulators.UnityEmulatorBase):
45class Dash(emulators.UnityEmulatorBase):
35 """An emulator that understands the Dash."""
46 """An emulator that understands the Dash."""
36
47
@@ -109,20 +120,22 @@ class Dash(emulators.UnityEmulatorBase):
109 @autopilot_logging.log_action(logger.info)
120 @autopilot_logging.log_action(logger.info)
110 def _scroll_to_left_scope(self):
121 def _scroll_to_left_scope(self):
111 original_index = self.dash_content_list.currentIndex
122 original_index = self.dash_content_list.currentIndex
112− dashContent = self.select_single(objectName="dashContent")
123+ dash_content = self.select_single(objectName="dashContent")
124+ x, y, width, height = dash_content.globalRect
113− start_x = dashContent.width / 3
125+ start_x = x + width / 3
114− stop_x = dashContent.width / 3 * 2
126+ stop_x = x + width / 3 * 2
115− start_y = stop_y = dashContent.globalRect.y + 1
127+ start_y = stop_y = y + 1
116 self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
128 self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
117 self.dash_content_list.currentIndex.wait_for(original_index - 1)
129 self.dash_content_list.currentIndex.wait_for(original_index - 1)
118
130
119 @autopilot_logging.log_action(logger.info)
131 @autopilot_logging.log_action(logger.info)
120 def _scroll_to_right_scope(self):
132 def _scroll_to_right_scope(self):
121 original_index = self.dash_content_list.currentIndex
133 original_index = self.dash_content_list.currentIndex
122− dashContent = self.select_single(objectName="dashContent")
134+ dash_content = self.select_single(objectName="dashContent")
135+ x, y, width, height = dash_content.globalRect
123− start_x = dashContent.width / 3 * 2
136+ start_x = x + width / 3 * 2
124− stop_x = dashContent.width / 3
137+ stop_x = x + width / 3
125− start_y = stop_y = dashContent.globalRect.y + 1
138+ start_y = stop_y = y + 1
126 self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
139 self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
127 self.dash_content_list.currentIndex.wait_for(original_index + 1)
140 self.dash_content_list.currentIndex.wait_for(original_index + 1)
128
141
@@ -188,10 +201,6 @@ class GenericScopeView(emulators.UnityEmulatorBase):
188 raise emulators.UnityEmulatorException(
201 raise emulators.UnityEmulatorException(
189 'No category found with name {}'.format(category))
202 'No category found with name {}'.format(category))
190
203
191−
192−class DashApps(GenericScopeView):
193− """Autopilot emulator for the applications scope."""
194−
195 def get_applications(self, category):
204 def get_applications(self, category):
196 """Return the list of applications on a category.
205 """Return the list of applications on a category.
197
206
tests/autopilot/unity8/shell/emulators/greeter.py+4−1
@@ -17,6 +17,8 @@
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#
18#
19
19
20+import ubuntuuitoolkit
21+
20from unity8.shell.emulators import UnityEmulatorBase
22from unity8.shell.emulators import UnityEmulatorBase
21
23
22
24
@@ -37,4 +39,5 @@ class Greeter(UnityEmulatorBase):
37 self.created.wait_for(False)
39 self.created.wait_for(False)
38
40
39 def get_prompt(self):
41 def get_prompt(self):
42+ return self.select_single(
40− return self.select_single("TextField", objectName="passwordInput")
43+ ubuntuuitoolkit.TextField, objectName='passwordInput')
tests/autopilot/unity8/shell/emulators/main_window.py+34−20
@@ -25,7 +25,6 @@ from autopilot import input
25from unity8.shell import emulators
25from unity8.shell import emulators
26from unity8.shell.emulators.greeter import Greeter
26from unity8.shell.emulators.greeter import Greeter
27from unity8.shell.emulators.hud import Hud
27from unity8.shell.emulators.hud import Hud
28−from unity8.shell.emulators.dash import Dash
29from unity8.shell.emulators.launcher import Launcher
28from unity8.shell.emulators.launcher import Launcher
30
29
31logger = logging.getLogger(__name__)
30logger = logging.getLogger(__name__)
@@ -61,9 +60,6 @@ class QQuickView(emulators.UnityEmulatorBase):
61 def get_hud_edge_drag_area(self):
60 def get_hud_edge_drag_area(self):
62 return self.select_single(objectName="hudDragArea")
61 return self.select_single(objectName="hudDragArea")
63
62
64− def get_dash(self):
65− return self.select_single(Dash)
66−
67 def get_bottombar(self):
63 def get_bottombar(self):
68 return self.select_single("Bottombar")
64 return self.select_single("Bottombar")
69
65
@@ -76,12 +72,6 @@ class QQuickView(emulators.UnityEmulatorBase):
76 objectName="pinPadLoader"
72 objectName="pinPadLoader"
77 )
73 )
78
74
79− def get_pinPadButton(self, buttonId):
80− return self.select_single(
81− "PinPadButton",
82− objectName="pinPadButton%i" % buttonId
83− )
84−
85 def get_lockscreen(self):
75 def get_lockscreen(self):
86 return self.select_single("Lockscreen")
76 return self.select_single("Lockscreen")
87
77
@@ -117,20 +107,44 @@ class QQuickView(emulators.UnityEmulatorBase):
117 @autopilot_logging.log_action(logger.info)
107 @autopilot_logging.log_action(logger.info)
118 def show_dash_swiping(self):
108 def show_dash_swiping(self):
119 """Show the dash swiping from the left."""
109 """Show the dash swiping from the left."""
120− width = self.width
110+ x, y, width, height = self._get_shell().globalRect
121− height = self.height
122− start_x = 0
111+ start_x = x
123− start_y = height // 2
124− end_x = width
112+ end_x = x + width
125− end_y = start_y
113+ start_y = end_y = y + height // 2
126
114
127 self.pointing_device.drag(start_x, start_y, end_x, end_y)
115 self.pointing_device.drag(start_x, start_y, end_x, end_y)
116+
117+ def _get_shell(self):
128− return self.get_dash()
118+ return self.select_single('Shell')
129
119
130 def get_current_focused_app_id(self):
120 def get_current_focused_app_id(self):
131 """Return the id of the focused application."""
121 """Return the id of the focused application."""
132− return self.select_single('Shell').focusedApplicationId
122+ return self._get_shell().focusedApplicationId
133
123
134 @autopilot_logging.log_action(logger.info)
124 @autopilot_logging.log_action(logger.info)
135− def search(self, query):
125+ def enter_pin_code(self, code):
136− self.get_dash().enter_search_query(query)
126+ """Enter code 'code' into the single-pin lightdm pincode entry screen.
127+
128+ :param code: must be a string of numeric characters.
129+ :raises: TypeError if code is not a string.
130+ :raises: ValueError if code contains non-numeric characters.
131+
132+ """
133+ if not isinstance(code, str):
134+ raise TypeError(
135+ "'code' parameter must be a string, not %r."
136+ % type(code)
137+ )
138+ for num in code:
139+ if not num.isdigit():
140+ raise ValueError(
141+ "'code' parameter contains non-numeric characters."
142+ )
143+ self.pointing_device.click_object(
144+ self._get_pinpad_button(int(num)))
145+
146+ def _get_pinpad_button(self, button_id):
147+ return self.select_single(
148+ 'PinPadButton',
149+ objectName='pinPadButton{}'.format(button_id)
150+ )
tests/autopilot/unity8/shell/tests/__init__.py+141−48
@@ -24,6 +24,7 @@ try:
24except ImportError:
24except ImportError:
25 Gio = None
25 Gio = None
26
26
27+from autopilot import introspection
27from autopilot.platform import model
28from autopilot.platform import model
28from autopilot.testcase import AutopilotTestCase
29from autopilot.testcase import AutopilotTestCase
29from autopilot.matchers import Eventually
30from autopilot.matchers import Eventually
@@ -34,6 +35,10 @@ import os.path
34import subprocess
35import subprocess
35import sys
36import sys
36from testtools.matchers import Equals
37from testtools.matchers import Equals
38+from ubuntuuitoolkit import (
39+ fixture_setup as toolkit_fixtures,
40+ ubuntu_scenarios
41+)
37
42
38from unity8 import (
43from unity8 import (
39 get_lib_path,
44 get_lib_path,
@@ -42,9 +47,15 @@ from unity8 import (
42 get_default_extra_mock_libraries,
47 get_default_extra_mock_libraries,
43 get_data_dirs
48 get_data_dirs
44)
49)
45−from unity8.process_helpers import restart_unity_with_testability
50+from unity8 import (
51+ fixture_setup,
52+ process_helpers
53+)
54+from unity8.shell import emulators
55+from unity8.shell.emulators import (
56+ dash as dash_helpers,
46−from unity8.shell.emulators import main_window as main_window_emulator
57+ main_window as main_window_emulator,
47−from unity8.shell.emulators.dash import Dash
58+)
48
59
49
60
50logger = logging.getLogger(__name__)
61logger = logging.getLogger(__name__)
@@ -78,6 +89,26 @@ def _get_device_emulation_scenarios(devices='All'):
78 return [native]
89 return [native]
79
90
80
91
92+def is_unity7_running():
93+ """Return True if Unity7 is running. Otherwise, return False."""
94+ return (
95+ Gio is not None and
96+ UNITYSHELL_GSETTINGS_SCHEMA in
97+ Gio.Settings.list_relocatable_schemas()
98+ )
99+
100+
101+def get_qml_import_path_with_mock():
102+ """Return the QML2_IMPORT_PATH value with the mock path prepended."""
103+ qml_import_path = [get_mocks_library_path()]
104+ if os.getenv('QML2_IMPORT_PATH') is not None:
105+ qml_import_path.append(os.getenv('QML2_IMPORT_PATH'))
106+
107+ qml_import_path = ':'.join(qml_import_path)
108+ logger.info("New QML2 import path: %s", qml_import_path)
109+ return qml_import_path
110+
111+
81class UnityTestCase(AutopilotTestCase):
112class UnityTestCase(AutopilotTestCase):
82
113
83 """A test case base class for the Unity shell tests."""
114 """A test case base class for the Unity shell tests."""
@@ -118,23 +149,8 @@ class UnityTestCase(AutopilotTestCase):
118
149
119 def setUp(self):
150 def setUp(self):
120 super(UnityTestCase, self).setUp()
151 super(UnityTestCase, self).setUp()
121− if (Gio is not None and
152+ if is_unity7_running():
122− UNITYSHELL_GSETTINGS_SCHEMA in
153+ self.useFixture(toolkit_fixtures.HideUnity7Launcher())
123− Gio.Settings.list_relocatable_schemas()):
124−
125− # Hide Unity launcher
126− self._unityshell_schema = Gio.Settings.new_with_path(
127− UNITYSHELL_GSETTINGS_SCHEMA,
128− UNITYSHELL_GSETTINGS_PATH,
129− )
130− self._launcher_hide_mode = self._unityshell_schema.get_int(
131− UNITYSHELL_LAUNCHER_KEY,
132− )
133− self._unityshell_schema.set_int(
134− UNITYSHELL_LAUNCHER_KEY,
135− UNITYSHELL_LAUNCHER_MODE,
136− )
137− self.addCleanup(self._reset_launcher)
138
154
139 self._proxy = None
155 self._proxy = None
140 self._lightdm_mock_type = None
156 self._lightdm_mock_type = None
@@ -151,13 +167,6 @@ class UnityTestCase(AutopilotTestCase):
151 self.touch = Touch.create()
167 self.touch = Touch.create()
152 self._setup_display_details()
168 self._setup_display_details()
153
169
154− def _reset_launcher(self):
155− """Reset Unity launcher hide mode"""
156− self._unityshell_schema.set_int(
157− UNITYSHELL_LAUNCHER_KEY,
158− self._launcher_hide_mode,
159− )
160−
161 def _setup_display_details(self):
170 def _setup_display_details(self):
162 scale_divisor = self._determine_geometry()
171 scale_divisor = self._determine_geometry()
163 self._setup_grid_size(scale_divisor)
172 self._setup_grid_size(scale_divisor)
@@ -263,7 +272,9 @@ class UnityTestCase(AutopilotTestCase):
263 self.patch_lightdm_mock()
272 self.patch_lightdm_mock()
264
273
265 if self._qml_mock_enabled:
274 if self._qml_mock_enabled:
266− self._setup_extra_mock_environment_patch()
275+ self._environment['QML2_IMPORT_PATH'] = (
276+ get_qml_import_path_with_mock()
277+ )
267
278
268 if self._data_dirs_mock_enabled:
279 if self._data_dirs_mock_enabled:
269 self._patch_data_dirs()
280 self._patch_data_dirs()
@@ -292,9 +303,14 @@ class UnityTestCase(AutopilotTestCase):
292
303
293 # Ensure that the dash is visible before we return:
304 # Ensure that the dash is visible before we return:
294 logger.debug("Unity started, waiting for it to be ready.")
305 logger.debug("Unity started, waiting for it to be ready.")
295− self.assertUnityReady()
306+ self.wait_for_unity()
296 logger.debug("Unity loaded and ready.")
307 logger.debug("Unity loaded and ready.")
297
308
309+ if model() == 'Desktop':
310+ # On desktop, close the dash because it's opened in a separate
311+ # window and it gets in the way.
312+ process_helpers.stop_job('unity8-dash')
313+
298 return app_proxy
314 return app_proxy
299
315
300 def _launch_unity_with_upstart(self, binary_path, args):
316 def _launch_unity_with_upstart(self, binary_path, args):
@@ -308,7 +324,7 @@ class UnityTestCase(AutopilotTestCase):
308
324
309 self.addCleanup(self._cleanup_launching_upstart_unity)
325 self.addCleanup(self._cleanup_launching_upstart_unity)
310
326
311− return restart_unity_with_testability(*all_args)
327+ return process_helpers.restart_unity_with_testability(*all_args)
312
328
313 def _cleanup_launching_upstart_unity(self):
329 def _cleanup_launching_upstart_unity(self):
314 logger.info("Stopping unity")
330 logger.info("Stopping unity")
@@ -353,15 +369,6 @@ class UnityTestCase(AutopilotTestCase):
353 )
369 )
354 return lightdm_mock_path
370 return lightdm_mock_path
355
371
356− def _setup_extra_mock_environment_patch(self):
357− qml_import_path = [get_mocks_library_path()]
358− if os.getenv('QML2_IMPORT_PATH') is not None:
359− qml_import_path.append(os.getenv('QML2_IMPORT_PATH'))
360−
361− qml_import_path = ':'.join(qml_import_path)
362− logger.info("New QML2 import path: %s", qml_import_path)
363− self._environment['QML2_IMPORT_PATH'] = qml_import_path
364−
365 def _set_proxy(self, proxy):
372 def _set_proxy(self, proxy):
366 """Keep a copy of the proxy object, so we can use it to get common
373 """Keep a copy of the proxy object, so we can use it to get common
367 parts of the shell later on.
374 parts of the shell later on.
@@ -373,10 +380,80 @@ class UnityTestCase(AutopilotTestCase):
373 def _clear_proxy(self):
380 def _clear_proxy(self):
374 self._proxy = None
381 self._proxy = None
375
382
376− def assertUnityReady(self):
383+ def wait_for_unity(self):
384+ greeter_content_loader = self.main_window.wait_select_single(
385+ objectName='greeterContentLoader')
386+ greeter_content_loader.progress.wait_for(1)
387+
377− dash = self.get_dash()
388+ def get_dash(self):
378− home_scope = dash.get_scope('clickscope')
389+ pid = process_helpers.get_job_pid('unity8-dash')
390+ dash_proxy = introspection.get_proxy_object_for_existing_process(
391+ pid=pid,
392+ emulator_base=emulators.UnityEmulatorBase,
393+ )
394+ dash_app = dash_helpers.DashApp(dash_proxy)
395+ return dash_app.dash
396+
397+ @property
398+ def main_window(self):
399+ return self._proxy.select_single(main_window_emulator.QQuickView)
379
400
401+
402+class DashBaseTestCase(AutopilotTestCase):
403+
404+ scenarios = ubuntu_scenarios.get_device_simulation_scenarios()
405+ qml_mock_enabled = True
406+ environment = {}
407+
408+ def setUp(self):
409+ super(DashBaseTestCase, self).setUp()
410+
411+ if is_unity7_running():
412+ self.useFixture(toolkit_fixtures.HideUnity7Launcher())
413+
414+ if model() != 'Desktop':
415+ # On the phone, we need unity to be running and unlocked.
416+ self.addCleanup(process_helpers.stop_job, 'unity8')
417+ process_helpers.restart_unity_with_testability()
418+ process_helpers.unlock_unity()
419+
420+ self.ensure_dash_not_running()
421+
422+ if self.qml_mock_enabled:
423+ self.environment['QML2_IMPORT_PATH'] = (
424+ get_qml_import_path_with_mock()
425+ )
426+
427+ if self.should_simulate_device():
428+ # This sets the grid units, so it should be called before launching
429+ # the app.
430+ self.simulate_device()
431+
432+ binary_path = get_binary_path('unity8-dash')
433+ dash_proxy = self.launch_dash(binary_path, self.environment)
434+
435+ if self.should_simulate_device():
436+ # XXX Currently we have no way to launch the application with a
437+ # specific size, so we must resize it after it's launched.
438+ # --elopio - 2014-06-25
439+ self.resize_window()
440+
441+ self.dash_app = dash_helpers.DashApp(dash_proxy)
442+ self.dash = self.dash_app.dash
443+ self.wait_for_dash()
444+
445+ def ensure_dash_not_running(self):
446+ if process_helpers.is_job_running('unity8-dash'):
447+ process_helpers.stop_job('unity8-dash')
448+
449+ def launch_dash(self, binary_path, variables):
450+ launch_dash_app_fixture = fixture_setup.LaunchDashApp(
451+ binary_path, variables)
452+ self.useFixture(launch_dash_app_fixture)
453+ return launch_dash_app_fixture.application_proxy
454+
455+ def wait_for_dash(self):
456+ home_scope = self.dash.get_scope('clickscope')
380 # FIXME! There is a huge timeout here for when we're doing CI on
457 # FIXME! There is a huge timeout here for when we're doing CI on
381 # VMs. See lp:1203715
458 # VMs. See lp:1203715
382 self.assertThat(
459 self.assertThat(
@@ -385,10 +462,26 @@ class UnityTestCase(AutopilotTestCase):
385 )
462 )
386 self.assertThat(home_scope.isCurrent, Eventually(Equals(True)))
463 self.assertThat(home_scope.isCurrent, Eventually(Equals(True)))
387
464
388− def get_dash(self):
465+ def should_simulate_device(self):
389− dash = self._proxy.wait_select_single(Dash)
466+ return (hasattr(self, 'app_width') and hasattr(self, 'app_height') and
390− return dash
467+ hasattr(self, 'grid_unit_px'))
391
468
392− @property
469+ def simulate_device(self):
393− def main_window(self):
470+ simulate_device_fixture = self.useFixture(
394− return self._proxy.select_single(main_window_emulator.QQuickView)
471+ toolkit_fixtures.SimulateDevice(
472+ self.app_width, self.app_height, self.grid_unit_px))
473+ self.app_width = simulate_device_fixture.app_width
474+ self.app_height = simulate_device_fixture.app_height
475+
476+ def resize_window(self):
477+ application = self.process_manager.get_running_applications()[0]
478+ window = application.get_windows()[0]
479+ window.resize(self.app_width, self.app_height)
480+
481+ def get_window_size():
482+ _, _, window_width, window_height = window.geometry
483+ return window_width, window_height
484+
485+ self.assertThat(
486+ get_window_size,
487+ Eventually(Equals((self.app_width, self.app_height))))
tests/autopilot/unity8/shell/tests/test_emulators.py+8−19
@@ -47,24 +47,13 @@ class MainWindowTestCase(tests.UnityTestCase):
47 unity_proxy = self.launch_unity()
47 unity_proxy = self.launch_unity()
48 process_helpers.unlock_unity(unity_proxy)
48 process_helpers.unlock_unity(unity_proxy)
49
49
50− def test_search(self):
51− self.main_window.search('Test')
52− text_field = self.main_window.get_dash()._get_search_text_field()
53− self.assertEqual(text_field.text, 'Test')
54
50
51+class DashEmulatorTestCase(tests.DashBaseTestCase):
55
52
56−class DashBaseTestCase(tests.UnityTestCase):
57−
58− scenarios = tests._get_device_emulation_scenarios()
59−
60− def setUp(self):
53+ def test_search(self):
61− super(DashBaseTestCase, self).setUp()
54+ self.dash.enter_search_query('Test')
62− unity_proxy = self.launch_unity()
55+ text_field = self.dash._get_search_text_field()
63− process_helpers.unlock_unity(unity_proxy)
56+ self.assertEqual(text_field.text, 'Test')
64− self.dash = self.main_window.get_dash()
65−
66−
67−class DashEmulatorTestCase(DashBaseTestCase):
68
57
69 def test_open_unexisting_scope(self):
58 def test_open_unexisting_scope(self):
70 scope_name = 'unexisting'
59 scope_name = 'unexisting'
@@ -142,10 +131,10 @@ class DashEmulatorTestCase(DashBaseTestCase):
142 scope_id = 'clickscope'
131 scope_id = 'clickscope'
143 scope = self.dash.open_scope(scope_id)
132 scope = self.dash.open_scope(scope_id)
144 self._assert_scope_is_opened(scope, scope_id)
133 self._assert_scope_is_opened(scope, scope_id)
145− self.assertIsInstance(scope, dash_emulators.DashApps)
134+ self.assertIsInstance(scope, dash_emulators.GenericScopeView)
146
135
147
136
148−class GenericScopeViewEmulatorTestCase(DashBaseTestCase):
137+class GenericScopeViewEmulatorTestCase(tests.DashBaseTestCase):
149
138
150 def setUp(self):
139 def setUp(self):
151 # Set up the fake scopes before launching unity.
140 # Set up the fake scopes before launching unity.
@@ -159,7 +148,7 @@ class GenericScopeViewEmulatorTestCase(DashBaseTestCase):
159 self.assertTrue(preview.isCurrent)
148 self.assertTrue(preview.isCurrent)
160
149
161
150
162−class DashAppsEmulatorTestCase(DashBaseTestCase):
151+class DashAppsEmulatorTestCase(tests.DashBaseTestCase):
163
152
164 available_applications = [
153 available_applications = [
165 'Title.2.0', 'Title.2.1', 'Title.2.2', 'Title.2.3', 'Title.2.4',
154 'Title.2.0', 'Title.2.1', 'Title.2.2', 'Title.2.3', 'Title.2.4',
tests/autopilot/unity8/shell/tests/test_lock_screen.py+7−47
@@ -51,7 +51,7 @@ class TestLockscreen(UnityTestCase):
51 if greeter.narrowMode:
51 if greeter.narrowMode:
52 unlock_unity(unity_proxy)
52 unlock_unity(unity_proxy)
53 lockscreen = self._wait_for_lockscreen()
53 lockscreen = self._wait_for_lockscreen()
54− self._enter_pincode("1234")
54+ self.main_window.enter_pin_code("1234")
55 self.assertThat(lockscreen.shown, Eventually(Equals(False)))
55 self.assertThat(lockscreen.shown, Eventually(Equals(False)))
56 else:
56 else:
57 self._enter_prompt_passphrase("1234")
57 self._enter_prompt_passphrase("1234")
@@ -81,7 +81,7 @@ class TestLockscreen(UnityTestCase):
81 if greeter.narrowMode:
81 if greeter.narrowMode:
82 unlock_unity(unity_proxy)
82 unlock_unity(unity_proxy)
83 lockscreen = self._wait_for_lockscreen()
83 lockscreen = self._wait_for_lockscreen()
84− self._enter_pincode("4321")
84+ self.main_window.enter_pin_code("4321")
85 pinentryField = self.main_window.get_pinentryField()
85 pinentryField = self.main_window.get_pinentryField()
86 self.assertThat(pinentryField.text, Eventually(Equals("")))
86 self.assertThat(pinentryField.text, Eventually(Equals("")))
87 self.assertThat(lockscreen.shown, Eventually(Equals(True)))
87 self.assertThat(lockscreen.shown, Eventually(Equals(True)))
@@ -118,28 +118,6 @@ class TestLockscreen(UnityTestCase):
118 self.assertThat(lockscreen.shown, Eventually(Equals(True)))
118 self.assertThat(lockscreen.shown, Eventually(Equals(True)))
119 return lockscreen
119 return lockscreen
120
120
121− def _enter_pincode(self, code):
122− """Enter code 'code' into the single-pin lightdm pincode entry
123− screen.
124−
125− :param code: must be a string of numeric characters.
126− :raises: TypeError if code is not a string.
127− :raises: ValueError if code contains non-numeric characters.
128−
129− """
130−
131− if not isinstance(code, basestring):
132− raise TypeError(
133− "'code' parameter must be a string, not %r."
134− % type(code)
135− )
136− for num in code:
137− if not num.isdigit():
138− raise ValueError(
139− "'code' parameter contains non-numeric characters."
140− )
141− self.touch.tap_object(self.main_window.get_pinPadButton(int(num)))
142−
143 def _enter_pin_passphrase(self, passphrase):
121 def _enter_pin_passphrase(self, passphrase):
144 """Enter the password specified in 'passphrase' into the password entry
122 """Enter the password specified in 'passphrase' into the password entry
145 field of the pin lock screen.
123 field of the pin lock screen.
@@ -148,18 +126,15 @@ class TestLockscreen(UnityTestCase):
148 :raises: TypeError if passphrase is not a string.
126 :raises: TypeError if passphrase is not a string.
149
127
150 """
128 """
151− if not isinstance(passphrase, basestring):
129+ if not isinstance(passphrase, str):
152 raise TypeError(
130 raise TypeError(
153 "'passphrase' parameter must be a string, not %r."
131 "'passphrase' parameter must be a string, not %r."
154 % type(passphrase)
132 % type(passphrase)
155 )
133 )
156
134
157− pinentryField = self.main_window.get_pinentryField()
135+ pin_entry_field = self.main_window.get_pinentryField()
158− self.touch.tap_object(pinentryField)
136+ pin_entry_field.write(passphrase)
159− self.assertThat(pinentryField.activeFocus, Eventually(Equals(True)))
160− for character in passphrase:
161− self._type_character(character, pinentryField)
162− logger.debug("Typed passphrase: %s", pinentryField.text)
137+ logger.debug("Typed passphrase: %s", pin_entry_field.text)
163 self.keyboard.type("\n")
138 self.keyboard.type("\n")
164
139
165 def _enter_prompt_passphrase(self, passphrase):
140 def _enter_prompt_passphrase(self, passphrase):
@@ -177,21 +152,6 @@ class TestLockscreen(UnityTestCase):
177 )
152 )
178
153
179 prompt = self.main_window.get_greeter().get_prompt()
154 prompt = self.main_window.get_greeter().get_prompt()
180− self.touch.tap_object(prompt)
181− self.assertThat(prompt.activeFocus, Eventually(Equals(True)))
182− for character in passphrase:
155+ prompt.write(passphrase)
183− self._type_character(character, prompt)
184 logger.debug("Typed passphrase: %s", prompt.text)
156 logger.debug("Typed passphrase: %s", prompt.text)
185 self.keyboard.type("\n")
157 self.keyboard.type("\n")
186−
187− def _type_character(self, character, prompt, retries=5):
188− current_text = prompt.text
189− self.keyboard.type(character)
190− try:
191− self.assertThat(
192− prompt.text, Eventually(Equals(current_text + character)))
193− except AssertionError:
194− if retries > 0:
195− self._type_character(character, prompt, retries-1)
196− else:
197− raise
tests/autopilot/unity8/shell/tests/test_notifications.py+12−7
@@ -154,7 +154,7 @@ class InteractiveNotificationBase(NotificationsBase):
154 'Notification', objectName='notification1')
154 'Notification', objectName='notification1')
155 notification = get_notification()
155 notification = get_notification()
156
156
157− self.touch.tap_object(
157+ notification.pointing_device.click_object(
158 notification.select_single(objectName="interactiveArea")
158 notification.select_single(objectName="interactiveArea")
159 )
159 )
160
160
@@ -201,11 +201,14 @@ class InteractiveNotificationBase(NotificationsBase):
201 'Notification', objectName='notification1')
201 'Notification', objectName='notification1')
202 notification = get_notification()
202 notification = get_notification()
203 self._assert_notification(notification, summary, body, True, True, 1.0)
203 self._assert_notification(notification, summary, body, True, True, 1.0)
204− initial_height = notification.height
204+ notification.pointing_device.click_object(
205− self.touch.tap_object(notification.select_single(objectName="combobutton_dropdown"))
205+ notification.select_single(objectName="combobutton_dropdown"))
206+ self.assertThat(
206− self.assertThat(notification.select_single(objectName="button2").expanded, Eventually(Equals(True)))
207+ notification.select_single(objectName="button2").expanded,
208+ Eventually(Equals(True)))
207 time.sleep(2)
209 time.sleep(2)
210+ notification.pointing_device.click_object(
208− self.touch.tap_object(notification.select_single(objectName="button4"))
211+ notification.select_single(objectName="button4"))
209 self.assert_notification_action_id_was_called("action_decline_4")
212 self.assert_notification_action_id_was_called("action_decline_4")
210
213
211 def test_modal_sd_without_greeter(self):
214 def test_modal_sd_without_greeter(self):
@@ -249,7 +252,8 @@ class InteractiveNotificationBase(NotificationsBase):
249 notification = get_notification()
252 notification = get_notification()
250 self._assert_notification(
253 self._assert_notification(
251 notification, summary, body, True, False, 1.0)
254 notification, summary, body, True, False, 1.0)
255+ notification.pointing_device.click_object(
252− self.touch.tap_object(notification.select_single(objectName="button0"))
256+ notification.select_single(objectName="button0"))
253 self.assert_notification_action_id_was_called("action_accept")
257 self.assert_notification_action_id_was_called("action_accept")
254
258
255 def test_modal_sd_with_greeter(self):
259 def test_modal_sd_with_greeter(self):
@@ -291,7 +295,8 @@ class InteractiveNotificationBase(NotificationsBase):
291 notification = get_notification()
295 notification = get_notification()
292 self._assert_notification(
296 self._assert_notification(
293 notification, summary, body, True, False, 1.0)
297 notification, summary, body, True, False, 1.0)
298+ notification.pointing_device.click_object(
294− self.touch.tap_object(notification.select_single(objectName="button0"))
299+ notification.select_single(objectName="button0"))
295 self.assert_notification_action_id_was_called("action_accept")
300 self.assert_notification_action_id_was_called("action_accept")
296
301
297 def _create_interactive_notification(
302 def _create_interactive_notification(
tests/autopilot/unity8/shell/tests/test_upstart.py+20−5
@@ -25,7 +25,8 @@ import signal
25import subprocess
25import subprocess
26import time
26import time
27
27
28+import fixtures
28−from testtools.matchers._basic import Equals
29+from testtools.matchers import Equals, MismatchError
29from autopilot.matchers import Eventually
30from autopilot.matchers import Eventually
30from autopilot.introspection import get_proxy_object_for_existing_process
31from autopilot.introspection import get_proxy_object_for_existing_process
31
32
@@ -100,16 +101,30 @@ class UpstartIntegrationTests(UnityTestCase):
100 emulator_base=UnityEmulatorBase,))
101 emulator_base=UnityEmulatorBase,))
101
102
102 def test_no_sigstop(self):
103 def test_no_sigstop(self):
104+ self.useFixture(
105+ fixtures.EnvironmentVariable(
103− self.patch_environment("UNITY_MIR_EMITS_SIGSTOP", "")
106+ 'UNITY_MIR_EMITS_SIGSTOP', newvalue=None))
104 self._launch_unity()
107 self._launch_unity()
108+
109+ try:
110+ self.assertThat(
111+ lambda: os.WIFSTOPPED(self._get_status()),
112+ Eventually(Equals(True)))
113+ except MismatchError:
114+ pass
115+ else:
116+ self.process.send_signal(signal.SIGCONT)
117+ self.fail('Unity8 raised SIGSTOP')
118+
105 self._set_proxy()
119 self._set_proxy()
106
120
107 logger.debug("Unity started, waiting for it to be ready.")
121 logger.debug("Unity started, waiting for it to be ready.")
108− self.assertUnityReady()
122+ self.wait_for_unity()
109 logger.debug("Unity loaded and ready.")
123 logger.debug("Unity loaded and ready.")
110
124
111 def test_expect_sigstop(self):
125 def test_expect_sigstop(self):
126+ self.useFixture(
112− self.patch_environment("UNITY_MIR_EMITS_SIGSTOP", "1")
127+ fixtures.EnvironmentVariable('UNITY_MIR_EMITS_SIGSTOP', '1'))
113 self._launch_unity()
128 self._launch_unity()
114 self.assertThat(
129 self.assertThat(
115 lambda: os.WIFSTOPPED(self._get_status()),
130 lambda: os.WIFSTOPPED(self._get_status()),
@@ -122,5 +137,5 @@ class UpstartIntegrationTests(UnityTestCase):
122
137
123 logger.debug("Unity started, waiting for it to be ready.")
138 logger.debug("Unity started, waiting for it to be ready.")
124 self._set_proxy()
139 self._set_proxy()
125− self.assertUnityReady()
140+ self.wait_for_unity()
126 logger.debug("Unity loaded and ready.")
141 logger.debug("Unity loaded and ready.")
tests/mocks/AccountsService/AccountsService.cpp+12−2
@@ -29,12 +29,14 @@ AccountsService::AccountsService(QObject* parent)
29
29
30QString AccountsService::user() const
30QString AccountsService::user() const
31{
31{
32− return "testuser";
32+ return m_user;
33}
33}
34
34
35void AccountsService::setUser(const QString &user)
35void AccountsService::setUser(const QString &user)
36{
36{
37− Q_UNUSED(user)
37+ m_user = user;
38+ Q_EMIT userChanged();
39+ Q_EMIT passwordDisplayHintChanged();
38}
40}
39
41
40bool AccountsService::demoEdges() const
42bool AccountsService::demoEdges() const
@@ -68,3 +70,11 @@ void AccountsService::setStatsWelcomeScreen(bool statsWelcomeScreen)
68 m_statsWelcomeScreen = statsWelcomeScreen;
70 m_statsWelcomeScreen = statsWelcomeScreen;
69 statsWelcomeScreenChanged();
71 statsWelcomeScreenChanged();
70}
72}
73+
74+AccountsService::PasswordDisplayHint AccountsService::passwordDisplayHint() const
75+{
76+ if (m_user == "has-pin")
77+ return PasswordDisplayHint::Numeric;
78+ else
79+ return PasswordDisplayHint::Keyboard;
80+}
tests/mocks/AccountsService/AccountsService.h+12
@@ -27,6 +27,7 @@
27class AccountsService: public QObject
27class AccountsService: public QObject
28{
28{
29 Q_OBJECT
29 Q_OBJECT
30+ Q_ENUMS(PasswordDisplayHint)
30 Q_PROPERTY (QString user
31 Q_PROPERTY (QString user
31 READ user
32 READ user
32 WRITE setUser
33 WRITE setUser
@@ -43,8 +44,16 @@ class AccountsService: public QObject
43 READ statsWelcomeScreen
44 READ statsWelcomeScreen
44 WRITE setStatsWelcomeScreen // only available in mock
45 WRITE setStatsWelcomeScreen // only available in mock
45 NOTIFY statsWelcomeScreenChanged)
46 NOTIFY statsWelcomeScreenChanged)
47+ Q_PROPERTY (PasswordDisplayHint passwordDisplayHint
48+ READ passwordDisplayHint
49+ NOTIFY passwordDisplayHintChanged)
46
50
47public:
51public:
52+ enum PasswordDisplayHint {
53+ Keyboard,
54+ Numeric,
55+ };
56+
48 explicit AccountsService(QObject *parent = 0);
57 explicit AccountsService(QObject *parent = 0);
49
58
50 QString user() const;
59 QString user() const;
@@ -55,15 +64,18 @@ public:
55 void setBackgroundFile(const QString &backgroundFile);
64 void setBackgroundFile(const QString &backgroundFile);
56 bool statsWelcomeScreen() const;
65 bool statsWelcomeScreen() const;
57 void setStatsWelcomeScreen(bool statsWelcomeScreen);
66 void setStatsWelcomeScreen(bool statsWelcomeScreen);
67+ PasswordDisplayHint passwordDisplayHint() const;
58
68
59Q_SIGNALS:
69Q_SIGNALS:
60 void userChanged();
70 void userChanged();
61 void demoEdgesChanged();
71 void demoEdgesChanged();
62 void backgroundFileChanged();
72 void backgroundFileChanged();
63 void statsWelcomeScreenChanged();
73 void statsWelcomeScreenChanged();
74+ void passwordDisplayHintChanged();
64
75
65private:
76private:
66 QString m_backgroundFile;
77 QString m_backgroundFile;
78+ QString m_user;
67 bool m_statsWelcomeScreen;
79 bool m_statsWelcomeScreen;
68};
80};
69
81
tests/mocks/AccountsService/CMakeLists.txt+4−1
@@ -5,4 +5,7 @@ add_library(MockAccountsService-qml MODULE
5
5
6qt5_use_modules(MockAccountsService-qml DBus Qml)
6qt5_use_modules(MockAccountsService-qml DBus Qml)
7
7
8−add_unity8_mock(AccountsService 0.1 AccountsService TARGETS MockAccountsService-qml)
8+add_unity8_mock(AccountsService 0.1 AccountsService
9+ PREFIX mocks
10+ TARGETS MockAccountsService-qml
11+ )
tests/mocks/AccountsService/plugin.cpp+1
@@ -32,5 +32,6 @@ static QObject *service_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
32void AccountsServicePlugin::registerTypes(const char *uri)
32void AccountsServicePlugin::registerTypes(const char *uri)
33{
33{
34 Q_ASSERT(uri == QLatin1String("AccountsService"));
34 Q_ASSERT(uri == QLatin1String("AccountsService"));
35+ qRegisterMetaType<AccountsService::PasswordDisplayHint>("AccountsService::PasswordDisplayHint");
35 qmlRegisterSingletonType<AccountsService>(uri, 0, 1, "AccountsService", service_provider);
36 qmlRegisterSingletonType<AccountsService>(uri, 0, 1, "AccountsService", service_provider);
36}
37}
tests/mocks/LightDM/GreeterPrivate.h+2
@@ -24,6 +24,7 @@
24namespace QLightDM
24namespace QLightDM
25{
25{
26class Greeter;
26class Greeter;
27+class GreeterImpl;
27
28
28class GreeterPrivate
29class GreeterPrivate
29{
30{
@@ -40,6 +41,7 @@ public:
40 void handleRespond(const QString &response);
41 void handleRespond(const QString &response);
41
42
42protected:
43protected:
44+ GreeterImpl *m_impl; // if the backend needs more private data
43 Greeter * const q_ptr;
45 Greeter * const q_ptr;
44
46
45private:
47private:
tests/mocks/LightDM/demo/CMakeLists.txt+4−2
@@ -11,11 +11,13 @@ set(LibLightDM_SOURCES
11add_library(MockLightDM-demo STATIC ${LibLightDM_SOURCES})
11add_library(MockLightDM-demo STATIC ${LibLightDM_SOURCES})
12
12
13include_directories(
13include_directories(
14+ ${CMAKE_CURRENT_BINARY_DIR}
14 ${LIBUSERMETRICSOUTPUT_INCLUDE_DIRS}
15 ${LIBUSERMETRICSOUTPUT_INCLUDE_DIRS}
15)
16)
16
17
17target_link_libraries(MockLightDM-demo
18target_link_libraries(MockLightDM-demo
18 ${LIBUSERMETRICSOUTPUT_LDFLAGS}
19 ${LIBUSERMETRICSOUTPUT_LDFLAGS}
20+ -lpam
19− )
21+)
20
22
21−qt5_use_modules(MockLightDM-demo Gui)
23+qt5_use_modules(MockLightDM-demo Concurrent Gui)
tests/mocks/LightDM/demo/GreeterPrivate.cpp+202−30
@@ -18,53 +18,225 @@
18
18
19#include "../Greeter.h"
19#include "../Greeter.h"
20#include "../GreeterPrivate.h"
20#include "../GreeterPrivate.h"
21−#include <QtCore/QDir>
21+#include <QFuture>
22−#include <QtCore/QSettings>
22+#include <QFutureInterface>
23+#include <QFutureWatcher>
24+#include <QQueue>
25+#include <QtConcurrent>
26+#include <QVector>
27+#include <security/pam_appl.h>
23
28
24namespace QLightDM
29namespace QLightDM
25{
30{
26
31
32+class GreeterImpl : public QObject
33+{
34+ Q_OBJECT
35+
36+ typedef QFutureInterface<QString> ResponseFuture;
37+
38+public:
39+ explicit GreeterImpl(Greeter *parent, GreeterPrivate *greeterPrivate)
40+ : QObject(parent),
41+ greeter(parent),
42+ greeterPrivate(greeterPrivate),
43+ pamHandle(NULL)
44+ {
45+ qRegisterMetaType<QLightDM::GreeterImpl::ResponseFuture>("QLightDM::GreeterImpl::ResponseFuture");
46+
47+ connect(&futureWatcher, SIGNAL(finished()),
48+ this, SLOT(finishPam()));
49+ connect(this, SIGNAL(showMessage(QString, QLightDM::Greeter::MessageType)),
50+ greeter, SIGNAL(showMessage(QString, QLightDM::Greeter::MessageType)));
51+ // This next connect is how we pass ResponseFutures between threads
52+ connect(this, SIGNAL(showPrompt(QString, QLightDM::Greeter::PromptType, QLightDM::GreeterImpl::ResponseFuture)),
53+ this, SLOT(handlePrompt(QString, QLightDM::Greeter::PromptType, QLightDM::GreeterImpl::ResponseFuture)));
54+ }
55+
56+ void start(QString username)
57+ {
58+ // Clear out any existing PAM interactions first (we can't simply
59+ // cancel our QFuture because QtConcurrent::run doesn't support cancel)
60+ if (pamHandle != NULL) {
61+ pam_handle *handle = pamHandle;
62+ pamHandle = NULL; // to disable normal finishPam() handling
63+ while (respond(QString())); // clear our local queue of QFutures
64+ pam_end(handle, PAM_CONV_ERR);
65+ }
66+
67+ // Now actually start a new conversation with PAM
68+ pam_conv conversation;
69+ conversation.conv = converseWithPam;
70+ conversation.appdata_ptr = static_cast<void*>(this);
71+
72+ if (pam_start("lightdm", username.toUtf8(), &conversation, &pamHandle) == PAM_SUCCESS) {
73+ futureWatcher.setFuture(QtConcurrent::run(authenticateWithPam, pamHandle));
74+ } else {
75+ greeterPrivate->authenticated = false;
76+ Q_EMIT greeter->showMessage("Internal error: could not start PAM authentication", QLightDM::Greeter::MessageTypeError);
77+ Q_EMIT greeter->authenticationComplete();
78+ }
79+ }
80+
81+ static int authenticateWithPam(pam_handle* pamHandle)
82+ {
83+ int pamStatus = pam_authenticate(pamHandle, 0);
84+ if (pamStatus == PAM_SUCCESS) {
85+ pamStatus = pam_acct_mgmt(pamHandle, 0);
86+ }
87+ if (pamStatus == PAM_NEW_AUTHTOK_REQD) {
88+ pamStatus = pam_chauthtok(pamHandle, PAM_CHANGE_EXPIRED_AUTHTOK);
89+ }
90+ if (pamStatus == PAM_SUCCESS) {
91+ pam_setcred(pamHandle, PAM_REINITIALIZE_CRED);
92+ }
93+ return pamStatus;
94+ }
95+
96+ static int converseWithPam(int num_msg, const pam_message** msg,
97+ pam_response** resp, void* appdata_ptr)
98+ {
99+ if (num_msg <= 0)
100+ return PAM_CONV_ERR;
101+
102+ auto* tmp_response = static_cast<pam_response*>(calloc(num_msg, sizeof(pam_response)));
103+ if (!tmp_response)
104+ return PAM_CONV_ERR;
105+
106+ GreeterImpl* impl = static_cast<GreeterImpl*>(appdata_ptr);
107+
108+ int count;
109+ QVector<ResponseFuture> responses;
110+
111+ for (count = 0; count < num_msg; ++count)
112+ {
113+ switch (msg[count]->msg_style)
114+ {
115+ case PAM_PROMPT_ECHO_ON:
116+ {
117+ QString message(msg[count]->msg);
118+ responses.append(ResponseFuture());
119+ responses.last().reportStarted();
120+ Q_EMIT impl->showPrompt(message, Greeter::PromptTypeQuestion, responses.last());
121+ break;
122+ }
123+ case PAM_PROMPT_ECHO_OFF:
124+ {
125+ QString message(msg[count]->msg);
126+ responses.append(ResponseFuture());
127+ responses.last().reportStarted();
128+ Q_EMIT impl->showPrompt(message, Greeter::PromptTypeSecret, responses.last());
129+ break;
130+ }
131+ case PAM_TEXT_INFO:
132+ {
133+ QString message(msg[count]->msg);
134+ Q_EMIT impl->showMessage(message, Greeter::MessageTypeInfo);
135+ break;
136+ }
137+ default:
138+ {
139+ QString message(msg[count]->msg);
140+ Q_EMIT impl->showMessage(message, Greeter::MessageTypeError);
141+ break;
142+ }
143+ }
144+ }
145+
146+ int i = 0;
147+ bool raise_error = false;
148+
149+ for (auto &response : responses)
150+ {
151+ pam_response* resp_item = &tmp_response[i++];
152+ resp_item->resp_retcode = 0;
153+ resp_item->resp = strdup(response.future().result().toUtf8());
154+
155+ if (!resp_item->resp)
156+ {
157+ raise_error = true;
158+ break;
159+ }
160+ }
161+
162+ if (raise_error)
163+ {
164+ for (int i = 0; i < count; ++i)
165+ free(tmp_response[i].resp);
166+
167+ free(tmp_response);
168+ return PAM_CONV_ERR;
169+ }
170+ else
171+ {
172+ *resp = tmp_response;
173+ return PAM_SUCCESS;
174+ }
175+ }
176+
177+public Q_SLOTS:
178+ bool respond(QString response)
179+ {
180+ if (!futures.isEmpty()) {
181+ futures.dequeue().reportFinished(&response);
182+ return true;
183+ } else {
184+ return false;
185+ }
186+ }
187+
188+Q_SIGNALS:
189+ void showMessage(QString text, QLightDM::Greeter::MessageType type);
190+ void showPrompt(QString text, QLightDM::Greeter::PromptType type, QLightDM::GreeterImpl::ResponseFuture response);
191+
192+private Q_SLOTS:
193+ void finishPam()
194+ {
195+ if (pamHandle == NULL) {
196+ return;
197+ }
198+
199+ int pamStatus = futureWatcher.result();
200+
201+ pam_end(pamHandle, pamStatus);
202+ pamHandle = NULL;
203+
204+ greeterPrivate->authenticated = (pamStatus == PAM_SUCCESS);
205+ Q_EMIT greeter->authenticationComplete();
206+ }
207+
208+ void handlePrompt(QString text, QLightDM::Greeter::PromptType type, QLightDM::GreeterImpl::ResponseFuture future)
209+ {
210+ futures.enqueue(future);
211+ Q_EMIT greeter->showPrompt(text, type);
212+ }
213+
214+private:
215+ Greeter *greeter;
216+ GreeterPrivate *greeterPrivate;
217+ pam_handle* pamHandle;
218+ QFutureWatcher<int> futureWatcher;
219+ QQueue<ResponseFuture> futures;
220+};
221+
27GreeterPrivate::GreeterPrivate(Greeter* parent)
222GreeterPrivate::GreeterPrivate(Greeter* parent)
28 : authenticated(false),
223 : authenticated(false),
29 authenticationUser(),
224 authenticationUser(),
225+ m_impl(new GreeterImpl(parent, this)),
30 q_ptr(parent)
226 q_ptr(parent)
31{
227{
32}
228}
33
229
34void GreeterPrivate::handleAuthenticate()
230void GreeterPrivate::handleAuthenticate()
35{
231{
36− Q_Q(Greeter);
37−
38− QSettings settings(QDir::homePath() + "/.unity8-greeter-demo", QSettings::NativeFormat);
39− settings.beginGroup(authenticationUser);
232+ m_impl->start(authenticationUser);
40− QVariant password = settings.value("password", "none");
41−
42− if (password == "pin") {
43− Q_EMIT q->showPrompt("PIN", Greeter::PromptTypeSecret);
44− } else if (password == "keyboard") {
45− Q_EMIT q->showPrompt("Password", Greeter::PromptTypeSecret);
46− } else {
47− authenticated = true;
48− Q_EMIT q->authenticationComplete();
49− }
50}
233}
51
234
52void GreeterPrivate::handleRespond(const QString &response)
235void GreeterPrivate::handleRespond(const QString &response)
53{
236{
54− Q_Q(Greeter);
237+ m_impl->respond(response);
55−
56− QSettings settings(QDir::homePath() + "/.unity8-greeter-demo", QSettings::NativeFormat);
57− settings.beginGroup(authenticationUser);
58− QVariant password = settings.value("password", "none");
59−
60− QString passwordValue;
61− if (password == "pin") {
62− passwordValue = settings.value("passwordValue", "1234").toString();
63− } else {
64− passwordValue = settings.value("passwordValue", "password").toString();
65− }
66− authenticated = (response == passwordValue);
67− Q_EMIT q->authenticationComplete();
68}
238}
69
239
70}
240}
241+
242+#include "GreeterPrivate.moc"
tests/mocks/LightDM/demo/UsersModelPrivate.cpp+1−1
@@ -29,7 +29,7 @@ UsersModelPrivate::UsersModelPrivate(UsersModel* parent)
29 : q_ptr(parent)
29 : q_ptr(parent)
30{
30{
31 QSettings settings(QDir::homePath() + "/.unity8-greeter-demo", QSettings::NativeFormat);
31 QSettings settings(QDir::homePath() + "/.unity8-greeter-demo", QSettings::NativeFormat);
32− QStringList users = settings.value("users", QStringList() << "phablet").toStringList();
32+ QStringList users = settings.value("users", QStringList() << qgetenv("USER")).toStringList();
33
33
34 Q_FOREACH(const QString &user, users)
34 Q_FOREACH(const QString &user, users)
35 {
35 {
tests/mocks/LightDM/full/GreeterPrivate.cpp+1−1
@@ -54,7 +54,7 @@ void GreeterPrivate::handleAuthenticate()
54 authenticated = true;
54 authenticated = true;
55 Q_EMIT q->authenticationComplete();
55 Q_EMIT q->authenticationComplete();
56 } else if (authenticationUser == "has-pin"){
56 } else if (authenticationUser == "has-pin"){
57− Q_EMIT q->showPrompt("PIN", Greeter::PromptTypeSecret);
57+ Q_EMIT q->showPrompt("Password:", Greeter::PromptTypeSecret);
58 } else if (authenticationUser == "auth-error") {
58 } else if (authenticationUser == "auth-error") {
59 authenticated = false;
59 authenticated = false;
60 Q_EMIT q->authenticationComplete();
60 Q_EMIT q->authenticationComplete();
tests/mocks/LightDM/single-pin/GreeterPrivate.cpp+1−1
@@ -32,7 +32,7 @@ GreeterPrivate::GreeterPrivate(Greeter* parent)
32void GreeterPrivate::handleAuthenticate()
32void GreeterPrivate::handleAuthenticate()
33{
33{
34 Q_Q(Greeter);
34 Q_Q(Greeter);
35− Q_EMIT q->showPrompt("PIN", Greeter::PromptTypeSecret);
35+ Q_EMIT q->showPrompt("Password:", Greeter::PromptTypeSecret);
36}
36}
37
37
38void GreeterPrivate::handleRespond(const QString &response)
38void GreeterPrivate::handleRespond(const QString &response)
tests/mocks/LightDM/single-pin/UsersModelPrivate.cpp+1−1
@@ -26,7 +26,7 @@ UsersModelPrivate::UsersModelPrivate(UsersModel* parent)
26{
26{
27 entries =
27 entries =
28 {
28 {
29− { "single", "Has PIN", 0, 0, false, false, 0, 0 },
29+ { "has-pin", "Has PIN", 0, 0, false, false, 0, 0 },
30 };
30 };
31}
31}
32
32
tests/mocks/Unity/Application/ApplicationInfo.cpp−62
@@ -31,8 +31,6 @@ ApplicationInfo::ApplicationInfo(const QString &appId, QObject *parent)
31 , m_state(Starting)
31 , m_state(Starting)
32 , m_focused(false)
32 , m_focused(false)
33 , m_fullscreen(false)
33 , m_fullscreen(false)
34− , m_windowItem(0)
35− , m_windowComponent(0)
36 , m_parentItem(0)
34 , m_parentItem(0)
37 , m_surface(0)
35 , m_surface(0)
38{
36{
@@ -45,8 +43,6 @@ ApplicationInfo::ApplicationInfo(QObject *parent)
45 , m_state(Starting)
43 , m_state(Starting)
46 , m_focused(false)
44 , m_focused(false)
47 , m_fullscreen(false)
45 , m_fullscreen(false)
48− , m_windowItem(0)
49− , m_windowComponent(0)
50 , m_parentItem(0)
46 , m_parentItem(0)
51 , m_surface(0)
47 , m_surface(0)
52{
48{
@@ -61,12 +57,6 @@ ApplicationInfo::~ApplicationInfo()
61 }
57 }
62}
58}
63
59
64−void ApplicationInfo::onWindowComponentStatusChanged(QQmlComponent::Status status)
65−{
66− if (status == QQmlComponent::Ready && !m_windowItem)
67− doCreateWindowItem();
68−}
69−
70void ApplicationInfo::onStateChanged(State state)
60void ApplicationInfo::onStateChanged(State state)
71{
61{
72 if (state == ApplicationInfo::Running) {
62 if (state == ApplicationInfo::Running) {
@@ -108,55 +98,3 @@ void ApplicationInfo::setSurface(MirSurfaceItem* surface)
108 Q_EMIT surfaceChanged(m_surface);
98 Q_EMIT surfaceChanged(m_surface);
109 SurfaceManager::singleton()->registerSurface(m_surface);
99 SurfaceManager::singleton()->registerSurface(m_surface);
110}
100}
111−
112−void ApplicationInfo::createWindowComponent()
113−{
114− // The assumptions I make here really should hold.
115− QQuickView *quickView =
116− qobject_cast<QQuickView*>(QGuiApplication::topLevelWindows()[0]);
117−
118− QQmlEngine *engine = quickView->engine();
119−
120− m_windowComponent = new QQmlComponent(engine, this);
121− m_windowComponent->setData(m_windowQml.toLatin1(), QUrl());
122−}
123−
124−void ApplicationInfo::doCreateWindowItem()
125−{
126− m_windowItem = qobject_cast<QQuickItem *>(m_windowComponent->create());
127− m_windowItem->setParentItem(m_parentItem);
128−}
129−
130−void ApplicationInfo::createWindowItem()
131−{
132− if (!m_windowComponent)
133− createWindowComponent();
134−
135− // only create the windowItem once the component is ready
136− if (!m_windowComponent->isReady()) {
137− connect(m_windowComponent, &QQmlComponent::statusChanged,
138− this, &ApplicationInfo::onWindowComponentStatusChanged);
139− } else {
140− doCreateWindowItem();
141− }
142−}
143−
144−void ApplicationInfo::showWindow(QQuickItem *parent)
145−{
146− m_parentItem = parent;
147−
148− if (!m_windowItem)
149− createWindowItem();
150−
151− if (m_windowItem) {
152− m_windowItem->setVisible(true);
153− }
154−}
155−
156−void ApplicationInfo::hideWindow()
157−{
158− if (!m_windowItem)
159− return;
160−
161− m_windowItem->setVisible(false);
162−}
tests/mocks/Unity/Application/ApplicationInfo.h−10
@@ -88,22 +88,12 @@ public:
88Q_SIGNALS:
88Q_SIGNALS:
89 void surfaceChanged(MirSurfaceItem*);
89 void surfaceChanged(MirSurfaceItem*);
90
90
91−public:
92− void showWindow(QQuickItem *parent);
93− void hideWindow();
94−
95private Q_SLOTS:
91private Q_SLOTS:
96− void onWindowComponentStatusChanged(QQmlComponent::Status status);
97 void onStateChanged(State state);
92 void onStateChanged(State state);
98
93
99 void createSurface();
94 void createSurface();
100
95
101private:
96private:
102− void createWindowItem();
103− void doCreateWindowItem();
104− void createWindowComponent();
105− QQuickItem *m_windowItem;
106− QQmlComponent *m_windowComponent;
107 QQuickItem *m_parentItem;
97 QQuickItem *m_parentItem;
108 MirSurfaceItem* m_surface;
98 MirSurfaceItem* m_surface;
109};
99};
tests/mocks/Unity/Application/ApplicationManager.cpp+17−147
@@ -48,16 +48,14 @@ ApplicationManager *ApplicationManager::singleton()
48ApplicationManager::ApplicationManager(QObject *parent)
48ApplicationManager::ApplicationManager(QObject *parent)
49 : ApplicationManagerInterface(parent)
49 : ApplicationManagerInterface(parent)
50 , m_suspended(false)
50 , m_suspended(false)
51− , m_mainStageComponent(0)
52− , m_mainStage(0)
53− , m_sideStageComponent(0)
54− , m_sideStage(0)
55− , m_rightMargin(0)
56{
51{
57 m_roleNames.insert(RoleSurface, "surface");
52 m_roleNames.insert(RoleSurface, "surface");
58 m_roleNames.insert(RoleFullscreen, "fullscreen");
53 m_roleNames.insert(RoleFullscreen, "fullscreen");
59
54
60 buildListOfAvailableApplications();
55 buildListOfAvailableApplications();
56+
57+ startApplication("unity8-dash");
58+ focusApplication("unity8-dash");
61}
59}
62
60
63ApplicationManager::~ApplicationManager()
61ApplicationManager::~ApplicationManager()
@@ -136,7 +134,6 @@ void ApplicationManager::add(ApplicationInfo *application) {
136 Q_EMIT applicationAdded(application->appId());
134 Q_EMIT applicationAdded(application->appId());
137 Q_EMIT countChanged();
135 Q_EMIT countChanged();
138 if (count() == 1) Q_EMIT emptyChanged(isEmpty()); // was empty but not anymore
136 if (count() == 1) Q_EMIT emptyChanged(isEmpty()); // was empty but not anymore
139− Q_EMIT focusRequested(application->appId());
140
137
141 connect(application, &ApplicationInfo::surfaceChanged, this, [application, this]() {
138 connect(application, &ApplicationInfo::surfaceChanged, this, [application, this]() {
142 QModelIndex appIndex = findIndex(application);
139 QModelIndex appIndex = findIndex(application);
@@ -222,6 +219,10 @@ ApplicationInfo* ApplicationManager::startApplication(const QString &appId,
222
219
223bool ApplicationManager::stopApplication(const QString &appId)
220bool ApplicationManager::stopApplication(const QString &appId)
224{
221{
222+ if (appId == "unity8-dash") {
223+ return false;
224+ }
225+
225 ApplicationInfo *application = findApplication(appId);
226 ApplicationInfo *application = findApplication(appId);
226 if (application == nullptr)
227 if (application == nullptr)
227 return false;
228 return false;
@@ -294,39 +295,25 @@ bool ApplicationManager::focusApplication(const QString &appId)
294 for (ApplicationInfo *app : m_runningApplications) {
295 for (ApplicationInfo *app : m_runningApplications) {
295 if (app->focused() && app->stage() == ApplicationInfo::MainStage) {
296 if (app->focused() && app->stage() == ApplicationInfo::MainStage) {
296 app->setFocused(false);
297 app->setFocused(false);
297− app->hideWindow();
298 app->setState(ApplicationInfo::Suspended);
298 app->setState(ApplicationInfo::Suspended);
299 }
299 }
300 }
300 }
301
301
302 // focus this app
302 // focus this app
303 application->setFocused(true);
303 application->setFocused(true);
304− if (!m_mainStage)
305− createMainStage();
306 application->setState(ApplicationInfo::Running);
304 application->setState(ApplicationInfo::Running);
307− application->showWindow(m_mainStage);
308− m_mainStage->setZ(-1000);
309− if (m_sideStage)
310− m_sideStage->setZ(-2000);
311 } else if (application->stage() == ApplicationInfo::SideStage) {
305 } else if (application->stage() == ApplicationInfo::SideStage) {
312 // unfocus currently focused sidestage app
306 // unfocus currently focused sidestage app
313 for (ApplicationInfo *app : m_runningApplications) {
307 for (ApplicationInfo *app : m_runningApplications) {
314 if (app->focused() && app->stage() == ApplicationInfo::SideStage) {
308 if (app->focused() && app->stage() == ApplicationInfo::SideStage) {
315 app->setFocused(false);
309 app->setFocused(false);
316− app->hideWindow();
317 app->setState(ApplicationInfo::Suspended);
310 app->setState(ApplicationInfo::Suspended);
318 }
311 }
319 }
312 }
320
313
321 // focus this app
314 // focus this app
322 application->setFocused(true);
315 application->setFocused(true);
323− if (!m_sideStage)
324− createSideStage();
325 application->setState(ApplicationInfo::Running);
316 application->setState(ApplicationInfo::Running);
326− application->showWindow(m_sideStage);
327− m_sideStage->setZ(-1000);
328− if (m_mainStage)
329− m_mainStage->setZ(-2000);
330 }
317 }
331
318
332 // move app to top of stack
319 // move app to top of stack
@@ -337,18 +324,14 @@ bool ApplicationManager::focusApplication(const QString &appId)
337
324
338bool ApplicationManager::requestFocusApplication(const QString &appId)
325bool ApplicationManager::requestFocusApplication(const QString &appId)
339{
326{
340− if (appId != focusedApplicationId()) {
341− QMetaObject::invokeMethod(this, "focusRequested", Qt::QueuedConnection, Q_ARG(QString, appId));
327+ QMetaObject::invokeMethod(this, "focusRequested", Qt::QueuedConnection, Q_ARG(QString, appId));
342− return true;
328+ return true;
343− }
344− return false;
345}
329}
346
330
347void ApplicationManager::unfocusCurrentApplication()
331void ApplicationManager::unfocusCurrentApplication()
348{
332{
349 for (ApplicationInfo *app : m_runningApplications) {
333 for (ApplicationInfo *app : m_runningApplications) {
350 if (app->focused()) {
334 if (app->focused()) {
351− app->hideWindow();
352 app->setFocused(false);
335 app->setFocused(false);
353 }
336 }
354 }
337 }
@@ -357,42 +340,6 @@ void ApplicationManager::unfocusCurrentApplication()
357
340
358void ApplicationManager::generateQmlStrings(ApplicationInfo *application)
341void ApplicationManager::generateQmlStrings(ApplicationInfo *application)
359{
342{
360− // TODO: Is there a better way of solving this fullscreen vs. regular
361− // application height?
362− QString topMargin;
363− if (application->fullscreen()) {
364− topMargin.append("0");
365− } else {
366− // Taken from Panel.panelHeight
367− topMargin.append("units.gu(3) + units.dp(2)");
368− }
369−
370− QString windowQml = QString(
371− "import QtQuick 2.0\n"
372− "Image {\n"
373− " anchors.fill: parent\n"
374− " anchors.topMargin: %1\n"
375− " anchors.rightMargin: %2\n"
376− " source: \"file://%3/Dash/graphics/phone/screenshots/%4.png\"\n"
377− " smooth: true\n"
378− " fillMode: Image.PreserveAspectCrop\n"
379− "}").arg(topMargin)
380− .arg(m_rightMargin)
381− .arg(qmlDirectory())
382− .arg(application->icon().toString());
383− application->setWindowQml(windowQml);
384−
385− QString imageQml = QString(
386− "import QtQuick 2.0\n"
387− "Image {\n"
388− " anchors.fill: parent\n"
389− " source: \"file://%1/Dash/graphics/phone/screenshots/%2.png\"\n"
390− " smooth: true\n"
391− " fillMode: Image.PreserveAspectCrop\n"
392− "}").arg(qmlDirectory())
393− .arg(application->icon().toString());
394− application->setImageQml(imageQml);
395−
396 application->setScreenshot(QString("file://%1/Dash/graphics/phone/screenshots/%2@12.png").arg(qmlDirectory())
343 application->setScreenshot(QString("file://%1/Dash/graphics/phone/screenshots/%2@12.png").arg(qmlDirectory())
397 .arg(application->icon().toString()));
344 .arg(application->icon().toString()));
398}
345}
@@ -401,6 +348,14 @@ void ApplicationManager::buildListOfAvailableApplications()
401{
348{
402 ApplicationInfo *application;
349 ApplicationInfo *application;
403
350
351+ application = new ApplicationInfo(this);
352+ application->setAppId("unity8-dash");
353+ application->setName("Unity 8 Mock Dash");
354+ application->setIcon(QUrl("unity8-dash"));
355+ application->setStage(ApplicationInfo::MainStage);
356+ generateQmlStrings(application);
357+ m_availableApplications.append(application);
358+
404 application = new ApplicationInfo(this);
359 application = new ApplicationInfo(this);
405 application->setAppId("dialer-app");
360 application->setAppId("dialer-app");
406 application->setName("Dialer");
361 application->setName("Dialer");
@@ -521,78 +476,6 @@ void ApplicationManager::buildListOfAvailableApplications()
521 m_availableApplications.append(application);
476 m_availableApplications.append(application);
522}
477}
523
478
524−void ApplicationManager::createMainStageComponent()
525−{
526− // The assumptions I make here really should hold.
527− QQuickView *quickView =
528− qobject_cast<QQuickView*>(QGuiApplication::topLevelWindows()[0]);
529−
530− QQmlEngine *engine = quickView->engine();
531−
532− m_mainStageComponent = new QQmlComponent(engine, this);
533− QString mainStageQml =
534− "import QtQuick 2.0\n"
535− "Rectangle {\n"
536− " anchors.fill: parent\n"
537− " color: 'black'\n"
538− " z: -2000\n"
539− "}\n";
540− m_mainStageComponent->setData(mainStageQml.toLatin1(), QUrl());
541−}
542−
543−void ApplicationManager::createMainStage()
544−{
545− if (!m_mainStageComponent)
546− createMainStageComponent();
547−
548− // The assumptions I make here really should hold.
549− QQuickView *quickView =
550− qobject_cast<QQuickView*>(QGuiApplication::topLevelWindows()[0]);
551−
552− QQuickItem *shell = quickView->rootObject();
553−
554− m_mainStage = qobject_cast<QQuickItem *>(m_mainStageComponent->create());
555− m_mainStage->setParentItem(shell);
556−}
557−
558−void ApplicationManager::createSideStageComponent()
559−{
560− // The assumptions I make here really should hold.
561− QQuickView *quickView =
562− qobject_cast<QQuickView*>(QGuiApplication::topLevelWindows()[0]);
563−
564− QQmlEngine *engine = quickView->engine();
565−
566− m_sideStageComponent = new QQmlComponent(engine, this);
567− QString sideStageQml =
568− "import QtQuick 2.0\n"
569− "import Ubuntu.Components 0.1\n"
570− "Item {\n"
571− " width: units.gu(40)\n" // from SideStage in Shell.qml
572− " anchors.top: parent.top\n"
573− " anchors.bottom: parent.bottom\n"
574− " anchors.right: parent.right\n"
575− " z: -1000\n"
576− "}\n";
577− m_sideStageComponent->setData(sideStageQml.toLatin1(), QUrl());
578−}
579−
580−void ApplicationManager::createSideStage()
581−{
582− if (!m_sideStageComponent)
583− createSideStageComponent();
584−
585− // The assumptions I make here really should hold.
586− QQuickView *quickView =
587− qobject_cast<QQuickView*>(QGuiApplication::topLevelWindows()[0]);
588−
589− QQuickItem *shell = quickView->rootObject();
590−
591− m_sideStage = qobject_cast<QQuickItem *>(m_sideStageComponent->create());
592− m_sideStage->setParentItem(shell);
593− m_sideStage->setFlag(QQuickItem::ItemHasContents, false);
594−}
595−
596QStringList ApplicationManager::availableApplications()
479QStringList ApplicationManager::availableApplications()
597{
480{
598 QStringList appIds;
481 QStringList appIds;
@@ -602,19 +485,6 @@ QStringList ApplicationManager::availableApplications()
602 return appIds;
485 return appIds;
603}
486}
604
487
605−int ApplicationManager::rightMargin() const
606−{
607− return m_rightMargin;
608−}
609−
610−void ApplicationManager::setRightMargin(int rightMargin)
611−{
612− m_rightMargin = rightMargin;
613− Q_FOREACH(ApplicationInfo *app, m_availableApplications) {
614− generateQmlStrings(app);
615− }
616−}
617−
618bool ApplicationManager::isEmpty() const
488bool ApplicationManager::isEmpty() const
619{
489{
620 return m_runningApplications.isEmpty();
490 return m_runningApplications.isEmpty();
tests/mocks/Unity/Application/ApplicationManager.h−16
@@ -44,10 +44,6 @@ class ApplicationManager : public ApplicationManagerInterface {
44
44
45 Q_PROPERTY(bool fake READ fake CONSTANT)
45 Q_PROPERTY(bool fake READ fake CONSTANT)
46
46
47− // Only for testing
48− // This can be used to place some controls to right, like make tryPhoneStage for example
49− Q_PROPERTY(int rightMargin READ rightMargin WRITE setRightMargin)
50−
51 public:
47 public:
52 ApplicationManager(QObject *parent = NULL);
48 ApplicationManager(QObject *parent = NULL);
53 virtual ~ApplicationManager();
49 virtual ~ApplicationManager();
@@ -108,8 +104,6 @@ class ApplicationManager : public ApplicationManagerInterface {
108
104
109 // Only for testing
105 // Only for testing
110 Q_INVOKABLE QStringList availableApplications();
106 Q_INVOKABLE QStringList availableApplications();
111− int rightMargin() const;
112− void setRightMargin(int rightMargin);
113
107
114 QModelIndex findIndex(ApplicationInfo* application);
108 QModelIndex findIndex(ApplicationInfo* application);
115
109
@@ -126,19 +120,9 @@ class ApplicationManager : public ApplicationManagerInterface {
126 void showApplicationWindow(ApplicationInfo *application);
120 void showApplicationWindow(ApplicationInfo *application);
127 void buildListOfAvailableApplications();
121 void buildListOfAvailableApplications();
128 void generateQmlStrings(ApplicationInfo *application);
122 void generateQmlStrings(ApplicationInfo *application);
129− void createMainStageComponent();
130− void createMainStage();
131− void createSideStageComponent();
132− void createSideStage();
133 bool m_suspended;
123 bool m_suspended;
134 QList<ApplicationInfo*> m_runningApplications;
124 QList<ApplicationInfo*> m_runningApplications;
135 QList<ApplicationInfo*> m_availableApplications;
125 QList<ApplicationInfo*> m_availableApplications;
136− QQmlComponent *m_mainStageComponent;
137− QQuickItem *m_mainStage;
138− QQmlComponent *m_sideStageComponent;
139− QQuickItem *m_sideStage;
140−
141− int m_rightMargin;
142
126
143 static ApplicationManager *the_application_manager;
127 static ApplicationManager *the_application_manager;
144};
128};
tests/mocks/Unity/Application/ApplicationScreenshotProvider.cpp+1−1
@@ -64,7 +64,7 @@ QImage ApplicationScreenshotProvider::requestImage(const QString &imageId, QSize
64 Q_FOREACH (QWindow *win, unity->allWindows()) {
64 Q_FOREACH (QWindow *win, unity->allWindows()) {
65 QQuickWindow *quickWin = qobject_cast<QQuickWindow*>(win);
65 QQuickWindow *quickWin = qobject_cast<QQuickWindow*>(win);
66 if (quickWin) {
66 if (quickWin) {
67− image = image.scaledToWidth(quickWin->width() - m_appManager->rightMargin());
67+ image = image.scaledToWidth(quickWin->width());
68 break;
68 break;
69 }
69 }
70 }
70 }
tests/mocks/Unity/CMakeLists.txt+2−1
@@ -2,11 +2,12 @@ add_subdirectory(Application)
2add_subdirectory(Indicators)
2add_subdirectory(Indicators)
3add_subdirectory(Launcher)
3add_subdirectory(Launcher)
4add_subdirectory(Notifications)
4add_subdirectory(Notifications)
5+add_subdirectory(DashCommunicator)
5
6
6pkg_search_module(DEE dee-1.0 REQUIRED)
7pkg_search_module(DEE dee-1.0 REQUIRED)
7pkg_search_module(GOBJECT gobject-2.0 REQUIRED)
8pkg_search_module(GOBJECT gobject-2.0 REQUIRED)
8pkg_search_module(DEEQT libdee-qt5 REQUIRED)
9pkg_search_module(DEEQT libdee-qt5 REQUIRED)
9−pkg_check_modules(SCOPES_API REQUIRED unity-shell-scopes=2)
10+pkg_check_modules(SCOPES_API REQUIRED unity-shell-scopes=3)
10
11
11include_directories(
12include_directories(
12 ${CMAKE_CURRENT_BINARY_DIR}
13 ${CMAKE_CURRENT_BINARY_DIR}
tests/mocks/Unity/DashCommunicator/CMakeLists.txtnew file+18
@@ -0,0 +1,18 @@
1+include_directories(
2+ ${CMAKE_CURRENT_SOURCE_DIR}
3+)
4+
5+set(QMLDASHCOMMUNICATORPLUGIN_SRC
6+ plugin.cpp
7+ dashcommunicatorservice.cpp
8+ dashcommunicator.cpp
9+ )
10+
11+add_library(MockDashCommunicator-qml MODULE
12+ ${QMLDASHCOMMUNICATORPLUGIN_SRC}
13+ )
14+
15+qt5_use_modules(MockDashCommunicator-qml DBus Qml)
16+
17+# export the qmldir and qmltypes files
18+add_unity8_mock(Unity.DashCommunicator 0.1 Unity/DashCommunicator TARGETS MockDashCommunicator-qml)
tests/mocks/Unity/DashCommunicator/dashcommunicator.cppnew file+37
@@ -0,0 +1,37 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it under
5+ * the terms of the GNU Lesser General Public License version 3, as published by
6+ * the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but WITHOUT
9+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+ * Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "dashcommunicator.h"
18+
19+#include <QObject>
20+#include <QDBusConnection>
21+#include <QDBusInterface>
22+#include <QDebug>
23+
24+DashCommunicator::DashCommunicator(QObject *parent):
25+ QObject(parent)
26+{
27+}
28+
29+DashCommunicator::~DashCommunicator()
30+{
31+}
32+
33+
34+void DashCommunicator::setCurrentScope(const QString &scopeId, bool animate, bool reset)
35+{
36+ Q_EMIT setCurrentScopeCalled(scopeId, animate, reset);
37+}
tests/mocks/Unity/DashCommunicator/dashcommunicator.hnew file+39
@@ -0,0 +1,39 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it under
5+ * the terms of the GNU Lesser General Public License version 3, as published by
6+ * the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but WITHOUT
9+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+ * Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef DASHCOMMUNICATOR_H
18+#define DASHCOMMUNICATOR_H
19+
20+#include <QObject>
21+
22+class DashCommunicator: public QObject
23+{
24+ Q_OBJECT
25+ Q_CLASSINFO("D-Bus Interface", "com.canonical.Unity.DashCommunicator")
26+
27+public:
28+ DashCommunicator(QObject *parent = 0);
29+ ~DashCommunicator();
30+
31+public Q_SLOTS:
32+ void setCurrentScope(const QString &scopeId, bool animate, bool reset);
33+
34+Q_SIGNALS:
35+ // This mock just emits calls back to the QML api for the plugin to verify calls
36+ void setCurrentScopeCalled(const QString &scopeId, bool animate, bool reset);
37+};
38+
39+#endif
tests/mocks/Unity/DashCommunicator/dashcommunicatorservice.cppnew file+33
@@ -0,0 +1,33 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it under
5+ * the terms of the GNU Lesser General Public License version 3, as published by
6+ * the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but WITHOUT
9+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+ * Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "dashcommunicatorservice.h"
18+
19+DashCommunicatorService::DashCommunicatorService(QObject *parent):
20+ QObject(parent)
21+{
22+}
23+
24+
25+DashCommunicatorService::~DashCommunicatorService()
26+{
27+
28+}
29+
30+void DashCommunicatorService::mockSetCurrentScope(const QString &scopeId, bool animate, bool reset)
31+{
32+ Q_EMIT setCurrentScopeRequested(scopeId, animate, reset);
33+}
tests/mocks/Unity/DashCommunicator/dashcommunicatorservice.hnew file+36
@@ -0,0 +1,36 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it under
5+ * the terms of the GNU Lesser General Public License version 3, as published by
6+ * the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but WITHOUT
9+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+ * Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef DASHCOMMUNICATORSERVICE_H
18+#define DASHCOMMUNICATORSERVICE_H
19+
20+#include <QObject>
21+
22+class DashCommunicatorService: public QObject
23+{
24+ Q_OBJECT
25+public:
26+ DashCommunicatorService(QObject *parent = 0);
27+ ~DashCommunicatorService();
28+
29+Q_SIGNALS:
30+ void setCurrentScopeRequested(const QString &scopeId, bool animate, bool reset);
31+
32+public Q_SLOTS:
33+ void mockSetCurrentScope(const QString &scopeId, bool animate, bool reset);
34+};
35+
36+#endif // DBUSUNITYSESSIONSERVICE_H
tests/mocks/Unity/DashCommunicator/plugin.cppnew file+31
@@ -0,0 +1,31 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Authors: Michael Zanetti <michael.zanetti@canonical.com>
17+ */
18+
19+#include "plugin.h"
20+#include "dashcommunicator.h"
21+#include "dashcommunicatorservice.h"
22+
23+#include <QDBusConnection>
24+#include <QtQml/qqml.h>
25+
26+void DashCommunicatorPlugin::registerTypes(const char *uri)
27+{
28+ Q_ASSERT(uri == QStringLiteral("Unity.DashCommunicator"));
29+ qmlRegisterType<DashCommunicatorService>(uri, 0, 1, "DashCommunicatorService");
30+ qmlRegisterType<DashCommunicator>(uri, 0, 1, "DashCommunicator");
31+}
tests/mocks/Unity/DashCommunicator/plugin.hnew file+34
@@ -0,0 +1,34 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Authors: Michael Zanetti <michael.zanetti@canonical.com>
17+ */
18+
19+#ifndef DASHCOMMUNICATOR_PLUGIN_H
20+#define DASHCOMMUNICATOR_PLUGIN_H
21+
22+#include <QtQml/QQmlEngine>
23+#include <QtQml/QQmlExtensionPlugin>
24+
25+class DashCommunicatorPlugin : public QQmlExtensionPlugin
26+{
27+ Q_OBJECT
28+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
29+
30+public:
31+ void registerTypes(const char *uri);
32+};
33+
34+#endif
tests/mocks/Unity/DashCommunicator/qmldirnew file+3
@@ -0,0 +1,3 @@
1+module Unity.DashCommunicator
2+plugin MockDashCommunicator-qml
3+typeinfo DashCommunicator.qmltypes
tests/mocks/Unity/fake_scopes.cpp+5
@@ -125,6 +125,11 @@ bool Scopes::loaded() const
125 return m_loaded;
125 return m_loaded;
126}
126}
127
127
128+unity::shell::scopes::ScopeInterface* Scopes::overviewScope() const
129+{
130+ return nullptr;
131+}
132+
128void Scopes::addScope(Scope* scope)
133void Scopes::addScope(Scope* scope)
129{
134{
130 int index = rowCount();
135 int index = rowCount();
tests/mocks/Unity/fake_scopes.h+1
@@ -50,6 +50,7 @@ public:
50 QModelIndex parent ( const QModelIndex & index ) const;
50 QModelIndex parent ( const QModelIndex & index ) const;
51
51
52 bool loaded() const override;
52 bool loaded() const override;
53+ unity::shell::scopes::ScopeInterface* overviewScope() const override;
53
54
54private Q_SLOTS:
55private Q_SLOTS:
55 void updateScopes();
56 void updateScopes();
tests/plugins/Dash/CMakeLists.txt+1
@@ -79,3 +79,4 @@ set(qmltest_DEFAULT_NO_ADD_TEST TRUE)
79set(qmltest_DEFAULT_PROPERTIES ENVIRONMENT "LC_ALL=C")
79set(qmltest_DEFAULT_PROPERTIES ENVIRONMENT "LC_ALL=C")
80add_qml_test(. ScopeStyle IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
80add_qml_test(. ScopeStyle IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
81add_qml_test(. ListViewWithPageHeaderQML IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
81add_qml_test(. ListViewWithPageHeaderQML IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
82+add_qml_test(. CardAttributes IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
tests/plugins/Dash/cardcreator/1.res+63−61
@@ -5,8 +5,8 @@ AbstractButton {
5 property var cardData;
5 property var cardData;
6 property var artShapeBorderSource: undefined;
6 property var artShapeBorderSource: undefined;
7 property real fontScale: 1.0;
7 property real fontScale: 1.0;
8− property var scopeStyle: null;
8+ property var scopeStyle: null;
9− property int headerAlignment: Text.AlignLeft;
9+ property int headerAlignment: Text.AlignLeft;
10 property int fixedHeaderHeight: -1;
10 property int fixedHeaderHeight: -1;
11 property size fixedArtShapeSize: Qt.size(-1, -1);
11 property size fixedArtShapeSize: Qt.size(-1, -1);
12 readonly property string title: cardData && cardData["title"] || "";
12 readonly property string title: cardData && cardData["title"] || "";
@@ -16,71 +16,73 @@ AbstractButton {
16onArtShapeBorderSourceChanged: { if (artShapeBorderSource !== undefined && artShapeLoader.item) artShapeLoader.item.borderSource = artShapeBorderSource; }
16onArtShapeBorderSourceChanged: { if (artShapeBorderSource !== undefined && artShapeLoader.item) artShapeLoader.item.borderSource = artShapeBorderSource; }
17readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
17readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
18Item {
18Item {
19− id: artShapeHolder;
19+ id: artShapeHolder;
20− height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
20+ height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
21− width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
21+ width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
22− anchors { horizontalCenter: parent.horizontalCenter; }
22+ anchors { horizontalCenter: parent.horizontalCenter; }
23− Loader {
23+ Loader {
24− id: artShapeLoader;
24+ id: artShapeLoader;
25− objectName: "artShapeLoader";
25+ objectName: "artShapeLoader";
26− active: cardData && cardData["art"] || false;
26+ active: cardData && cardData["art"] || false;
27− asynchronous: root.asynchronous;
28− visible: status == Loader.Ready;
29− sourceComponent: UbuntuShape {
30− id: artShape;
31− objectName: "artShape";
32− radius: "medium";
33− visible: image.status == Image.Ready;
34− readonly property real fixedArtShapeSizeAspect: (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) ? root.fixedArtShapeSize.width / root.fixedArtShapeSize.height : -1;
35− readonly property real aspect: fixedArtShapeSizeAspect > 0 ? fixedArtShapeSizeAspect : components !== undefined ? components["art"]["aspect-ratio"] : 1;
36− readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect;
37− Component.onCompleted: { updateWidthHeightBindings(); if (artShapeBorderSource !== undefined) borderSource = artShapeBorderSource; }
38− onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings();
39− Connections { target: root; onFixedArtShapeSizeChanged: updateWidthHeightBindings(); }
40− function updateWidthHeightBindings() {
41− if (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) {
42− width = root.fixedArtShapeSize.width;
43− height = root.fixedArtShapeSize.height;
44− } else if (aspectSmallerThanImageAspect) {
45− width = Qt.binding(function() { return !visible ? 0 : image.width });
46− height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect });
47− } else {
48− width = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.width : height * image.aspect });
49− height = Qt.binding(function() { return !visible ? 0 : image.height });
50− }
51− }
52− image: Image {
53− objectName: "artImage";
54− source: cardData && cardData["art"] || "";
55− cache: true;
56 asynchronous: root.asynchronous;
27 asynchronous: root.asynchronous;
28+ visible: status == Loader.Ready;
29+ sourceComponent: UbuntuShape {
30+ id: artShape;
31+ objectName: "artShape";
32+ radius: "medium";
33+ visible: image.status == Image.Ready;
34+ readonly property real fixedArtShapeSizeAspect: (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) ? root.fixedArtShapeSize.width / root.fixedArtShapeSize.height : -1;
35+ readonly property real aspect: fixedArtShapeSizeAspect > 0 ? fixedArtShapeSizeAspect : components !== undefined ? components["art"]["aspect-ratio"] : 1;
36+ readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect;
37+ Component.onCompleted: { updateWidthHeightBindings(); if (artShapeBorderSource !== undefined) borderSource = artShapeBorderSource; }
38+ onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings();
39+ Connections { target: root; onFixedArtShapeSizeChanged: updateWidthHeightBindings(); }
40+ function updateWidthHeightBindings() {
41+ if (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) {
42+ width = root.fixedArtShapeSize.width;
43+ height = root.fixedArtShapeSize.height;
44+ } else if (aspectSmallerThanImageAspect) {
45+ width = Qt.binding(function() { return !visible ? 0 : image.width });
46+ height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect });
47+ } else {
48+ width = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.width : height * image.aspect });
49+ height = Qt.binding(function() { return !visible ? 0 : image.height });
50+ }
51+ }
52+ image: Image {
53+ objectName: "artImage";
54+ source: cardData && cardData["art"] || "";
55+ cache: true;
56+ asynchronous: root.asynchronous;
57− fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop;
57+ fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop;
58− readonly property real aspect: implicitWidth / implicitHeight;
58+ readonly property real aspect: implicitWidth / implicitHeight;
59− width: root.width;
59+ width: root.width;
60− height: width / artShape.aspect;
60+ height: width / artShape.aspect;
61+ }
62+ }
61 }
63 }
62− }
64+ }
63− }
64− }
65readonly property int headerHeight: titleLabel.height;
65readonly property int headerHeight: titleLabel.height;
66Label {
66Label {
67− id: titleLabel;
67+ id: titleLabel;
68− objectName: "titleLabel";
68+ objectName: "titleLabel";
69− anchors { right: parent.right;left: parent.left;
69+ anchors { right: parent.right;
70+rightMargin: units.gu(1);
71+left: parent.left;
70top: artShapeHolder.bottom;
72top: artShapeHolder.bottom;
71 topMargin: units.gu(1);
73 topMargin: units.gu(1);
72leftMargin: units.gu(1);
74leftMargin: units.gu(1);
73− }
75+ }
74− elide: Text.ElideRight;
76+ elide: Text.ElideRight;
75− fontSize: "small";
77+ fontSize: "small";
76− wrapMode: Text.Wrap;
78+ wrapMode: Text.Wrap;
77− maximumLineCount: 2;
79+ maximumLineCount: 2;
78− font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
80+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
79− color: root.scopeStyle ? root.scopeStyle.foreground : "grey";
81+ color: root.scopeStyle ? root.scopeStyle.foreground : "grey";
80− visible: showHeader ;
82+ visible: showHeader ;
81− text: root.title;
83+ text: root.title;
82− font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
84+ font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
83− horizontalAlignment: root.headerAlignment;
85+ horizontalAlignment: root.headerAlignment;
84− }
86+ }
85implicitHeight: titleLabel.y + titleLabel.height + units.gu(1);
87implicitHeight: titleLabel.y + titleLabel.height + units.gu(1);
86}
88}
tests/plugins/Dash/cardcreator/2.res+94−91
@@ -5,8 +5,8 @@ AbstractButton {
5 property var cardData;
5 property var cardData;
6 property var artShapeBorderSource: undefined;
6 property var artShapeBorderSource: undefined;
7 property real fontScale: 1.0;
7 property real fontScale: 1.0;
8− property var scopeStyle: null;
8+ property var scopeStyle: null;
9− property int headerAlignment: Text.AlignLeft;
9+ property int headerAlignment: Text.AlignLeft;
10 property int fixedHeaderHeight: -1;
10 property int fixedHeaderHeight: -1;
11 property size fixedArtShapeSize: Qt.size(-1, -1);
11 property size fixedArtShapeSize: Qt.size(-1, -1);
12 readonly property string title: cardData && cardData["title"] || "";
12 readonly property string title: cardData && cardData["title"] || "";
@@ -14,104 +14,107 @@ AbstractButton {
14 property bool showHeader: true;
14 property bool showHeader: true;
15 implicitWidth: childrenRect.width;
15 implicitWidth: childrenRect.width;
16Loader {
16Loader {
17− id: backgroundLoader;
17+ id: backgroundLoader;
18− objectName: "backgroundLoader";
18+ objectName: "backgroundLoader";
19− anchors.fill: parent;
19+ anchors.fill: parent;
20− asynchronous: root.asynchronous;
20+ asynchronous: root.asynchronous;
21− visible: status == Loader.Ready;
21+ visible: status == Loader.Ready;
22− sourceComponent: UbuntuShape {
22+ sourceComponent: UbuntuShape {
23− objectName: "background";
23+ objectName: "background";
24− radius: "medium";
24+ radius: "medium";
25− color: getColor(0) || "white";
25+ color: getColor(0) || "white";
26− gradientColor: getColor(1) || color;
26+ gradientColor: getColor(1) || color;
27− anchors.fill: parent;
27+ anchors.fill: parent;
28− image: backgroundImage.source ? backgroundImage : null;
28+ image: backgroundImage.source ? backgroundImage : null;
29− property real luminance: 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
29+ property real luminance: 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
30− property Image backgroundImage: Image {
30+ property Image backgroundImage: Image {
31− objectName: "backgroundImage";
31+ objectName: "backgroundImage";
32− source: {
32+ source: {
33− if (cardData && typeof cardData["background"] === "string") return cardData["background"];
33+ if (cardData && typeof cardData["background"] === "string") return cardData["background"];
34− else if (template && typeof template["card-background"] === "string") return template["card-background"];
34+ else if (template && typeof template["card-background"] === "string") return template["card-background"];
35− else return "";
35+ else return "";
36− }
36+ }
37− }
37+ }
38− function getColor(index) {
38+ function getColor(index) {
39− if (cardData && typeof cardData["background"] === "object"
39+ if (cardData && typeof cardData["background"] === "object"
40− && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
40+ && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
41− return cardData["background"]["elements"][index];
41+ return cardData["background"]["elements"][index];
42− } else if (template && typeof template["card-background"] === "object"
42+ } else if (template && typeof template["card-background"] === "object"
43− && (template["card-background"]["type"] === "color" || template["card-background"]["type"] === "gradient")) {
43+ && (template["card-background"]["type"] === "color" || template["card-background"]["type"] === "gradient")) {
44− return template["card-background"]["elements"][index];
44+ return template["card-background"]["elements"][index];
45− } else return undefined;
45+ } else return undefined;
46− }
46+ }
47− }
47+ }
48− }
48+ }
49readonly property size artShapeSize: Qt.size(-1, -1);
49readonly property size artShapeSize: Qt.size(-1, -1);
50readonly property int headerHeight: row.height;
50readonly property int headerHeight: row.height;
51Row {
51Row {
52− id: row;
52+ id: row;
53− objectName: "outerRow";
53+ objectName: "outerRow";
54− property real margins: units.gu(1);
54+ property real margins: units.gu(1);
55− spacing: margins;
55+ spacing: margins;
56− height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight;
56+ height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight;
57− anchors { top: parent.top;
57+ anchors { top: parent.top;
58 topMargin: units.gu(1);
58 topMargin: units.gu(1);
59− left: parent.left;
59+left: parent.left;
60−}
60+ }
61− anchors.right: parent.right;
61+ anchors.right: parent.right;
62− anchors.margins: margins;
62+ anchors.margins: margins;
63−data: [ Image {
63+ data: [ Image {
64− id: mascotImage;
64+ id: mascotImage;
65− objectName: "mascotImage";
65+ objectName: "mascotImage";
66− anchors { verticalCenter: parent.verticalCenter; }
66+ anchors { verticalCenter: parent.verticalCenter; }
67− readonly property int maxSize: Math.max(width, height) * 4;
67+ readonly property int maxSize: Math.max(width, height) * 4;
68− source: cardData && cardData["mascot"];
68+ source: cardData && cardData["mascot"];
69− width: units.gu(6);
69+ width: units.gu(6);
70− height: units.gu(5.625);
70+ height: units.gu(5.625);
71− sourceSize { width: maxSize; height: maxSize }
71+ sourceSize { width: maxSize; height: maxSize }
72− fillMode: Image.PreserveAspectCrop;
72+ fillMode: Image.PreserveAspectCrop;
73− horizontalAlignment: Image.AlignHCenter;
73+ horizontalAlignment: Image.AlignHCenter;
74− verticalAlignment: Image.AlignVCenter;
74+ verticalAlignment: Image.AlignVCenter;
75− visible: showHeader;
76− }
77−,
78−Column {
79− anchors.verticalCenter: parent.verticalCenter;
80− spacing: units.dp(2);
81− width: parent.width - x;
82− data: [ Label {
83− id: titleLabel;
84− objectName: "titleLabel";
85− anchors { left: parent.left; right: parent.right }
86− elide: Text.ElideRight;
87− fontSize: "small";
88− wrapMode: Text.Wrap;
89− maximumLineCount: 2;
90− font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
91− color: backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : (root.scopeStyle ? root.scopeStyle.foreground : "grey");
92− visible: showHeader ;
75+ visible: showHeader;
93− text: root.title;
76+ }
94− font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
95− horizontalAlignment: root.headerAlignment;
96− }
97−,
98−Label {
99− id: subtitleLabel;
100− objectName: "subtitleLabel";
101− anchors { left: parent.left; right: parent.right }
102
77
78+ ,
79+ Column {
80+ anchors.verticalCenter: parent.verticalCenter;
81+ spacing: units.dp(2);
82+ width: parent.width - x;
83+ data: [ Label {
84+ id: titleLabel;
85+ objectName: "titleLabel";
86+ anchors { left: parent.left; right: parent.right }
103 elide: Text.ElideRight;
87 elide: Text.ElideRight;
104 fontSize: "small";
88 fontSize: "small";
89+ wrapMode: Text.Wrap;
90+ maximumLineCount: 2;
105 font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
91 font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
106− color: backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : (root.scopeStyle ? root.scopeStyle.foreground : "grey");
92+ color: backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : (root.scopeStyle ? root.scopeStyle.foreground : "grey");
107− visible: titleLabel.visible && titleLabel.text;
93+ visible: showHeader ;
108− text: cardData && cardData["subtitle"] || "";
94+ text: root.title;
109− font.weight: Font.Light;
95+ font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
110 horizontalAlignment: root.headerAlignment;
96 horizontalAlignment: root.headerAlignment;
111 }
97 }
98+
99+ ,
100+ Label {
101+ id: subtitleLabel;
102+ objectName: "subtitleLabel";
103+ anchors { left: parent.left; right: parent.right }
104+ elide: Text.ElideRight;
105+ fontSize: "small";
106+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
107+ color: backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : (root.scopeStyle ? root.scopeStyle.foreground : "grey");
108+ visible: titleLabel.visible && titleLabel.text;
109+ text: cardData && cardData["subtitle"] || "";
110+ font.weight: Font.Light;
111+ horizontalAlignment: root.headerAlignment;
112+ }
113+
112−]
114+ ]
113−}
115+ }
116+
114−]
117+ ]
115−}
118+ }
116implicitHeight: row.y + row.height + units.gu(1);
119implicitHeight: row.y + row.height + units.gu(1);
117}
120}
tests/plugins/Dash/cardcreator/3.res+77−74
@@ -5,8 +5,8 @@ AbstractButton {
5 property var cardData;
5 property var cardData;
6 property var artShapeBorderSource: undefined;
6 property var artShapeBorderSource: undefined;
7 property real fontScale: 1.0;
7 property real fontScale: 1.0;
8− property var scopeStyle: null;
8+ property var scopeStyle: null;
9− property int headerAlignment: Text.AlignLeft;
9+ property int headerAlignment: Text.AlignLeft;
10 property int fixedHeaderHeight: -1;
10 property int fixedHeaderHeight: -1;
11 property size fixedArtShapeSize: Qt.size(-1, -1);
11 property size fixedArtShapeSize: Qt.size(-1, -1);
12 readonly property string title: cardData && cardData["title"] || "";
12 readonly property string title: cardData && cardData["title"] || "";
@@ -16,89 +16,92 @@ AbstractButton {
16onArtShapeBorderSourceChanged: { if (artShapeBorderSource !== undefined && artShapeLoader.item) artShapeLoader.item.borderSource = artShapeBorderSource; }
16onArtShapeBorderSourceChanged: { if (artShapeBorderSource !== undefined && artShapeLoader.item) artShapeLoader.item.borderSource = artShapeBorderSource; }
17readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
17readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
18Item {
18Item {
19− id: artShapeHolder;
19+ id: artShapeHolder;
20− height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
20+ height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
21− width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
21+ width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
22− anchors { horizontalCenter: parent.horizontalCenter; }
22+ anchors { horizontalCenter: parent.horizontalCenter; }
23− Loader {
23+ Loader {
24− id: artShapeLoader;
24+ id: artShapeLoader;
25− objectName: "artShapeLoader";
25+ objectName: "artShapeLoader";
26− active: cardData && cardData["art"] || false;
26+ active: cardData && cardData["art"] || false;
27− asynchronous: root.asynchronous;
28− visible: status == Loader.Ready;
29− sourceComponent: UbuntuShape {
30− id: artShape;
31− objectName: "artShape";
32− radius: "medium";
33− visible: image.status == Image.Ready;
34− readonly property real fixedArtShapeSizeAspect: (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) ? root.fixedArtShapeSize.width / root.fixedArtShapeSize.height : -1;
35− readonly property real aspect: fixedArtShapeSizeAspect > 0 ? fixedArtShapeSizeAspect : components !== undefined ? components["art"]["aspect-ratio"] : 1;
36− readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect;
37− Component.onCompleted: { updateWidthHeightBindings(); if (artShapeBorderSource !== undefined) borderSource = artShapeBorderSource; }
38− onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings();
39− Connections { target: root; onFixedArtShapeSizeChanged: updateWidthHeightBindings(); }
40− function updateWidthHeightBindings() {
41− if (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) {
42− width = root.fixedArtShapeSize.width;
43− height = root.fixedArtShapeSize.height;
44− } else if (aspectSmallerThanImageAspect) {
45− width = Qt.binding(function() { return !visible ? 0 : image.width });
46− height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect });
47− } else {
48− width = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.width : height * image.aspect });
49− height = Qt.binding(function() { return !visible ? 0 : image.height });
50− }
51− }
52− image: Image {
53− objectName: "artImage";
54− source: cardData && cardData["art"] || "";
55− cache: true;
56 asynchronous: root.asynchronous;
27 asynchronous: root.asynchronous;
28+ visible: status == Loader.Ready;
29+ sourceComponent: UbuntuShape {
30+ id: artShape;
31+ objectName: "artShape";
32+ radius: "medium";
33+ visible: image.status == Image.Ready;
34+ readonly property real fixedArtShapeSizeAspect: (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) ? root.fixedArtShapeSize.width / root.fixedArtShapeSize.height : -1;
35+ readonly property real aspect: fixedArtShapeSizeAspect > 0 ? fixedArtShapeSizeAspect : components !== undefined ? components["art"]["aspect-ratio"] : 1;
36+ readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect;
37+ Component.onCompleted: { updateWidthHeightBindings(); if (artShapeBorderSource !== undefined) borderSource = artShapeBorderSource; }
38+ onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings();
39+ Connections { target: root; onFixedArtShapeSizeChanged: updateWidthHeightBindings(); }
40+ function updateWidthHeightBindings() {
41+ if (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) {
42+ width = root.fixedArtShapeSize.width;
43+ height = root.fixedArtShapeSize.height;
44+ } else if (aspectSmallerThanImageAspect) {
45+ width = Qt.binding(function() { return !visible ? 0 : image.width });
46+ height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect });
47+ } else {
48+ width = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.width : height * image.aspect });
49+ height = Qt.binding(function() { return !visible ? 0 : image.height });
50+ }
51+ }
52+ image: Image {
53+ objectName: "artImage";
54+ source: cardData && cardData["art"] || "";
55+ cache: true;
56+ asynchronous: root.asynchronous;
57− fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop;
57+ fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop;
58− readonly property real aspect: implicitWidth / implicitHeight;
58+ readonly property real aspect: implicitWidth / implicitHeight;
59− width: root.width;
59+ width: root.width;
60− height: width / artShape.aspect;
60+ height: width / artShape.aspect;
61+ }
62+ }
61 }
63 }
62− }
64+ }
63− }
64− }
65readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;
65readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;
66Label {
66Label {
67− id: titleLabel;
67+ id: titleLabel;
68− objectName: "titleLabel";
68+ objectName: "titleLabel";
69− anchors { right: parent.right;left: parent.left;
69+ anchors { right: parent.right;
70+rightMargin: units.gu(1);
71+left: parent.left;
70top: artShapeHolder.bottom;
72top: artShapeHolder.bottom;
71 topMargin: units.gu(1);
73 topMargin: units.gu(1);
72leftMargin: units.gu(1);
74leftMargin: units.gu(1);
73−}
74− elide: Text.ElideRight;
75− fontSize: "small";
76− wrapMode: Text.Wrap;
77− maximumLineCount: 2;
78− font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
79− color: root.scopeStyle ? root.scopeStyle.foreground : "grey";
80− visible: showHeader ;
81− text: root.title;
82− font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
83− horizontalAlignment: root.headerAlignment;
84− }
75+ }
85−Label {
86− id: subtitleLabel;
87− objectName: "subtitleLabel";
88− anchors { left: titleLabel.left;
89− leftMargin: titleLabel.leftMargin;
90− right: titleLabel.right;
91− top: titleLabel.bottom;
92− topMargin: units.dp(2);
93− }
94 elide: Text.ElideRight;
76 elide: Text.ElideRight;
95 fontSize: "small";
77 fontSize: "small";
78+ wrapMode: Text.Wrap;
79+ maximumLineCount: 2;
96 font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
80 font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
97− color: root.scopeStyle ? root.scopeStyle.foreground : "grey";
81+ color: root.scopeStyle ? root.scopeStyle.foreground : "grey";
98− visible: titleLabel.visible && titleLabel.text;
82+ visible: showHeader ;
99− text: cardData && cardData["subtitle"] || "";
83+ text: root.title;
100− font.weight: Font.Light;
84+ font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
101 horizontalAlignment: root.headerAlignment;
85 horizontalAlignment: root.headerAlignment;
102 }
86 }
87+Label {
88+ id: subtitleLabel;
89+ objectName: "subtitleLabel";
90+ anchors { left: titleLabel.left;
91+ leftMargin: titleLabel.leftMargin;
92+ right: titleLabel.right;
93+ rightMargin: titleLabel.rightMargin;
94+ top: titleLabel.bottom;
95+ topMargin: units.dp(2);
96+ }
97+ elide: Text.ElideRight;
98+ fontSize: "small";
99+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
100+ color: root.scopeStyle ? root.scopeStyle.foreground : "grey";
101+ visible: titleLabel.visible && titleLabel.text;
102+ text: cardData && cardData["subtitle"] || "";
103+ font.weight: Font.Light;
104+ horizontalAlignment: root.headerAlignment;
105+ }
103implicitHeight: subtitleLabel.y + subtitleLabel.height + units.gu(1);
106implicitHeight: subtitleLabel.y + subtitleLabel.height + units.gu(1);
104}
107}
tests/plugins/Dash/cardcreator/5.res+119−117
@@ -4,9 +4,9 @@ AbstractButton {
4 property var components;
4 property var components;
5 property var cardData;
5 property var cardData;
6 property var artShapeBorderSource: undefined;
6 property var artShapeBorderSource: undefined;
7− property real fontScale: 1.0;
7+ property real fontScale: 1.0;
8− property var scopeStyle: null;
8+ property var scopeStyle: null;
9− property int headerAlignment: Text.AlignLeft;
9+ property int headerAlignment: Text.AlignLeft;
10 property int fixedHeaderHeight: -1;
10 property int fixedHeaderHeight: -1;
11 property size fixedArtShapeSize: Qt.size(-1, -1);
11 property size fixedArtShapeSize: Qt.size(-1, -1);
12 readonly property string title: cardData && cardData["title"] || "";
12 readonly property string title: cardData && cardData["title"] || "";
@@ -16,130 +16,132 @@ AbstractButton {
16onArtShapeBorderSourceChanged: { if (artShapeBorderSource !== undefined && artShapeLoader.item) artShapeLoader.item.borderSource = artShapeBorderSource; }
16onArtShapeBorderSourceChanged: { if (artShapeBorderSource !== undefined && artShapeLoader.item) artShapeLoader.item.borderSource = artShapeBorderSource; }
17readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
17readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);
18Item {
18Item {
19− id: artShapeHolder;
19+ id: artShapeHolder;
20− height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
20+ height: root.fixedArtShapeSize.height > 0 ? root.fixedArtShapeSize.height : artShapeLoader.height;
21− width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
21+ width: root.fixedArtShapeSize.width > 0 ? root.fixedArtShapeSize.width : artShapeLoader.width;
22− anchors { horizontalCenter: parent.horizontalCenter; }
22+ anchors { horizontalCenter: parent.horizontalCenter; }
23− Loader {
23+ Loader {
24− id: artShapeLoader;
24+ id: artShapeLoader;
25− objectName: "artShapeLoader";
25+ objectName: "artShapeLoader";
26− active: cardData && cardData["art"] || false;
26+ active: cardData && cardData["art"] || false;
27− asynchronous: root.asynchronous;
28− visible: status == Loader.Ready;
29− sourceComponent: UbuntuShape {
30− id: artShape;
31− objectName: "artShape";
32− radius: "medium";
33− visible: image.status == Image.Ready;
34− readonly property real fixedArtShapeSizeAspect: (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) ? root.fixedArtShapeSize.width / root.fixedArtShapeSize.height : -1;
35− readonly property real aspect: fixedArtShapeSizeAspect > 0 ? fixedArtShapeSizeAspect : components !== undefined ? components["art"]["aspect-ratio"] : 1;
36− readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect;
37− Component.onCompleted: { updateWidthHeightBindings(); if (artShapeBorderSource !== undefined) borderSource = artShapeBorderSource; }
38− onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings();
39− Connections { target: root; onFixedArtShapeSizeChanged: updateWidthHeightBindings(); }
40− function updateWidthHeightBindings() {
41− if (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) {
42− width = root.fixedArtShapeSize.width;
43− height = root.fixedArtShapeSize.height;
44− } else if (aspectSmallerThanImageAspect) {
45− width = Qt.binding(function() { return !visible ? 0 : image.width });
46− height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect });
47− } else {
48− width = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.width : height * image.aspect });
49− height = Qt.binding(function() { return !visible ? 0 : image.height });
50− }
51− }
52− image: Image {
53− objectName: "artImage";
54− source: cardData && cardData["art"] || "";
55− cache: true;
56 asynchronous: root.asynchronous;
27 asynchronous: root.asynchronous;
28+ visible: status == Loader.Ready;
29+ sourceComponent: UbuntuShape {
30+ id: artShape;
31+ objectName: "artShape";
32+ radius: "medium";
33+ visible: image.status == Image.Ready;
34+ readonly property real fixedArtShapeSizeAspect: (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) ? root.fixedArtShapeSize.width / root.fixedArtShapeSize.height : -1;
35+ readonly property real aspect: fixedArtShapeSizeAspect > 0 ? fixedArtShapeSizeAspect : components !== undefined ? components["art"]["aspect-ratio"] : 1;
36+ readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect;
37+ Component.onCompleted: { updateWidthHeightBindings(); if (artShapeBorderSource !== undefined) borderSource = artShapeBorderSource; }
38+ onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings();
39+ Connections { target: root; onFixedArtShapeSizeChanged: updateWidthHeightBindings(); }
40+ function updateWidthHeightBindings() {
41+ if (root.fixedArtShapeSize.height > 0 && root.fixedArtShapeSize.width > 0) {
42+ width = root.fixedArtShapeSize.width;
43+ height = root.fixedArtShapeSize.height;
44+ } else if (aspectSmallerThanImageAspect) {
45+ width = Qt.binding(function() { return !visible ? 0 : image.width });
46+ height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect });
47+ } else {
48+ width = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.width : height * image.aspect });
49+ height = Qt.binding(function() { return !visible ? 0 : image.height });
50+ }
51+ }
52+ image: Image {
53+ objectName: "artImage";
54+ source: cardData && cardData["art"] || "";
55+ cache: true;
56+ asynchronous: root.asynchronous;
57− fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop;
57+ fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop;
58− readonly property real aspect: implicitWidth / implicitHeight;
58+ readonly property real aspect: implicitWidth / implicitHeight;
59− width: root.width;
59+ width: root.width;
60− height: width / artShape.aspect;
60+ height: width / artShape.aspect;
61+ }
62+ }
61 }
63 }
62− }
64+ }
63− }
64− }
65Loader {
65Loader {
66− id: overlayLoader;
66+ id: overlayLoader;
67− anchors {
67+ anchors {
68− left: artShapeHolder.left;
68+ left: artShapeHolder.left;
69− right: artShapeHolder.right;
69+ right: artShapeHolder.right;
70− bottom: artShapeHolder.bottom;
70+ bottom: artShapeHolder.bottom;
71− }
71+ }
72− active: artShapeLoader.active && artShapeLoader.item && artShapeLoader.item.image.status === Image.Ready || false;
72+ active: artShapeLoader.active && artShapeLoader.item && artShapeLoader.item.image.status === Image.Ready || false;
73− asynchronous: root.asynchronous;
73+ asynchronous: root.asynchronous;
74− visible: showHeader && status == Loader.Ready;
74+ visible: showHeader && status == Loader.Ready;
75− sourceComponent: ShaderEffect {
75+ sourceComponent: ShaderEffect {
76− id: overlay;
76+ id: overlay;
77− height: (fixedHeaderHeight > 0 ? fixedHeaderHeight : headerHeight) + units.gu(2);
77+ height: (fixedHeaderHeight > 0 ? fixedHeaderHeight : headerHeight) + units.gu(2);
78− opacity: 0.6;
78+ opacity: 0.6;
79− property var source: ShaderEffectSource {
79+ property var source: ShaderEffectSource {
80− id: shaderSource;
80+ id: shaderSource;
81− sourceItem: artShapeLoader.item;
81+ sourceItem: artShapeLoader.item;
82− onVisibleChanged: if (visible) scheduleUpdate();
82+ onVisibleChanged: if (visible) scheduleUpdate();
83− live: false;
83+ live: false;
84− sourceRect: Qt.rect(0, artShapeLoader.height - overlay.height, artShapeLoader.width, overlay.height);
84+ sourceRect: Qt.rect(0, artShapeLoader.height - overlay.height, artShapeLoader.width, overlay.height);
85− }
85+ }
86− vertexShader: "
86+ vertexShader: "
87− uniform highp mat4 qt_Matrix;
87+ uniform highp mat4 qt_Matrix;
88− attribute highp vec4 qt_Vertex;
88+ attribute highp vec4 qt_Vertex;
89− attribute highp vec2 qt_MultiTexCoord0;
89+ attribute highp vec2 qt_MultiTexCoord0;
90− varying highp vec2 coord;
90+ varying highp vec2 coord;
91− void main() {
91+ void main() {
92− coord = qt_MultiTexCoord0;
92+ coord = qt_MultiTexCoord0;
93− gl_Position = qt_Matrix * qt_Vertex;
93+ gl_Position = qt_Matrix * qt_Vertex;
94− }";
94+ }";
95− fragmentShader: "
95+ fragmentShader: "
96− varying highp vec2 coord;
96+ varying highp vec2 coord;
97− uniform sampler2D source;
97+ uniform sampler2D source;
98− uniform lowp float qt_Opacity;
98+ uniform lowp float qt_Opacity;
99− void main() {
99+ void main() {
100− lowp vec4 tex = texture2D(source, coord);
100+ lowp vec4 tex = texture2D(source, coord);
101− gl_FragColor = vec4(0, 0, 0, tex.a) * qt_Opacity;
101+ gl_FragColor = vec4(0, 0, 0, tex.a) * qt_Opacity;
102− }";
102+ }";
103− }
103+ }
104− }
104+ }
105readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;
105readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;
106Label {
106Label {
107− id: titleLabel;
107+ id: titleLabel;
108− objectName: "titleLabel";
108+ objectName: "titleLabel";
109− anchors { left: parent.left;
109+ anchors { left: parent.left;
110− leftMargin: units.gu(1);
110+ leftMargin: units.gu(1);
111− right: parent.right;
111+ right: parent.right;
112− top: overlayLoader.top;
113− topMargin: units.gu(1);
112+ rightMargin: units.gu(1);
114−}
115− elide: Text.ElideRight;
116− fontSize: "small";
117− wrapMode: Text.Wrap;
118− maximumLineCount: 2;
119− font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
120− color: "white";
121− visible: showHeader && overlayLoader.active;
113+ top: overlayLoader.top;
122− text: root.title;
123− font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
124− horizontalAlignment: root.headerAlignment;
125− }
126−Label {
127− id: subtitleLabel;
128− objectName: "subtitleLabel";
129− anchors { left: titleLabel.left;
130− leftMargin: titleLabel.leftMargin;
131− right: titleLabel.right;
132− top: titleLabel.bottom;
133− topMargin: units.dp(2);
114+ topMargin: units.gu(1);
134− }
115+ }
135 elide: Text.ElideRight;
116 elide: Text.ElideRight;
136 fontSize: "small";
117 fontSize: "small";
118+ wrapMode: Text.Wrap;
119+ maximumLineCount: 2;
137 font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
120 font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
138 color: "white";
121 color: "white";
139− visible: titleLabel.visible && titleLabel.text;
122+ visible: showHeader && overlayLoader.active;
140− text: cardData && cardData["subtitle"] || "";
123+ text: root.title;
141− font.weight: Font.Light;
124+ font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
142 horizontalAlignment: root.headerAlignment;
125 horizontalAlignment: root.headerAlignment;
143 }
126 }
127+Label {
128+ id: subtitleLabel;
129+ objectName: "subtitleLabel";
130+ anchors { left: titleLabel.left;
131+ leftMargin: titleLabel.leftMargin;
132+ right: titleLabel.right;
133+ rightMargin: titleLabel.rightMargin;
134+ top: titleLabel.bottom;
135+ topMargin: units.dp(2);
136+ }
137+ elide: Text.ElideRight;
138+ fontSize: "small";
139+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
140+ color: "white";
141+ visible: titleLabel.visible && titleLabel.text;
142+ text: cardData && cardData["subtitle"] || "";
143+ font.weight: Font.Light;
144+ horizontalAlignment: root.headerAlignment;
145+ }
144implicitHeight: subtitleLabel.y + subtitleLabel.height + units.gu(1);
146implicitHeight: subtitleLabel.y + subtitleLabel.height + units.gu(1);
145}
147}
tests/plugins/Dash/cardcreator/7.resnew file+128
@@ -0,0 +1,128 @@
1+AbstractButton {
2+ id: root;
3+ property var template;
4+ property var components;
5+ property var cardData;
6+ property var artShapeBorderSource: undefined;
7+ property real fontScale: 1.0;
8+ property var scopeStyle: null;
9+ property int headerAlignment: Text.AlignLeft;
10+ property int fixedHeaderHeight: -1;
11+ property size fixedArtShapeSize: Qt.size(-1, -1);
12+ readonly property string title: cardData && cardData["title"] || "";
13+ property bool asynchronous: true;
14+ property bool showHeader: true;
15+ implicitWidth: childrenRect.width;
16+Loader {
17+ id: backgroundLoader;
18+ objectName: "backgroundLoader";
19+ anchors.fill: parent;
20+ asynchronous: root.asynchronous;
21+ visible: status == Loader.Ready;
22+ sourceComponent: UbuntuShape {
23+ objectName: "background";
24+ radius: "medium";
25+ color: getColor(0) || "white";
26+ gradientColor: getColor(1) || color;
27+ anchors.fill: parent;
28+ image: backgroundImage.source ? backgroundImage : null;
29+ property real luminance: 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
30+ property Image backgroundImage: Image {
31+ objectName: "backgroundImage";
32+ source: {
33+ if (cardData && typeof cardData["background"] === "string") return cardData["background"];
34+ else if (template && typeof template["card-background"] === "string") return template["card-background"];
35+ else return "";
36+ }
37+ }
38+ function getColor(index) {
39+ if (cardData && typeof cardData["background"] === "object"
40+ && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
41+ return cardData["background"]["elements"][index];
42+ } else if (template && typeof template["card-background"] === "object"
43+ && (template["card-background"]["type"] === "color" || template["card-background"]["type"] === "gradient")) {
44+ return template["card-background"]["elements"][index];
45+ } else return undefined;
46+ }
47+ }
48+ }
49+readonly property size artShapeSize: Qt.size(-1, -1);
50+readonly property int headerHeight: row.height;
51+Row {
52+ id: row;
53+ objectName: "outerRow";
54+ property real margins: units.gu(1);
55+ spacing: margins;
56+ height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight;
57+ anchors { top: parent.top;
58+ topMargin: units.gu(1);
59+left: parent.left;
60+ }
61+ anchors.right: parent.right;
62+ anchors.margins: margins;
63+ data: [ Image {
64+ id: mascotImage;
65+ objectName: "mascotImage";
66+ anchors { verticalCenter: parent.verticalCenter; }
67+ readonly property int maxSize: Math.max(width, height) * 4;
68+ source: cardData && cardData["mascot"];
69+ width: units.gu(6);
70+ height: units.gu(5.625);
71+ sourceSize { width: maxSize; height: maxSize }
72+ fillMode: Image.PreserveAspectCrop;
73+ horizontalAlignment: Image.AlignHCenter;
74+ verticalAlignment: Image.AlignVCenter;
75+ visible: showHeader;
76+ }
77+
78+ ,
79+ Column {
80+ anchors.verticalCenter: parent.verticalCenter;
81+ spacing: units.dp(2);
82+ width: parent.width - x;
83+ data: [
84+ Label {
85+ id: titleLabel;
86+ objectName: "titleLabel";
87+ anchors { left: parent.left; right: parent.right }
88+ elide: Text.ElideRight;
89+ fontSize: "small";
90+ wrapMode: Text.Wrap;
91+ maximumLineCount: 2;
92+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
93+ color: backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : (root.scopeStyle ? root.scopeStyle.foreground : "grey");
94+ visible: showHeader ;
95+ text: root.title;
96+ font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
97+ horizontalAlignment: root.headerAlignment;
98+ }
99+,
100+Label {
101+ id: subtitleLabel;
102+ objectName: "subtitleLabel";
103+ anchors { left: parent.left; right: parent.right }
104+ elide: Text.ElideRight;
105+ fontSize: "small";
106+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
107+ color: backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : (root.scopeStyle ? root.scopeStyle.foreground : "grey");
108+ visible: titleLabel.visible && titleLabel.text;
109+ text: cardData && cardData["subtitle"] || "";
110+ font.weight: Font.Light;
111+ horizontalAlignment: root.headerAlignment;
112+ }
113+,
114+CardAttributes {
115+ id: attributesRow;
116+ objectName: "attributesRow";
117+ anchors { left: parent.left; right: parent.right }
118+ color: backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : (root.scopeStyle ? root.scopeStyle.foreground : "grey");
119+ model: cardData["attributes"]
120+ }
121+
122+ ]
123+ }
124+
125+ ]
126+ }
127+implicitHeight: row.y + row.height + units.gu(1);
128+}
tests/plugins/Dash/cardcreator/7.tstnew file+3
@@ -0,0 +1,3 @@
1+template: {"card-background":{"elements":["#E9E9E9"],"type":"color"},"card-layout":"vertical","card-size":"medium","category-layout":"grid","collapsed-rows":2}
2+components: {"art":{"aspect-ratio":1,"fill-mode":"crop"},"background":{"field":"background"},"mascot":{"field":"icon"},"subtitle":{"field":"author"},"title":{"field":"title"},"attributes":{"field":"attributes","max-count":2}}
3+result: 7.res
tests/plugins/Dash/cardcreatortest.cpp+9−1
@@ -19,6 +19,7 @@
19#include <QQuickItem>
19#include <QQuickItem>
20#include <QQuickView>
20#include <QQuickView>
21#include <QtTestGui>
21#include <QtTestGui>
22+#include <QTemporaryFile>
22
23
23class CardCreatorTest : public QObject
24class CardCreatorTest : public QObject
24{
25{
@@ -74,7 +75,14 @@ private Q_SLOTS:
74 QVERIFY(testResultFile.open(QIODevice::ReadOnly));
75 QVERIFY(testResultFile.open(QIODevice::ReadOnly));
75 QTextStream ts2(&testResultFile);
76 QTextStream ts2(&testResultFile);
76 const QString expectedResult = ts2.readAll();
77 const QString expectedResult = ts2.readAll();
78+ const QString executedResult = cardStringResult.toString();
79+ // Record failed results to /tmp
80+ QTemporaryFile tmpFile(QDir::tempPath() + QDir::separator() + "testCardCreatorFailedResultXXXXXX");
81+ tmpFile.open();
82+ tmpFile.setAutoRemove(false);
83+ tmpFile.write(executedResult.toUtf8().constData());
77− QCOMPARE(cardStringResult.toString().simplified(), expectedResult.simplified());
84+ QCOMPARE(executedResult.simplified(), expectedResult.simplified());
85+ tmpFile.setAutoRemove(true); // Remove the result if it passed
78
86
79 QVariant createCardComponentResult;
87 QVariant createCardComponentResult;
80 QMetaObject::invokeMethod(view->rootObject(), "createCardComponent", Q_RETURN_ARG(QVariant, createCardComponentResult), Q_ARG(QVariant, templateJSON), Q_ARG(QVariant, componentsJSON));
88 QMetaObject::invokeMethod(view->rootObject(), "createCardComponent", Q_RETURN_ARG(QVariant, createCardComponentResult), Q_ARG(QVariant, templateJSON), Q_ARG(QVariant, componentsJSON));
tests/plugins/Dash/listviewwithpageheadersectiontest.cpp−1
@@ -40,7 +40,6 @@ private:
40 QTRY_COMPARE(lvwph->m_visibleItems[visibleIndex]->height(), height);
40 QTRY_COMPARE(lvwph->m_visibleItems[visibleIndex]->height(), height);
41 QCOMPARE(QQuickItemPrivate::get(lvwph->m_visibleItems[visibleIndex]->m_item)->culled, culled);
41 QCOMPARE(QQuickItemPrivate::get(lvwph->m_visibleItems[visibleIndex]->m_item)->culled, culled);
42 QCOMPARE(section(lvwph->m_visibleItems[visibleIndex]->sectionItem()), sectionHeader);
42 QCOMPARE(section(lvwph->m_visibleItems[visibleIndex]->sectionItem()), sectionHeader);
43− QTRY_COMPARE(hasSectionHeaderProperty(lvwph->m_visibleItems[visibleIndex]->m_item), !sectionHeader.isNull());
44 if (!sectionHeader.isNull()) {
43 if (!sectionHeader.isNull()) {
45 QCOMPARE(QQuickItemPrivate::get(lvwph->m_visibleItems[visibleIndex]->sectionItem())->culled, sectionHeaderCulled);
44 QCOMPARE(QQuickItemPrivate::get(lvwph->m_visibleItems[visibleIndex]->sectionItem())->culled, sectionHeaderCulled);
46 QCOMPARE(sectionDelegateIndex(lvwph->m_visibleItems[visibleIndex]->sectionItem()), lvwph->m_firstVisibleIndex + visibleIndex);
45 QCOMPARE(sectionDelegateIndex(lvwph->m_visibleItems[visibleIndex]->sectionItem()), lvwph->m_firstVisibleIndex + visibleIndex);
tests/plugins/Dash/tst_CardAttributes.qmlnew file+54
@@ -0,0 +1,54 @@
1+/*
2+ * Copyright 2013 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import QtTest 1.0
19+import Dash 0.1
20+
21+Item {
22+ width: units.gu(40)
23+ height: units.gu(4.5)
24+
25+ property var testData: [
26+ [{"value":"text1","icon":"image://theme/ok"},{"value":"text2","icon":"image://theme/cancel"}],
27+ [{"value":"text1","icon":"image://theme/ok"},{"value":"text2","icon":"image://theme/cancel"},{"value":"text3"}],
28+ [{"value":"text1","icon":"image://theme/ok"},{"value":"text2","icon":"image://theme/cancel"},{"value":"text3"},{"value":"text4"}],
29+ [{"value":"text1","icon":"image://theme/ok"},{"value":"text2","icon":"image://theme/cancel"},{"value":"text3","style":"highlighted"},{"value":"text4","icon":"image://theme/close","style":"highlighted"},{"value":"text5"}]
30+ ]
31+
32+ CardAttributes {
33+ id: cardAttributes
34+ model: testData[3]
35+ clip: true
36+ }
37+
38+ TestCase {
39+ name: "CardAttributesTest"
40+ when: windowShown
41+
42+ function init() {
43+ }
44+
45+ function test_columns_data() {
46+ return testData;
47+ }
48+
49+ function test_columns(data) {
50+ cardAttributes.model = data;
51+ compare(cardAttributes.columns, 2 + data.length % 2);
52+ }
53+ }
54+}
tests/qmltests/CMakeLists.txt−1
@@ -40,7 +40,6 @@ add_qml_test(Dash Card)
40add_qml_benchmark(Dash CardBenchmark 1000)
40add_qml_benchmark(Dash CardBenchmark 1000)
41add_qml_test(Dash CardTool)
41add_qml_test(Dash CardTool)
42add_qml_test(Dash GenericScopeView)
42add_qml_test(Dash GenericScopeView)
43−add_qml_test(Dash/Apps RunningApplicationsGrid)
44add_qml_test(Dash/Previews Preview)
43add_qml_test(Dash/Previews Preview)
45add_qml_test(Dash/Previews PreviewActions)
44add_qml_test(Dash/Previews PreviewActions)
46add_qml_test(Dash/Previews PreviewAudioPlayback)
45add_qml_test(Dash/Previews PreviewAudioPlayback)
tests/qmltests/Components/tst_PageHeader.qml+36−6
@@ -36,6 +36,16 @@ Item {
36
36
37 function init() {
37 function init() {
38 searchEnabled = true;
38 searchEnabled = true;
39+
40+ // Reset to initial state
41+ pageHeader.searchQuery = "";
42+ pageHeader.closePopup();
43+ pageHeader.searchHistory.clear();
44+
45+ // Check initial state
46+ var headerContainer = findChild(pageHeader, "headerContainer");
47+ tryCompareFunction(function() { return headerContainer.popover === null; }, true);
48+ compare(pageHeader.searchHistory.count, 0);
39 }
49 }
40
50
41 function test_search_disabled() {
51 function test_search_disabled() {
@@ -80,9 +90,6 @@ Item {
80 }
90 }
81
91
82 function test_history() {
92 function test_history() {
83− pageHeader.searchHistory.clear()
84− compare(pageHeader.searchHistory.count, 0)
85−
86 pageHeader.triggerSearch()
93 pageHeader.triggerSearch()
87 typeString("humppa1")
94 typeString("humppa1")
88 pageHeader.resetSearch()
95 pageHeader.resetSearch()
@@ -143,16 +150,26 @@ Item {
143 tryCompareFunction(function() { return headerContainer.popover !== null; }, true);
150 tryCompareFunction(function() { return headerContainer.popover !== null; }, true);
144
151
145 tryCompare(headerContainer.popover, "visible", true);
152 tryCompare(headerContainer.popover, "visible", true);
153+
154+ var searchTextField = findChild(pageHeader, "searchTextField");
155+ compare(searchTextField.focus, true);
156+
157+ var recentSearches = findChild(headerContainer.popover, "recentSearches");
158+ mouseClick(recentSearches.itemAt(0), 0, 0);
159+
160+ compare(pageHeader.searchQuery, "Search2");
161+ tryCompareFunction(function() { return headerContainer.popover === null; }, true);
162+ compare(searchTextField.focus, false);
146 }
163 }
147
164
148− function test_tap_outside_closes_popup_data() {
165+ function test_popup_closing_data() {
149 return [
166 return [
150 { tag: "with search text", searchText: "foobar", hideSearch: false },
167 { tag: "with search text", searchText: "foobar", hideSearch: false },
151 { tag: "without search text", searchText: "", hideSearch: true }
168 { tag: "without search text", searchText: "", hideSearch: true }
152 ];
169 ];
153 }
170 }
154
171
155− function test_tap_outside_closes_popup(data) {
172+ function test_popup_closing(data) {
156 searchEnabled = true;
173 searchEnabled = true;
157 pageHeader.searchHistory.clear();
174 pageHeader.searchHistory.clear();
158
175
@@ -168,13 +185,26 @@ Item {
168
185
169 pageHeader.searchQuery = data.searchText;
186 pageHeader.searchQuery = data.searchText;
170
187
188+ if (data.searchText == "") {
189+ // When the text is empty the user can also close the
190+ // popup by clicking outside the header instead of by starting a search
171− mouseClick(root, root.width / 2, root.height - 1);
191+ mouseClick(root, root.width / 2, root.height - 1);
192+ }
172
193
173 tryCompare(headerContainer, "showSearch", !data.hideSearch);
194 tryCompare(headerContainer, "showSearch", !data.hideSearch);
174 tryCompareFunction(function() { return headerContainer.popover === null; }, true);
195 tryCompareFunction(function() { return headerContainer.popover === null; }, true);
175
196
176 pageHeader.resetSearch();
197 pageHeader.resetSearch();
177 }
198 }
199+
200+ function test_search_change_shows_search() {
201+ var headerContainer = findChild(pageHeader, "headerContainer");
202+ compare(headerContainer.showSearch, false);
203+ compare(searchQuery, "");
204+
205+ searchQuery = "H";
206+ compare(headerContainer.showSearch, true);
207+ }
178 }
208 }
179
209
180 Column {
210 Column {
tests/qmltests/Dash/Apps/tst_RunningApplicationsGrid.qmldeleted−217
@@ -1,217 +0,0 @@
1−/*
2− * Copyright 2013 Canonical Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import QtTest 1.0
19−import "../../../../qml/Dash/Apps"
20−import Unity.Test 0.1 as UT
21−import Unity.Application 0.1
22−import Ubuntu.Components 1.1
23−
24−// Using Rectangle to have an opaque surface because AppManager paints app surfaces behind it.
25−Rectangle {
26− width: units.gu(70)
27− height: units.gu(40)
28−
29− function resetRunningApplications() {
30− while (ApplicationManager.count > 0) {
31− ApplicationManager.stopApplication(ApplicationManager.get(0).appId)
32− }
33−
34− ApplicationManager.startApplication("dialer-app");
35− ApplicationManager.startApplication("webbrowser-app");
36− }
37−
38− Component.onCompleted: {
39− resetRunningApplications()
40− }
41−
42− // The component under test
43− RunningApplicationsGrid {
44− id: runningApplicationsGrid
45− orientationAngle: orientationAngleSelector.selectedIndex * 90
46− anchors.left: parent.left
47− anchors.right: controlPanel.left
48− anchors.top: parent.top
49− anchors.bottom: parent.bottom
50− model: ApplicationManager
51− }
52−
53− Rectangle {
54− id: controlPanel
55− anchors.right: parent.right
56− anchors.top: parent.top
57− anchors.bottom: parent.bottom
58− width: units.gu(20)
59− color: "black"
60−
61− OptionSelector {
62− id: orientationAngleSelector
63− model: ["0","90","180","270"]
64− text: "Orientation Angle"
65− width: parent.width
66− }
67− }
68−
69− UT.UnityTestCase {
70− name: "RunningApplicationsGrid"
71− when: windowShown
72−
73− function init() {
74− runningApplicationsGrid.terminationModeEnabled = false
75− resetRunningApplications()
76− }
77−
78− property var browserTile
79− property var phoneTile
80−
81− property var isBrowserLongPressed: false
82− function onBrowserLongPressed() {isBrowserLongPressed = true}
83−
84− property var isPhoneLongPressed: false
85− function onPhoneLongPressed() {isPhoneLongPressed = true}
86−
87− // Tiles should go to termination mode when any one of them is long-pressed.
88− // Long-pressing when they're in termination mode brings them back to activation mode
89− function test_enterTerminationMode() {
90− browserTile = findChild(runningApplicationsGrid, "runningAppTile Browser")
91− verify(browserTile != undefined)
92− browserTile.onPressAndHold.connect(onBrowserLongPressed)
93−
94− phoneTile = findChild(runningApplicationsGrid, "runningAppTile Dialer")
95− verify(phoneTile != undefined)
96− phoneTile.onPressAndHold.connect(onPhoneLongPressed)
97−
98− compare(browserTile.terminationModeEnabled, false)
99− compare(phoneTile.terminationModeEnabled, false)
100− compare(runningApplicationsGrid.terminationModeEnabled, false)
101−
102− isBrowserLongPressed = false
103− mousePress(browserTile, browserTile.width/2, browserTile.height/2)
104− tryCompareFunction(checkSwitchToTerminationModeAfterLongPress, true)
105−
106− mouseRelease(browserTile, browserTile.width/2, browserTile.height/2)
107−
108− compare(browserTile.terminationModeEnabled, true)
109− compare(phoneTile.terminationModeEnabled, true)
110− compare(runningApplicationsGrid.terminationModeEnabled, true)
111−
112− isPhoneLongPressed = false
113− mousePress(phoneTile, phoneTile.width/2, phoneTile.height/2)
114− tryCompareFunction(checkSwitchToActivationModeAfterLongPress, true)
115−
116− mouseRelease(phoneTile, phoneTile.width/2, phoneTile.height/2)
117−
118− compare(browserTile.terminationModeEnabled, false)
119− compare(phoneTile.terminationModeEnabled, false)
120− compare(runningApplicationsGrid.terminationModeEnabled, false)
121−
122− browserTile.onPressAndHold.disconnect(onBrowserLongPressed)
123− phoneTile.onPressAndHold.disconnect(onPhoneLongPressed)
124− }
125−
126− // Checks that components swicth to termination mode after (and only after) a long
127− // press happens on Browser tile.
128− function checkSwitchToTerminationModeAfterLongPress() {
129− compare(browserTile.terminationModeEnabled, isBrowserLongPressed)
130− compare(phoneTile.terminationModeEnabled, isBrowserLongPressed)
131− compare(runningApplicationsGrid.terminationModeEnabled, isBrowserLongPressed)
132−
133− return isBrowserLongPressed &&
134− browserTile.terminationModeEnabled &&
135− phoneTile.terminationModeEnabled &&
136− runningApplicationsGrid.terminationModeEnabled
137− }
138−
139− // Checks that components swicth to activation mode after (and only after) a long
140− // press happens on Phone tile.
141− function checkSwitchToActivationModeAfterLongPress() {
142− compare(browserTile.terminationModeEnabled, !isPhoneLongPressed)
143− compare(phoneTile.terminationModeEnabled, !isPhoneLongPressed)
144− compare(runningApplicationsGrid.terminationModeEnabled, !isPhoneLongPressed)
145−
146− return isPhoneLongPressed &&
147− !browserTile.terminationModeEnabled &&
148− !phoneTile.terminationModeEnabled &&
149− !runningApplicationsGrid.terminationModeEnabled
150− }
151−
152− // While on termination mode, clicking a running application tile, outside of
153− // the close icon should do nothing
154− function test_clickTileNotClose() {
155− runningApplicationsGrid.terminationModeEnabled = true
156−
157− var browserTile = findChild(runningApplicationsGrid, "runningAppTile Browser")
158− verify(browserTile != undefined)
159−
160− verify(ApplicationManager.findApplication("webbrowser-app") !== null)
161−
162− mouseClick(browserTile, browserTile.width/2, browserTile.height/2)
163−
164− verify(ApplicationManager.findApplication("webbrowser-app") !== null)
165−
166− // The tile for the Browser app should stay there
167− tryCompareFunction(checkBrowserTileExists, true)
168− }
169−
170− // While in termination mode, clicking on a running application tile's close icon
171− // causes the corresponding application to be terminated
172− function test_clickCloseIconToTerminateApp() {
173− runningApplicationsGrid.terminationModeEnabled = true
174−
175− var browserTile = findChild(runningApplicationsGrid, "runningAppTile Browser")
176− var browserTileCloseButton = findChild(runningApplicationsGrid, "closeIcon Browser")
177−
178− verify(browserTile != undefined)
179− verify(browserTileCloseButton != undefined)
180− verify(ApplicationManager.findApplication("webbrowser-app") !== 0)
181−
182− mouseClick(browserTileCloseButton, browserTileCloseButton.width/2, browserTileCloseButton.height/2)
183− wait(0) // spin event loop to start any pending animation
184−
185− verify(ApplicationManager.findApplication("webbrowser-app") === null)
186−
187− // The tile for the Browser app should eventually vanish since the
188− // application has been terminated.
189− tryCompareFunction(checkBrowserTileExists, false)
190− }
191−
192− function checkBrowserTileExists() {
193− return findChild(runningApplicationsGrid, "runningAppTile Browser")
194− != undefined
195− }
196−
197− // While in termination mode, if you click outside any of the tiles, the
198− // termination mode is disabled (i.e. we switch back to activation mode).
199− function test_clickOutsideTilesDisablesTerminationMode() {
200− runningApplicationsGrid.terminationModeEnabled = true
201−
202− var browserTile = findChild(runningApplicationsGrid, "runningAppTile Browser")
203− verify(browserTile != undefined)
204−
205− verify(runningApplicationsGrid.terminationModeEnabled);
206−
207− // Click on the bottom right corner of the grid, where there's no
208− // RunningApplicationTile lying around
209− mouseClick(runningApplicationsGrid,
210− runningApplicationsGrid.width - 1, runningApplicationsGrid.height - 1);
211−
212− wait(0) // spin event loop to ensure that any pending signal emission went through
213−
214− verify(!runningApplicationsGrid.terminationModeEnabled);
215− }
216− }
217−}
tests/qmltests/Dash/CardHelpers.js+5−3
@@ -16,7 +16,7 @@
16
16
17.pragma library
17.pragma library
18
18
19−var components = ["title", "art", "subtitle", "mascot", "emblem", "old-price", "price", "alt-price", "rating", "alt-rating", "summary"]
19+var components = ["title", "art", "subtitle", "mascot", "emblem", "old-price", "price", "alt-price", "rating", "alt-rating", "summary", "attributes"]
20
20
21var defaultLayout = ' \
21var defaultLayout = ' \
22{ \
22{ \
@@ -48,7 +48,8 @@ var defaultLayout = ' \
48 "empty": "image://theme/rating-star-empty" \
48 "empty": "image://theme/rating-star-empty" \
49 }, \
49 }, \
50 "alt-rating": null, \
50 "alt-rating": null, \
51− "summary": null \
51+ "summary": null, \
52+ "attributes": { "max-count": 2 } \
52 }, \
53 }, \
53 "resources": {} \
54 "resources": {} \
54}'
55}'
@@ -59,7 +60,8 @@ var fullMapping = ' \
59 "art": "art", \
60 "art": "art", \
60 "subtitle": "subtitle", \
61 "subtitle": "subtitle", \
61 "mascot": "mascot", \
62 "mascot": "mascot", \
62− "summary": "summary" \
63+ "summary": "summary", \
64+ "attributes": "attributes" \
63}'
65}'
64
66
65
67
tests/qmltests/Dash/tst_Card.qml+4−2
@@ -33,7 +33,8 @@ Rectangle {
33 "mascot": "../../../tests/qmltests/Dash/artwork/avatar.png",
33 "mascot": "../../../tests/qmltests/Dash/artwork/avatar.png",
34 "title": "foo",
34 "title": "foo",
35 "subtitle": "bar",
35 "subtitle": "bar",
36− "summary": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
36+ "summary": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
37+ "attributes": [{"value":"text1","icon":"image://theme/ok"},{"value":"text2","icon":"image://theme/cancel"}]
37 }'
38 }'
38
39
39 property var cardsModel: [
40 property var cardsModel: [
@@ -110,6 +111,7 @@ Rectangle {
110 anchors { top: parent.top; left: parent.left; margins: units.gu(1) }
111 anchors { top: parent.top; left: parent.left; margins: units.gu(1) }
111
112
112 sourceComponent: cardTool.cardComponent
113 sourceComponent: cardTool.cardComponent
114+ clip: true
113 onLoaded: {
115 onLoaded: {
114 item.template = Qt.binding(function() { return cardTool.template; });
116 item.template = Qt.binding(function() { return cardTool.template; });
115 item.components = Qt.binding(function() { return cardTool.components; });
117 item.components = Qt.binding(function() { return cardTool.components; });
@@ -359,7 +361,7 @@ Rectangle {
359
361
360 waitForRendering(card);
362 waitForRendering(card);
361
363
362− tryCompareFunction(function() { return testCase.summary.y === data.top() }, true);
364+ tryCompareFunction(function() { return art.height > 0 && testCase.summary.y === data.top() }, true);
363 }
365 }
364
366
365 function test_art_visibility() {
367 function test_art_visibility() {
tests/qmltests/Dash/tst_Dash.qml+19
@@ -55,6 +55,7 @@ Item {
55 verify(dashContentList != undefined);
55 verify(dashContentList != undefined);
56 tryCompare(dashContentList, "count", 0);
56 tryCompare(dashContentList, "count", 0);
57 scopes.load();
57 scopes.load();
58+ tryCompare(dashContentList, "currentIndex", 0);
58 }
59 }
59
60
60 function get_scope_data() {
61 function get_scope_data() {
@@ -88,6 +89,24 @@ Item {
88 tryCompare(dashContentList, "currentIndex", data.visualIndex);
89 tryCompare(dashContentList, "currentIndex", data.visualIndex);
89 }
90 }
90
91
92+ function test_setCurrentScope() {
93+ var dashContentList = findChild(dash, "dashContentList");
94+ var startX = dash.width - units.gu(1);
95+ var startY = dash.height / 2;
96+ var stopX = units.gu(1)
97+ var stopY = startY;
98+ var retry = 0;
99+ while (dashContentList.currentIndex != 2 && retry <= 5) {
100+ mouseFlick(dash, startX, startY, stopX, stopY)
101+ waitForRendering(dashContentList)
102+ retry++;
103+ }
104+ compare(dashContentList.currentIndex, 2);
105+ var dashCommunicatorService = findInvisibleChild(dash, "dashCommunicatorService");
106+ dashCommunicatorService.mockSetCurrentScope("clickscope", true, true);
107+ tryCompare(dashContentList, "currentIndex", 1)
108+ }
109+
91 function test_processing_indicator() {
110 function test_processing_indicator() {
92 tryCompare(scopes, "loaded", true);
111 tryCompare(scopes, "loaded", true);
93
112
tests/qmltests/Greeter/tst_Lockscreen.qml+11−5
@@ -18,8 +18,9 @@ import QtQuick 2.0
18import QtTest 1.0
18import QtTest 1.0
19import ".."
19import ".."
20import "../../../qml/Components"
20import "../../../qml/Components"
21−import Ubuntu.Components 0.1
21+import AccountsService 0.1
22import LightDM 0.1 as LightDM
22import LightDM 0.1 as LightDM
23+import Ubuntu.Components 0.1
23import Unity.Test 0.1 as UT
24import Unity.Test 0.1 as UT
24
25
25Rectangle {
26Rectangle {
@@ -53,11 +54,16 @@ Rectangle {
53 }
54 }
54 }
55 }
55
56
57+ function setUser(username) {
58+ AccountsService.user = username
59+ LightDM.Greeter.authenticate(username)
60+ }
61+
56 Connections {
62 Connections {
57 target: LightDM.Greeter
63 target: LightDM.Greeter
58
64
59 onShowPrompt: {
65 onShowPrompt: {
60− if (text === "PIN") {
66+ if (AccountsService.passwordDisplayHint === AccountsService.Numeric) {
61 pinPadCheckBox.checked = false
67 pinPadCheckBox.checked = false
62 } else {
68 } else {
63 pinPadCheckBox.checked = true
69 pinPadCheckBox.checked = true
@@ -139,12 +145,12 @@ Rectangle {
139 Button {
145 Button {
140 text: "start auth (1234)"
146 text: "start auth (1234)"
141 width: parent.width
147 width: parent.width
142− onClicked: LightDM.Greeter.authenticate("has-pin")
148+ onClicked: setUser("has-pin")
143 }
149 }
144 Button {
150 Button {
145 text: "start auth (password)"
151 text: "start auth (password)"
146 width: parent.width
152 width: parent.width
147− onClicked: LightDM.Greeter.authenticate("has-password")
153+ onClicked: setUser("has-password")
148 }
154 }
149
155
150 TextField {
156 TextField {
@@ -255,7 +261,7 @@ Rectangle {
255 enteredLabel.text = ""
261 enteredLabel.text = ""
256 minPinLengthTextField.text = data.minPinLength
262 minPinLengthTextField.text = data.minPinLength
257 maxPinLengthTextField.text = data.maxPinLength
263 maxPinLengthTextField.text = data.maxPinLength
258− LightDM.Greeter.authenticate(data.username)
264+ setUser(data.username)
259 waitForLockscreenReady();
265 waitForLockscreenReady();
260
266
261 var inputField = findChild(lockscreen, "pinentryField")
267 var inputField = findChild(lockscreen, "pinentryField")
tests/qmltests/Stages/tst_PhoneStage.qml+3−2
@@ -231,9 +231,10 @@ Item {
231 }
231 }
232
232
233 function cleanup() {
233 function cleanup() {
234− while (ApplicationManager.count > 0) {
234+ while (ApplicationManager.count > 1) {
235 var oldCount = ApplicationManager.count;
235 var oldCount = ApplicationManager.count;
236+ var closingIndex = ApplicationManager.focusedApplicationId == "unity8-dash" ? 1 : 0
236− ApplicationManager.stopApplication(ApplicationManager.get(0).appId)
237+ ApplicationManager.stopApplication(ApplicationManager.get(closingIndex).appId)
237 tryCompare(ApplicationManager, "count", oldCount - 1)
238 tryCompare(ApplicationManager, "count", oldCount - 1)
238 }
239 }
239 }
240 }
tests/qmltests/tst_Shell.qml+63−280
@@ -56,49 +56,20 @@ Item {
56 signalName: "sessionStarted"
56 signalName: "sessionStarted"
57 }
57 }
58
58
59+ SignalSpy {
60+ id: dashCommunicatorSpy
61+ signalName: "setCurrentScopeCalled"
62+ }
63+
59 UT.UnityTestCase {
64 UT.UnityTestCase {
60 name: "Shell"
65 name: "Shell"
61 when: windowShown
66 when: windowShown
62
67
63 function initTestCase() {
68 function initTestCase() {
64− var ok = false;
65− var attempts = 0;
66− var maxAttempts = 1000;
67−
68− // Qt loads a qml scene asynchronously. So early on, some findChild() calls made in
69− // tests may fail because the desired child item wasn't loaded yet.
70− // Thus here we try to ensure the scene has been fully loaded before proceeding with the tests.
71− // As I couldn't find an API in QQuickView & friends to tell me that the scene is 100% loaded
72− // (all items instantiated, etc), I resort to checking the existence of some key items until
73− // repeatedly until they're all there.
74− do {
75− var dashContentList = findChild(shell, "dashContentList");
76− waitForRendering(dashContentList);
77− var homeLoader = findChild(dashContentList, "clickscope loader");
78− ok = homeLoader !== null
79− && homeLoader.item !== undefined;
80−
81− var greeter = findChild(shell, "greeter");
82− ok &= greeter !== null;
83−
84− var launcherPanel = findChild(shell, "launcherPanel");
85− ok &= launcherPanel !== null;
86−
87− attempts++;
88− if (!ok) {
89− console.log("Attempt " + attempts + " failed. Waiting a bit before trying again.");
90− // wait a bit before retrying
91− wait(100);
92− } else {
93− console.log("All seem fine after " + attempts + " attempts.");
94− }
95− } while (!ok && attempts <= maxAttempts);
96−
97− verify(ok);
98−
99 swipeAwayGreeter();
69 swipeAwayGreeter();
100
70
101 sessionSpy.target = findChild(shell, "greeter")
71 sessionSpy.target = findChild(shell, "greeter")
72+ dashCommunicatorSpy.target = findInvisibleChild(shell, "dashCommunicator");
102 }
73 }
103
74
104 function cleanup() {
75 function cleanup() {
@@ -111,56 +82,16 @@ Item {
111 // kill all (fake) running apps
82 // kill all (fake) running apps
112 killApps(ApplicationManager);
83 killApps(ApplicationManager);
113
84
114− var dashContent = findChild(shell, "dashContent");
85+ waitForUIToSettle();
115− dashContent.closePreview();
116−
117− var dashHome = findChild(shell, "clickscope loader");
118− swipeUntilScopeViewIsReached(dashHome);
119−
120 hideIndicators();
86 hideIndicators();
121 }
87 }
122
88
123− function killApps(apps) {
89+ function killApps() {
124− if (!apps) return;
125− while (apps.count > 0) {
90+ while (ApplicationManager.count > 1) {
91+ var appIndex = ApplicationManager.get(0).appId == "unity8-dash" ? 1 : 0
126− ApplicationManager.stopApplication(apps.get(0).appId);
92+ ApplicationManager.stopApplication(ApplicationManager.get(appIndex).appId);
127 }
93 }
128− compare(ApplicationManager.count, 0)
94+ compare(ApplicationManager.count, 1)
129− }
130−
131− /*
132− Test the effect of a right-edge drag on the dash in 3 situations:
133− 1 - when no application has been launched yet
134− 2 - when there's a minimized application
135− 3 - after the last running application has been closed/stopped
136−
137− The behavior of Dash on 3 should be the same as on 1.
138− */
139− function test_rightEdgeDrag() {
140− checkRightEdgeDragWithNoRunningApps();
141−
142− dragLauncherIntoView();
143−
144− // Launch an app from the launcher
145− tapOnAppIconInLauncher();
146− waitUntilApplicationWindowIsFullyVisible();
147−
148− // Minimize the application we just launched
149− swipeFromLeftEdge(units.gu(27));
150−
151− waitForUIToSettle();
152−
153− checkRightEdgeDragWithMinimizedApp();
154−
155− // Minimize that application once again
156− swipeFromLeftEdge(units.gu(27));
157−
158− // Right edge behavior should now be the same as before that app,
159− // was launched. Manually cleanup beforehand to get to initial
160− // state.
161− cleanup();
162− waitForUIToSettle();
163− checkRightEdgeDragWithNoRunningApps();
164 }
95 }
165
96
166 function test_leftEdgeDrag_data() {
97 function test_leftEdgeDrag_data() {
@@ -183,7 +114,7 @@ Item {
183
114
184 swipeFromLeftEdge(data.swipeLength);
115 swipeFromLeftEdge(data.swipeLength);
185 if (data.appHides)
116 if (data.appHides)
186− waitUntilApplicationWindowIsFullyHidden();
117+ waitUntilDashIsFocused();
187 else
118 else
188 waitUntilApplicationWindowIsFullyVisible();
119 waitUntilApplicationWindowIsFullyVisible();
189 }
120 }
@@ -239,100 +170,18 @@ Item {
239 waitForRendering(greeter);
170 waitForRendering(greeter);
240 }
171 }
241
172
242− /*
243− Perform a right-edge drag when the Dash is being show and there are
244− no running/minimized apps to be restored.
245−
246− The expected behavior is that an animation should be played to hint the
247− user that his right-edge drag gesture has been successfully recognized
248− but there is no application to be brought to foreground.
249− */
250− function checkRightEdgeDragWithNoRunningApps() {
251− var touchX = shell.width - (shell.edgeSize / 2);
252− var touchY = shell.height / 2;
253−
254− var dash = findChild(shell, "dash");
255− // check that dash has normal scale and opacity
256− tryCompare(dash, "contentScale", 1.0);
257− tryCompare(dash, "opacity", 1.0);
258−
259− touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY,
260− true /* beginTouch */, false /* endTouch */, units.gu(10), 50);
261−
262− // check that Dash has been scaled down and had its opacity reduced
263− tryCompareFunction(function() { return dash.contentScale <= 0.9; }, true);
264− tryCompareFunction(function() { return dash.opacity <= 0.5; }, true);
265−
266− touchRelease(shell, shell.width * 0.1, touchY);
267−
268− // and now everything should have gone back to normal
269− tryCompare(dash, "contentScale", 1.0);
270− tryCompare(dash, "opacity", 1.0);
271− }
272−
273− /*
274− Perform a right-edge drag when the Dash is being show and there is
275− a running/minimized app to be restored.
276−
277− The expected behavior is that the dash should fade away and ultimately be
278− made invisible once the gesture is finished as the restored app will now
279− be on foreground.
280− */
281− function checkRightEdgeDragWithMinimizedApp() {
282− var touchX = shell.width - (shell.edgeSize / 2);
283− var touchY = shell.height / 2;
284−
285− var dash = findChild(shell, "dash");
286− // check that dash has normal scale and opacity
287− tryCompare(dash, "contentScale", 1.0);
288− tryCompare(dash, "opacity", 1.0);
289−
290− touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY,
291− true /* beginTouch */, false /* endTouch */, units.gu(10), 50);
292−
293− // check that Dash has been scaled down and had its opacity reduced
294− tryCompareFunction(function() { return dash.contentScale <= 0.9; }, true);
295− tryCompareFunction(function() { return dash.opacity <= 0.5; }, true);
296−
297− touchRelease(shell, shell.width * 0.1, touchY);
298−
299− // dash should have gone away, now that the app is on foreground
300− tryCompare(dash, "visible", false);
301− }
302−
303 /*
173 /*
304 Regression test for bug https://bugs.launchpad.net/touch-preview-images/+bug/1193419
174 Regression test for bug https://bugs.launchpad.net/touch-preview-images/+bug/1193419
305
175
306− When the user minimizes an application (left-edge swipe) he should always end up in the "Running Apps"
176+ When the user minimizes an application (left-edge swipe) he should always end up in the
307− category of the "Applications" scope view.
177+ "Applications" scope view.
308
178
309 Steps:
179 Steps:
310− - go to apps lens
311− - scroll to the bottom
312− - reveal launcher and launch an app
180+ - reveal launcher and launch an app that covers the dash
313 - perform long left edge swipe to go minimize the app and go back to the dash.
181 - perform long left edge swipe to go minimize the app and go back to the dash.
314−
182+ - verify the setCurrentScope() D-Bus call to the dash has been called for the correct scope id.
315− Expected Results
316− - apps lens shown and Running Apps visible on screen
317 */
183 */
318− function test_minimizingAppTakesToRunningApps() {
184+ function test_minimizingAppTakesToDashApps() {
319− var dashApps = findChild(shell, "clickscope");
320− swipeUntilScopeViewIsReached(dashApps);
321−
322− // swipe finger up until the running/recent apps section (which we assume
323− // it's the first one) is as far from view as possible.
324− // We also assume that DashApps is tall enough that it's scrollable
325− var appsCategoryListView = findChild(dashApps, "categoryListView");
326− while (!appsCategoryListView.atYEnd) {
327− swipeUpFromCenter();
328− tryCompare(appsCategoryListView, "moving", false);
329− }
330−
331− // Switch away from the Applications scope.
332− swipeRightFromCenter();
333− waitUntilItemStopsMoving(dashApps);
334− verify(!itemIsOnScreen(dashApps));
335−
336 dragLauncherIntoView();
185 dragLauncherIntoView();
337
186
338 // Launch an app from the launcher
187 // Launch an app from the launcher
@@ -340,23 +189,16 @@ Item {
340
189
341 waitUntilApplicationWindowIsFullyVisible();
190 waitUntilApplicationWindowIsFullyVisible();
342
191
343− // Dragging launcher into view with a little bit of gap (units.gu(1)) should switch to Apps scope
192+ verify(ApplicationManager.focusedApplicationId !== "unity8-dash")
344− dragLauncherIntoView();
345− verify(itemIsOnScreen(dashApps));
346
193
194+ dashCommunicatorSpy.clear();
347 // Minimize the application we just launched
195 // Minimize the application we just launched
348 swipeFromLeftEdge(units.gu(27));
196 swipeFromLeftEdge(units.gu(27));
349
197
350− // Wait for the whole UI to settle down
198+ tryCompare(ApplicationManager, "focusedApplicationId", "unity8-dash");
351− waitUntilApplicationWindowIsFullyHidden();
352− waitUntilItemStopsMoving(dashApps);
353− tryCompare(appsCategoryListView, "moving", false);
354
199
355− verify(itemIsOnScreen(dashApps));
200+ compare(dashCommunicatorSpy.count, 1);
356−
201+ compare(dashCommunicatorSpy.signalArguments[0][0], "clickscope");
357− var runningApplicationsGrid = findChild(appsCategoryListView, "running.apps.category");
358− verify(runningApplicationsGrid);
359− verify(itemIsOnScreen(runningApplicationsGrid));
360 }
202 }
361
203
362 function test_showInputMethod() {
204 function test_showInputMethod() {
@@ -413,9 +255,14 @@ Item {
413
255
414 // Wait for the whole UI to settle down
256 // Wait for the whole UI to settle down
415 function waitForUIToSettle() {
257 function waitForUIToSettle() {
416− waitUntilApplicationWindowIsFullyHidden();
417− var dashContentList = findChild(shell, "dashContentList");
258+ var launcher = findChild(shell, "launcherPanel")
418− tryCompare(dashContentList, "moving", false);
259+ tryCompareFunction(function() {return launcher.x === 0 || launcher.x === -launcher.width;}, true);
260+ if (launcher.x === 0) {
261+ mouseClick(shell, shell.width / 2, shell.height / 2)
262+ }
263+ tryCompare(launcher, "x", -launcher.width)
264+
265+ waitForRendering(shell)
419 }
266 }
420
267
421 function dragToCloseIndicatorsPanel() {
268 function dragToCloseIndicatorsPanel() {
@@ -472,24 +319,13 @@ Item {
472 }
319 }
473
320
474 function waitUntilApplicationWindowIsFullyVisible() {
321 function waitUntilApplicationWindowIsFullyVisible() {
475− var underlay = findChild(shell, "underlay");
322+ var appDelegate = findChild(shell, "appDelegate0")
476− tryCompare(underlay, "visible", false);
323+ var surfaceContainer = findChild(appDelegate, "surfaceContainer");
477− }
324+ tryCompareFunction(function() { return surfaceContainer.surface !== null; }, true);
478−
479− function waitUntilApplicationWindowIsFullyHidden() {
480− var stages = findChild(shell, "stages");
481− tryCompare(stages, "fullyHidden", true);
482 }
325 }
483
326
484− function swipeUntilScopeViewIsReached(scopeView) {
327+ function waitUntilDashIsFocused() {
485− while (!itemIsOnScreen(scopeView)) {
328+ tryCompare(ApplicationManager, "focusedApplicationId", "unity8-dash");
486− if (itemIsToLeftOfScreen(scopeView)) {
487− swipeRightFromCenter();
488− } else {
489− swipeLeftFromCenter();
490− }
491− waitUntilItemStopsMoving(scopeView);
492− }
493 }
329 }
494
330
495 function swipeFromLeftEdge(swipeLength) {
331 function swipeFromLeftEdge(swipeLength) {
@@ -498,24 +334,6 @@ Item {
498 touchFlick(shell, touchStartX, touchStartY, swipeLength, touchStartY);
334 touchFlick(shell, touchStartX, touchStartY, swipeLength, touchStartY);
499 }
335 }
500
336
501− function swipeLeftFromCenter() {
502− var touchStartX = shell.width * 3 / 4;
503− var touchStartY = shell.height / 2;
504− touchFlick(shell, touchStartX, touchStartY, 0, touchStartY);
505− }
506−
507− function swipeRightFromCenter() {
508− var touchStartX = shell.width * 3 / 4;
509− var touchStartY = shell.height / 2;
510− touchFlick(shell, touchStartX, touchStartY, shell.width, touchStartY);
511− }
512−
513− function swipeUpFromCenter() {
514− var touchStartX = shell.width / 2;
515− var touchStartY = shell.height / 2;
516− touchFlick(shell, touchStartX, touchStartY, touchStartX, 0);
517− }
518−
519 function itemIsOnScreen(item) {
337 function itemIsOnScreen(item) {
520 var itemRectInShell = item.mapToItem(shell, 0, 0, item.width, item.height);
338 var itemRectInShell = item.mapToItem(shell, 0, 0, item.width, item.height);
521
339
@@ -525,64 +343,6 @@ Item {
525 && itemRectInShell.y + itemRectInShell.height <= shell.height;
343 && itemRectInShell.y + itemRectInShell.height <= shell.height;
526 }
344 }
527
345
528− function itemIsToLeftOfScreen(item) {
529− var itemRectInShell = item.mapToItem(shell, 0, 0, item.width, item.height);
530− return itemRectInShell.x < 0;
531− }
532−
533− function waitUntilItemStopsMoving(item) {
534− var itemRectInShell = item.mapToItem(shell, 0, 0, item.width, item.height);
535− var previousX = itemRectInShell.x;
536− var previousY = itemRectInShell.y;
537− var isStill = false;
538−
539− do {
540− wait(100);
541− itemRectInShell = item.mapToItem(shell, 0, 0, item.width, item.height);
542− if (itemRectInShell.x == previousX && itemRectInShell.y == previousY) {
543− isStill = true;
544− } else {
545− previousX = itemRectInShell.x;
546− previousY = itemRectInShell.y;
547− }
548− } while (!isStill);
549− }
550−
551− function test_DashShown_data() {
552− return [
553− {tag: "in focus", greeter: false, app: false, launcher: false, indicators: false, expectedShown: true},
554− {tag: "under greeter", greeter: true, app: false, launcher: false, indicators: false, expectedShown: false},
555− {tag: "under app", greeter: false, app: true, launcher: false, indicators: false, expectedShown: false},
556− {tag: "under launcher", greeter: false, app: false, launcher: true, indicators: false, expectedShown: true},
557− {tag: "under indicators", greeter: false, app: false, launcher: false, indicators: true, expectedShown: false},
558− ]
559− }
560−
561− function test_DashShown(data) {
562− if (data.greeter) {
563− // Swipe the greeter in
564− var greeter = findChild(shell, "greeter");
565− Powerd.displayPowerStateChange(Powerd.Off, 0);
566− tryCompare(greeter, "showProgress", 1);
567− }
568−
569− if (data.app) {
570− dragLauncherIntoView();
571− tapOnAppIconInLauncher();
572− }
573−
574− if (data.launcher) {
575− dragLauncherIntoView();
576− }
577−
578− if (data.indicators) {
579− showIndicators();
580− }
581−
582− var dash = findChild(shell, "dash");
583− tryCompare(dash, "shown", data.expectedShown);
584− }
585−
586 function test_focusRequestedHidesGreeter() {
346 function test_focusRequestedHidesGreeter() {
587 var greeter = findChild(shell, "greeter");
347 var greeter = findChild(shell, "greeter");
588
348
@@ -591,10 +351,9 @@ Item {
591 tryCompareFunction(function() { return app.surface != null }, true);
351 tryCompareFunction(function() { return app.surface != null }, true);
592
352
593 // Minimize the application we just launched
353 // Minimize the application we just launched
594− swipeFromLeftEdge(units.gu(27));
354+ swipeFromLeftEdge(units.gu(26) + 1);
595
355
596− // Wait for the whole UI to settle down
356+ waitUntilDashIsFocused();
597− waitUntilApplicationWindowIsFullyHidden();
598
357
599 greeter.show();
358 greeter.show();
600 tryCompare(greeter, "showProgress", 1);
359 tryCompare(greeter, "showProgress", 1);
@@ -610,8 +369,9 @@ Item {
610
369
611 showIndicators();
370 showIndicators();
612
371
372+ var oldCount = ApplicationManager.count;
613 ApplicationManager.startApplication("camera-app");
373 ApplicationManager.startApplication("camera-app");
614− tryCompare(ApplicationManager, "count", 1);
374+ tryCompare(ApplicationManager, "count", oldCount + 1);
615
375
616 tryCompare(indicators, "fullyClosed", true);
376 tryCompare(indicators, "fullyClosed", true);
617 }
377 }
@@ -620,6 +380,7 @@ Item {
620 function test_showGreeterDBusCall() {
380 function test_showGreeterDBusCall() {
621 var greeter = findChild(shell, "greeter")
381 var greeter = findChild(shell, "greeter")
622 tryCompare(greeter, "showProgress", 0)
382 tryCompare(greeter, "showProgress", 0)
383+ waitForRendering(greeter);
623 LightDM.Greeter.showGreeter()
384 LightDM.Greeter.showGreeter()
624 tryCompare(greeter, "showProgress", 1)
385 tryCompare(greeter, "showProgress", 1)
625 }
386 }
@@ -628,6 +389,7 @@ Item {
628 sessionSpy.clear()
389 sessionSpy.clear()
629
390
630 var greeter = findChild(shell, "greeter")
391 var greeter = findChild(shell, "greeter")
392+ waitForRendering(greeter)
631 greeter.show()
393 greeter.show()
632 tryCompare(greeter, "showProgress", 1)
394 tryCompare(greeter, "showProgress", 1)
633
395
@@ -648,5 +410,26 @@ Item {
648 ApplicationManager.requestFocusApplication("gallery-app");
410 ApplicationManager.requestFocusApplication("gallery-app");
649 tryCompare(panel, "fullscreenMode", false);
411 tryCompare(panel, "fullscreenMode", false);
650 }
412 }
413+
414+ function test_leftEdgeDragFullscreen() {
415+ var panel = findChild(shell, "panel");
416+ tryCompare(panel, "fullscreenMode", false)
417+
418+ ApplicationManager.startApplication("camera-app");
419+ tryCompare(panel, "fullscreenMode", true)
420+
421+ var touchStartX = 2;
422+ var touchStartY = shell.height / 2;
423+
424+ touchFlick(shell, touchStartX, touchStartY, units.gu(2), touchStartY, true, false);
425+
426+ compare(panel.fullscreenMode, true);
427+
428+ touchFlick(shell, units.gu(2), touchStartY, units.gu(10), touchStartY, false, false);
429+
430+ tryCompare(panel, "fullscreenMode", false);
431+
432+ touchRelease(shell);
433+ }
651 }
434 }
652}
435}
tests/qmltests/tst_ShellWithPin.qml+4−37
@@ -58,40 +58,6 @@ Item {
58 when: windowShown
58 when: windowShown
59
59
60 function initTestCase() {
60 function initTestCase() {
61− var ok = false;
62− var attempts = 0;
63− var maxAttempts = 1000;
64−
65− // Qt loads a qml scene asynchronously. So early on, some findChild() calls made in
66− // tests may fail because the desired child item wasn't loaded yet.
67− // Thus here we try to ensure the scene has been fully loaded before proceeding with the tests.
68− // As I couldn't find an API in QQuickView & friends to tell me that the scene is 100% loaded
69− // (all items instantiated, etc), I resort to checking the existence of some key items until
70− // repeatedly until they're all there.
71− do {
72− var dashContentList = findChild(shell, "dashContentList");
73− waitForRendering(dashContentList);
74− var homeLoader = findChild(dashContentList, "clickscope loader");
75− ok = homeLoader !== null
76− && homeLoader.item !== undefined;
77−
78− var greeter = findChild(shell, "greeter");
79− ok &= greeter !== null;
80−
81− var launcherPanel = findChild(shell, "launcherPanel");
82− ok &= launcherPanel !== null;
83−
84− attempts++;
85− if (!ok) {
86− console.log("Attempt " + attempts + " failed. Waiting a bit before trying again.");
87− // wait a bit before retrying
88− wait(100);
89− } else {
90− console.log("All seem fine after " + attempts + " attempts.");
91− }
92− } while (!ok && attempts <= maxAttempts);
93−
94− verify(ok);
95
61
96 sessionSpy.target = findChild(shell, "greeter")
62 sessionSpy.target = findChild(shell, "greeter")
97 }
63 }
@@ -110,10 +76,11 @@ Item {
110 }
76 }
111
77
112 function killApps() {
78 function killApps() {
113− while (ApplicationManager.count > 0) {
79+ while (ApplicationManager.count > 1) {
80+ var appIndex = ApplicationManager.get(0).appId == "unity8-dash" ? 1 : 0
114− ApplicationManager.stopApplication(ApplicationManager.get(0).appId)
81+ ApplicationManager.stopApplication(ApplicationManager.get(appIndex).appId);
115 }
82 }
116− compare(ApplicationManager.count, 0)
83+ compare(ApplicationManager.count, 1)
117 }
84 }
118
85
119 function swipeAwayGreeter() {
86 function swipeAwayGreeter() {