Merge lp:~lukas-kde/unity8/dashboard into lp:unity8

Proposed by Lukáš Tinkl
Status: Work in progress
Proposed branch: lp:~lukas-kde/unity8/dashboard
Merge into: lp:unity8
Diff against target: 2003 lines (+1295/-169)
25 files modified
plugins/Dash/abstractdashview.cpp (+1/-1)
plugins/Dash/abstractdashview.h (+2/-2)
plugins/Dash/verticaljournal.cpp (+22/-21)
plugins/Utils/globalfunctions.cpp (+2/-2)
plugins/Utils/globalfunctions.h (+1/-1)
qml/Components/ResponsiveVerticalJournal.qml (+3/-1)
qml/Dash/Autoscroller.qml (+92/-0)
qml/Dash/Dash.qml (+1/-1)
qml/Dash/Dashboard/Dashboard.qml (+54/-0)
qml/Dash/Dashboard/DashboardDelegate.qml (+116/-0)
qml/Dash/Dashboard/DashboardView.qml (+170/-0)
qml/Dash/Dashboard/DashboardViewContents.qml (+120/-0)
qml/Dash/Dashboard/DashboardViewLocation.qml (+194/-0)
qml/Dash/ScopesList.qml (+29/-112)
qml/Greeter/ShimGreeter.qml (+4/-3)
qml/Shell.qml (+7/-0)
qml/Stage/DecoratedWindow.qml (+0/-1)
qml/Stage/Spread/Spread.qml (+6/-0)
qml/Stage/Stage.qml (+23/-11)
tests/mocks/Unity/Application/ApplicationManager.cpp (+6/-6)
tests/mocks/Unity/Application/ApplicationManager.h (+5/-5)
tests/qmltests/CMakeLists.txt (+1/-0)
tests/qmltests/Dash/tst_Dashboard.qml (+433/-0)
tests/qmltests/Stage/ApplicationCheckBox.qml (+0/-2)
tests/utils/modules/Unity/Test/MouseTouchEmulationCheckbox.qml (+3/-0)
To merge this branch: bzr merge lp:~lukas-kde/unity8/dashboard
Reviewer Review Type Date Requested Status
Unity Team Pending
Review via email: mp+315162@code.launchpad.net

Description of the change

WIP: Unity 8 Dashboard prototype & playground

To post a comment you must log in.
lp:~lukas-kde/unity8/dashboard updated
2776. By Lukáš Tinkl

implement a stub dashboard location edit view

turn the button bar into a state machine

2777. By Lukáš Tinkl

a more complete location/locale editor

2778. By Lukáš Tinkl

add location support, more sensible Back btn behavior

2779. By Lukáš Tinkl

updates to the location/locale page

2780. By Lukáš Tinkl

move the height randomization into the model so that it doesn't shuffle too much

2781. By Lukáš Tinkl

merge trunk, fix conflicts

2782. By Lukáš Tinkl

put u8-dash mostly back

2783. By Lukáš Tinkl

make dash non-special

2784. By Lukáš Tinkl

WIP undoable closing

2785. By Lukáš Tinkl

merge trunk

Unmerged revisions

2785. By Lukáš Tinkl

merge trunk

2784. By Lukáš Tinkl

WIP undoable closing

2783. By Lukáš Tinkl

make dash non-special

2782. By Lukáš Tinkl

put u8-dash mostly back

2781. By Lukáš Tinkl

merge trunk, fix conflicts

2780. By Lukáš Tinkl

move the height randomization into the model so that it doesn't shuffle too much

2779. By Lukáš Tinkl

updates to the location/locale page

2778. By Lukáš Tinkl

add location support, more sensible Back btn behavior

2777. By Lukáš Tinkl

a more complete location/locale editor

2776. By Lukáš Tinkl

implement a stub dashboard location edit view

turn the button bar into a state machine

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'plugins/Dash/abstractdashview.cpp'
2--- plugins/Dash/abstractdashview.cpp 2016-12-23 11:04:53 +0000
3+++ plugins/Dash/abstractdashview.cpp 2017-02-10 14:04:06 +0000
4@@ -301,7 +301,7 @@
5
6 // The current AbstractDashViews do not support insertions that are not at the end
7 // so reset if that happens
8- Q_FOREACH(const QQmlChangeSet::Change insert, changeSet.inserts()) {
9+ Q_FOREACH(const QQmlChangeSet::Change &insert, changeSet.inserts()) {
10 if (insert.index < m_delegateModel->count() - 1) {
11 cleanupExistingItems();
12 break;
13
14=== modified file 'plugins/Dash/abstractdashview.h'
15--- plugins/Dash/abstractdashview.h 2016-11-28 09:57:06 +0000
16+++ plugins/Dash/abstractdashview.h 2017-02-10 14:04:06 +0000
17@@ -79,8 +79,8 @@
18 void displayMarginBeginningChanged();
19 void displayMarginEndChanged();
20
21-protected Q_SLOTS:
22- void relayout();
23+public Q_SLOTS:
24+ Q_INVOKABLE void relayout();
25
26 protected:
27 void updatePolish() override;
28
29=== modified file 'plugins/Dash/verticaljournal.cpp'
30--- plugins/Dash/verticaljournal.cpp 2016-11-28 09:57:06 +0000
31+++ plugins/Dash/verticaljournal.cpp 2017-02-10 14:04:06 +0000
32@@ -34,6 +34,7 @@
33 VerticalJournal::VerticalJournal()
34 : m_columnWidth(0)
35 {
36+ setFlag(ItemAcceptsDrops);
37 }
38
39 qreal VerticalJournal::columnWidth() const
40@@ -49,7 +50,7 @@
41
42 if (isComponentComplete()) {
43 Q_FOREACH(const auto &column, m_columnVisibleItems) {
44- Q_FOREACH(const ViewItem item, column) {
45+ Q_FOREACH(const ViewItem &item, column) {
46 item.m_item->setWidth(columnWidth);
47 }
48 }
49@@ -65,7 +66,7 @@
50
51 Q_FOREACH(const auto &column, m_columnVisibleItems) {
52 if (!column.isEmpty()) {
53- const ViewItem &item = column.last();
54+ const ViewItem &item = column.constLast();
55 *yPos = qMin(*yPos, item.y() + item.height() + rowSpacing());
56 *modelIndex = qMax(*modelIndex, item.m_modelIndex + 1);
57 } else {
58@@ -82,9 +83,9 @@
59
60 // Find the topmost free column
61 for (int i = 0; i < m_columnVisibleItems.count(); ++i) {
62- const auto &column = m_columnVisibleItems[i];
63+ const auto &column = m_columnVisibleItems.at(i);
64 if (!column.isEmpty()) {
65- const ViewItem &item = column.first();
66+ const ViewItem &item = column.constFirst();
67 const auto itemTopPos = item.y() - rowSpacing();
68 if (itemTopPos > *yPos) {
69 *yPos = itemTopPos;
70@@ -113,12 +114,12 @@
71
72 for (int i = 0; i < m_columnVisibleItems.count(); ++i) {
73 QList<ViewItem> &column = m_columnVisibleItems[i];
74- while (!column.isEmpty() && column.first().y() + column.first().height() < bufferFromY) {
75+ while (!column.isEmpty() && column.constFirst().y() + column.constFirst().height() < bufferFromY) {
76 releaseItem(column.takeFirst().m_item);
77 changed = true;
78 }
79
80- while (!column.isEmpty() && column.last().y() > bufferToY) {
81+ while (!column.isEmpty() && column.constLast().y() > bufferToY) {
82 releaseItem(column.takeLast().m_item);
83 changed = true;
84 }
85@@ -135,20 +136,20 @@
86 }
87
88 // Check if we add it to the bottom of existing column items
89- const QList<ViewItem> &firstColumn = m_columnVisibleItems[0];
90- qreal columnToAddY = !firstColumn.isEmpty() ? firstColumn.last().y() + firstColumn.last().height() : -rowSpacing();
91+ const QList<ViewItem> &firstColumn = m_columnVisibleItems.constFirst();
92+ qreal columnToAddY = !firstColumn.isEmpty() ? firstColumn.constLast().y() + firstColumn.constLast().height() : -rowSpacing();
93 int columnToAddTo = 0;
94 for (int i = 1; i < m_columnVisibleItems.count(); ++i) {
95- const QList<ViewItem> &column = m_columnVisibleItems[i];
96- const qreal iY = !column.isEmpty() ? column.last().y() + column.last().height() : -rowSpacing();
97+ const QList<ViewItem> &column = m_columnVisibleItems.at(i);
98+ const qreal iY = !column.isEmpty() ? column.constLast().y() + column.constLast().height() : -rowSpacing();
99 if (iY < columnToAddY) {
100 columnToAddTo = i;
101 columnToAddY = iY;
102 }
103 }
104
105- const QList<ViewItem> &columnToAdd = m_columnVisibleItems[columnToAddTo];
106- if (columnToAdd.isEmpty() || columnToAdd.last().m_modelIndex < modelIndex) {
107+ const QList<ViewItem> &columnToAdd = m_columnVisibleItems.at(columnToAddTo);
108+ if (columnToAdd.isEmpty() || columnToAdd.constLast().m_modelIndex < modelIndex) {
109 item->setX(columnToAddTo * (m_columnWidth + columnSpacing()));
110 item->setY(columnToAddY + rowSpacing());
111
112@@ -157,7 +158,7 @@
113 } else {
114 Q_ASSERT(m_indexColumnMap.contains(modelIndex));
115 columnToAddTo = m_indexColumnMap[modelIndex];
116- columnToAddY = m_columnVisibleItems[columnToAddTo].first().y();
117+ columnToAddY = m_columnVisibleItems[columnToAddTo].constFirst().y();
118
119 item->setX(columnToAddTo * (m_columnWidth + columnSpacing()));
120 item->setY(columnToAddY - rowSpacing() - item->height());
121@@ -171,7 +172,7 @@
122 // Cleanup the existing items
123 for (int i = 0; i < m_columnVisibleItems.count(); ++i) {
124 QList<ViewItem> &column = m_columnVisibleItems[i];
125- Q_FOREACH(const ViewItem item, column)
126+ Q_FOREACH(const ViewItem &item, column)
127 releaseItem(item.m_item);
128 column.clear();
129 }
130@@ -185,7 +186,7 @@
131 qreal bottomMostY = 0;
132 Q_FOREACH(const auto &column, m_columnVisibleItems) {
133 if (!column.isEmpty()) {
134- const ViewItem &item = column.last();
135+ const ViewItem &item = column.constLast();
136 lastModelIndex = qMax(lastModelIndex, item.m_modelIndex);
137 bottomMostY = qMax(bottomMostY, item.y() + item.height());
138 }
139@@ -216,11 +217,11 @@
140 // all since we can't consistently relayout without the first item being there
141
142 if (!allItems.isEmpty()) {
143- if (allItems.first().m_modelIndex == 0) {
144- Q_FOREACH(const ViewItem item, allItems)
145+ if (allItems.constFirst().m_modelIndex == 0) {
146+ Q_FOREACH(const ViewItem &item, allItems)
147 addItemToView(item.m_modelIndex, item.m_item);
148 } else {
149- Q_FOREACH(const ViewItem item, allItems)
150+ Q_FOREACH(const ViewItem &item, allItems)
151 releaseItem(item.m_item);
152 }
153 }
154@@ -229,7 +230,7 @@
155 void VerticalJournal::updateItemCulling(qreal visibleFromY, qreal visibleToY)
156 {
157 Q_FOREACH(const auto &column, m_columnVisibleItems) {
158- Q_FOREACH(const ViewItem item, column) {
159+ Q_FOREACH(const ViewItem &item, column) {
160 const bool cull = item.y() + item.height() <= visibleFromY || item.y() >= visibleToY;
161 QQuickItemPrivate::get(item.m_item)->setCulled(cull);
162 }
163@@ -238,7 +239,7 @@
164
165 void VerticalJournal::processModelRemoves(const QVector<QQmlChangeSet::Change> &removes)
166 {
167- Q_FOREACH(const QQmlChangeSet::Change remove, removes) {
168+ Q_FOREACH(const QQmlChangeSet::Change &remove, removes) {
169 for (int i = remove.count - 1; i >= 0; --i) {
170 const int indexToRemove = remove.index + i;
171 // Since we only support removing from the end, indexToRemove
172@@ -250,7 +251,7 @@
173 for (int i = 0; !found && i < m_columnVisibleItems.count(); ++i) {
174 QList<ViewItem> &column = m_columnVisibleItems[i];
175 if (!column.isEmpty()) {
176- const int lastColumnIndex = column.last().m_modelIndex;
177+ const int lastColumnIndex = column.constLast().m_modelIndex;
178 if (lastColumnIndex == indexToRemove) {
179 releaseItem(column.takeLast().m_item);
180 found = true;
181
182=== modified file 'plugins/Utils/globalfunctions.cpp'
183--- plugins/Utils/globalfunctions.cpp 2016-12-07 13:47:15 +0000
184+++ plugins/Utils/globalfunctions.cpp 2017-02-10 14:04:06 +0000
185@@ -38,13 +38,13 @@
186 && child->width() >= point.x()
187 && point.y() >= 0
188 && child->height() >= point.y()) {
189- if (!matcher.isCallable()) return child;
190+ if (matcher.isUndefined() || !matcher.isCallable()) return child;
191
192 QQmlEngine* engine = qmlEngine(child);
193 if (!engine) return child;
194
195 QJSValue newObj = engine->newQObject(child);
196- if (matcher.call(QJSValueList() << newObj).toBool()) {
197+ if (matcher.call({newObj}).toBool()) {
198 return child;
199 }
200 }
201
202=== modified file 'plugins/Utils/globalfunctions.h'
203--- plugins/Utils/globalfunctions.h 2016-10-14 11:04:20 +0000
204+++ plugins/Utils/globalfunctions.h 2017-02-10 14:04:06 +0000
205@@ -36,7 +36,7 @@
206 static Q_INVOKABLE QQuickItem* itemAt(QQuickItem* parent,
207 int x,
208 int y,
209- QJSValue matcher);
210+ QJSValue matcher = QJSValue(QJSValue::UndefinedValue));
211
212 static Q_INVOKABLE bool itemUnderMouse(QQuickItem* item);
213 };
214
215=== modified file 'qml/Components/ResponsiveVerticalJournal.qml'
216--- qml/Components/ResponsiveVerticalJournal.qml 2015-07-15 15:07:19 +0000
217+++ qml/Components/ResponsiveVerticalJournal.qml 2017-02-10 14:04:06 +0000
218@@ -51,6 +51,8 @@
219 property real displayMarginBeginning: 0
220 property real displayMarginEnd: 0
221
222+ readonly property alias view: verticalJournalView
223+
224 implicitHeight: verticalJournalView.implicitHeight + rowSpacing
225 clip: height < implicitHeight
226
227@@ -75,7 +77,7 @@
228 columnSpacing: {
229 // parent.width = columns * columnWidth + (columns-1) * spacing + spacing(margins)
230 var expectedColumns = Math.max(1, Math.floor(parent.width / (columnWidth + minimumColumnSpacing)));
231- Math.floor((parent.width - expectedColumns * columnWidth) / expectedColumns);
232+ return Math.floor((parent.width - expectedColumns * columnWidth) / expectedColumns);
233 }
234 }
235 }
236
237=== added file 'qml/Dash/Autoscroller.qml'
238--- qml/Dash/Autoscroller.qml 1970-01-01 00:00:00 +0000
239+++ qml/Dash/Autoscroller.qml 2017-02-10 14:04:06 +0000
240@@ -0,0 +1,92 @@
241+/*
242+ * Copyright (C) 2017 Canonical, Ltd.
243+ *
244+ * This program is free software; you can redistribute it and/or modify
245+ * it under the terms of the GNU General Public License as published by
246+ * the Free Software Foundation; version 3.
247+ *
248+ * This program is distributed in the hope that it will be useful,
249+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
250+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
251+ * GNU General Public License for more details.
252+ *
253+ * You should have received a copy of the GNU General Public License
254+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
255+ */
256+
257+import QtQuick 2.4
258+
259+Item {
260+ id: autoscroller
261+
262+ property bool dragging: false
263+ property var dragItem: new Object();
264+ property var flickable
265+
266+ function autoscroll(dragging, dragItem) {
267+ if (dragging) {
268+ autoscroller.dragItem = dragItem;
269+ autoscroller.dragging = true;
270+ } else {
271+ autoscroller.dragItem = null;
272+ autoscroller.dragging = false;
273+ }
274+ }
275+
276+ readonly property real bottomBoundary: {
277+ var contentHeight = flickable.contentHeight
278+ var contentY = flickable.contentY
279+ var dragItemHeight = dragItem ? autoscroller.dragItem.height : 0
280+ var heightRatio = flickable.visibleArea.heightRatio
281+
282+ if (!dragItem) {
283+ return true;
284+ } else {
285+ return (heightRatio * contentHeight) -
286+ (1.5 * dragItemHeight) + contentY
287+ }
288+ }
289+
290+ readonly property int delayMs: 32
291+ readonly property real topBoundary: dragItem ? flickable.contentY + (.5 * dragItem.height) : 0
292+
293+ visible: false
294+ readonly property real maxStep: units.dp(10)
295+ function stepSize(scrollingUp) {
296+ var delta, step;
297+ if (scrollingUp) {
298+ delta = dragItem.y - topBoundary;
299+ delta /= (1.5 * dragItem.height);
300+ } else {
301+ delta = dragItem.y - bottomBoundary;
302+ delta /= (1.5 * dragItem.height);
303+ }
304+
305+ step = Math.abs(delta) * autoscroller.maxStep
306+ return Math.ceil(step);
307+ }
308+
309+ Timer {
310+ interval: autoscroller.delayMs
311+ running: autoscroller.dragItem ? (autoscroller.dragging &&
312+ autoscroller.dragItem.y < autoscroller.topBoundary &&
313+ !flickable.atYBeginning) : false
314+ repeat: true
315+ onTriggered: {
316+ flickable.contentY -= autoscroller.stepSize(true);
317+ autoscroller.dragItem.y -= autoscroller.stepSize(true);
318+ }
319+ }
320+
321+ Timer {
322+ interval: autoscroller.delayMs
323+ running: autoscroller.dragItem ? (autoscroller.dragging &&
324+ autoscroller.dragItem.y >= autoscroller.bottomBoundary &&
325+ !autoscroller.flickable.atYEnd) : false
326+ repeat: true
327+ onTriggered: {
328+ flickable.contentY += autoscroller.stepSize(false);
329+ autoscroller.dragItem.y += autoscroller.stepSize(false);
330+ }
331+ }
332+}
333
334=== modified file 'qml/Dash/Dash.qml'
335--- qml/Dash/Dash.qml 2016-09-02 15:38:15 +0000
336+++ qml/Dash/Dash.qml 2017-02-10 14:04:06 +0000
337@@ -262,7 +262,7 @@
338 bottomMargin: Qt.inputMethod.keyboardRectangle.height
339 }
340 height: units.dp(3)
341- color: scopeStyle.backgroundLuminance > 0.7 ? "#50000000" : "#50ffffff"
342+ color: scopeItem.scopeStyle.backgroundLuminance > 0.7 ? "#50000000" : "#50ffffff"
343 opacity: 0
344 visible: opacity > 0
345
346
347=== added directory 'qml/Dash/Dashboard'
348=== added file 'qml/Dash/Dashboard/Dashboard.qml'
349--- qml/Dash/Dashboard/Dashboard.qml 1970-01-01 00:00:00 +0000
350+++ qml/Dash/Dashboard/Dashboard.qml 2017-02-10 14:04:06 +0000
351@@ -0,0 +1,54 @@
352+/*
353+ * Copyright (C) 2017 Canonical, Ltd.
354+ *
355+ * This program is free software; you can redistribute it and/or modify
356+ * it under the terms of the GNU General Public License as published by
357+ * the Free Software Foundation; version 3.
358+ *
359+ * This program is distributed in the hope that it will be useful,
360+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
361+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
362+ * GNU General Public License for more details.
363+ *
364+ * You should have received a copy of the GNU General Public License
365+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
366+ */
367+
368+import QtQuick 2.4
369+import Ubuntu.Components 1.3
370+import "../../Components"
371+
372+Showable {
373+ id: root
374+
375+ // This is a bool instead of an alias because Loader classes like to emit
376+ // changed signals for 'active' during startup even if they aren't actually
377+ // changing values. Having it cached as a proper Qml bool property prevents
378+ // unnecessary 'changed' emissions and provides consuming classes the
379+ // expected behavior of no emission on startup.
380+ readonly property bool active: loader.active
381+
382+ property int columnCount: 3
383+
384+ hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
385+
386+ onRequiredChanged: {
387+ if (!required) {
388+ available = false;
389+ }
390+ }
391+
392+ Loader {
393+ id: loader
394+ anchors.fill: parent
395+ active: available
396+ source: "DashboardView.qml"
397+ }
398+
399+ Binding {
400+ target: loader.item
401+ property: "columnCount"
402+ value: root.columnCount
403+ when: root.active
404+ }
405+}
406
407=== added file 'qml/Dash/Dashboard/DashboardDelegate.qml'
408--- qml/Dash/Dashboard/DashboardDelegate.qml 1970-01-01 00:00:00 +0000
409+++ qml/Dash/Dashboard/DashboardDelegate.qml 2017-02-10 14:04:06 +0000
410@@ -0,0 +1,116 @@
411+/*
412+ * Copyright (C) 2017 Canonical, Ltd.
413+ *
414+ * This program is free software; you can redistribute it and/or modify
415+ * it under the terms of the GNU General Public License as published by
416+ * the Free Software Foundation; version 3.
417+ *
418+ * This program is distributed in the hope that it will be useful,
419+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
420+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
421+ * GNU General Public License for more details.
422+ *
423+ * You should have received a copy of the GNU General Public License
424+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
425+ */
426+
427+import QtQuick 2.4
428+import Ubuntu.Components 1.3
429+import "../../Notifications"
430+import ".."
431+
432+Item {
433+ id: root
434+ objectName: "dashboardDelegate" + index
435+
436+ // read-write API
437+ property bool editMode: false
438+ property bool shouldClose: false
439+
440+ // readonly API
441+ readonly property int visualIndex: index
442+
443+ scale: editMode ? 0.95 : 1
444+ Behavior on scale { UbuntuNumberAnimation {} }
445+
446+ Drag.keys: "unity8-dashboard"
447+ Drag.active: mouseArea.drag.active
448+ Drag.hotSpot.x: width/2
449+ Drag.hotSpot.y: height/2
450+ Drag.proposedAction: Qt.MoveAction
451+
452+ signal itemDragging(bool dragging, var dragItem)
453+ signal close()
454+ signal undoClose()
455+
456+ Rectangle {
457+ anchors.fill: parent
458+ radius: units.gu(.5)
459+ color: UbuntuColors.jet
460+ opacity: editMode ? 0.15 : 0.1
461+ Behavior on opacity { UbuntuNumberAnimation {} }
462+ }
463+
464+ Label {
465+ id: label
466+ anchors.fill: parent
467+ horizontalAlignment: Text.AlignHCenter
468+ verticalAlignment: Text.AlignVCenter
469+
470+ text: model.name + " (" + index + ")" + (model.content ? "\n" + eval(model.content) : "")
471+ color: model.headerColor ? model.headerColor : "white"
472+ }
473+
474+ MouseArea {
475+ id: mouseArea
476+ enabled: editMode
477+ anchors.fill: parent
478+ cursorShape: drag.active ? Qt.DragMoveCursor : undefined
479+
480+ drag.target: parent
481+
482+ drag.onActiveChanged: {
483+ itemDragging(drag.active, root);
484+ }
485+
486+ onReleased: {
487+ if (drag.active) {
488+ var result = parent.Drag.drop();
489+ if (result) {
490+ print("Drop accepted");
491+ } else {
492+ print("Drop rejected");
493+ }
494+ }
495+ }
496+ }
497+
498+ Timer {
499+ running: model.ttl
500+ repeat: running
501+ interval: running ? model.ttl : 0
502+ onTriggered: label.text = eval(model.content);
503+ }
504+
505+ NotificationButton { // FIXME make this a generic component
506+ objectName: "closeButton"
507+ width: units.gu(3)
508+ height: width
509+ radius: width / 2
510+ visible: opacity > 0
511+ enabled: editMode
512+ opacity: enabled ? 1 : 0
513+ iconName: root.shouldClose ? "add" : "close"
514+ outline: false
515+ hoverEnabled: true
516+ color: root.shouldClose ? theme.palette.normal.positive : theme.palette.normal.negative
517+ anchors.horizontalCenter: parent.left
518+ anchors.horizontalCenterOffset: width/4
519+ anchors.verticalCenter: parent.top
520+ anchors.verticalCenterOffset: height/4
521+
522+ onClicked: root.shouldClose ? undoClose() : close()
523+
524+ Behavior on opacity { UbuntuNumberAnimation {} }
525+ }
526+}
527
528=== added file 'qml/Dash/Dashboard/DashboardView.qml'
529--- qml/Dash/Dashboard/DashboardView.qml 1970-01-01 00:00:00 +0000
530+++ qml/Dash/Dashboard/DashboardView.qml 2017-02-10 14:04:06 +0000
531@@ -0,0 +1,170 @@
532+/*
533+ * Copyright (C) 2017 Canonical, Ltd.
534+ *
535+ * This program is free software; you can redistribute it and/or modify
536+ * it under the terms of the GNU General Public License as published by
537+ * the Free Software Foundation; version 3.
538+ *
539+ * This program is distributed in the hope that it will be useful,
540+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
541+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
542+ * GNU General Public License for more details.
543+ *
544+ * You should have received a copy of the GNU General Public License
545+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
546+ */
547+
548+import QtQuick 2.4
549+import QtQuick.Layouts 1.2
550+import Ubuntu.Components 1.3
551+import Ubuntu.Components.ListItems 1.3
552+import "../../Components"
553+import ".."
554+
555+StyledItem {
556+ id: root
557+ objectName: "dashboard"
558+
559+ property bool editMode: false
560+ property int columnCount: 3
561+
562+ readonly property int leftMargin: units.gu(3)
563+ readonly property int topMargin: units.gu(3)
564+ readonly property int contentSpacing: units.gu(1)
565+
566+ theme: ThemeSettings {
567+ name: "Ubuntu.Components.Themes.Ambiance"
568+ }
569+
570+ ListModel {
571+ id: fakeModel
572+ ListElement { name: "Weather"; headerColor: "yellow" }
573+ ListElement { name: "BBC News"; headerColor: "red" }
574+ ListElement { name: "Fitbit"; headerColor: "teal" }
575+ ListElement { name: "The Guardian"; headerColor: "blue" }
576+ ListElement { name: "Telegram"; headerColor: "navy" }
577+ ListElement { name: "Clock"; content: "Qt.formatTime(new Date())"; ttl: 1000 }
578+ ListElement { name: "G" }
579+ ListElement { name: "H" }
580+ ListElement { name: "I" }
581+ ListElement { name: "J" }
582+ ListElement { name: "K" }
583+ ListElement { name: "L" }
584+ ListElement { name: "M" }
585+ ListElement { name: "N" }
586+ ListElement { name: "O" }
587+ ListElement { name: "P" }
588+ ListElement { name: "Q" }
589+ ListElement { name: "R" }
590+ ListElement { name: "S" }
591+ ListElement { name: "T" }
592+ ListElement { name: "U" }
593+
594+ Component.onCompleted: { // set random heights
595+ for (var i = 0; i < count; i++) {
596+ setProperty(i, "height", Math.max(units.gu(8), Math.floor(Math.random() * 300)));
597+ }
598+ }
599+ }
600+
601+ Component {
602+ id: dashboardViewContentsComponent
603+ DashboardViewContents {
604+ model: fakeModel
605+ editMode: root.editMode
606+ columnCount: root.columnCount
607+ contentSpacing: root.contentSpacing
608+ }
609+ }
610+
611+ Component {
612+ id: dashboardViewLocationComponent
613+ DashboardViewLocation {
614+ contentSpacing: root.contentSpacing
615+ }
616+ }
617+
618+ Loader {
619+ id: loader
620+ active: true
621+ asynchronous: true
622+ visible: status === Loader.Ready
623+ anchors {
624+ left: parent.left
625+ top: parent.top
626+ bottom: buttonBar.top
627+ right: parent.right
628+ leftMargin: root.leftMargin
629+ rightMargin: root.leftMargin
630+ topMargin: root.topMargin
631+ bottomMargin: root.contentSpacing
632+ }
633+ }
634+
635+ RowLayout {
636+ id: buttonBar
637+ spacing: root.contentSpacing
638+ anchors {
639+ left: parent.left
640+ right: parent.right
641+ bottom: parent.bottom
642+ leftMargin: root.leftMargin
643+ rightMargin: root.leftMargin
644+ topMargin: root.contentSpacing
645+ bottomMargin: root.contentSpacing
646+ }
647+
648+ Item { // horizontal spacer
649+ Layout.fillWidth: true
650+ }
651+
652+ Button {
653+ id: btnSources
654+ text: i18n.tr("Add sources")
655+ onClicked: {
656+ root.state = "edit";
657+ // TODO launch Manage dashboard/scopes inside unity8-dash
658+ }
659+ }
660+
661+ Button {
662+ id: btnEditApply
663+ }
664+ }
665+
666+ state: "dashboard"
667+ states: [
668+ State {
669+ name: "dashboard"
670+ when: !root.editMode
671+ PropertyChanges { target: loader; sourceComponent: dashboardViewContentsComponent }
672+ PropertyChanges { target: btnEditApply; text: i18n.tr("Edit"); onClicked: root.editMode = true; }
673+ PropertyChanges { target: btnSources; visible: false }
674+ },
675+ State {
676+ name: "edit"
677+ extend: "dashboard"
678+ when: root.editMode
679+ PropertyChanges { target: btnEditApply; text: i18n.tr("Apply changes");
680+ onClicked: {
681+ var toClose = loader.item.indexesToClose;
682+ print("!!! To close:", toClose);
683+ if (toClose.length > 0) {
684+ toClose.sort(function(a, b) { // sort numerically in descending order
685+ return b - a;
686+ });
687+ print("!!! To close sorted:", toClose);
688+
689+ toClose.forEach(function(index) {
690+ print("Closing", index);
691+ loader.item.model.remove(index, 1);
692+ });
693+ }
694+ root.editMode = false;
695+ loader.item.indexesToClose = [];
696+ }
697+ }
698+ PropertyChanges { target: btnSources; visible: true }
699+ }
700+ ]
701+}
702
703=== added file 'qml/Dash/Dashboard/DashboardViewContents.qml'
704--- qml/Dash/Dashboard/DashboardViewContents.qml 1970-01-01 00:00:00 +0000
705+++ qml/Dash/Dashboard/DashboardViewContents.qml 2017-02-10 14:04:06 +0000
706@@ -0,0 +1,120 @@
707+/*
708+ * Copyright (C) 2017 Canonical, Ltd.
709+ *
710+ * This program is free software; you can redistribute it and/or modify
711+ * it under the terms of the GNU General Public License as published by
712+ * the Free Software Foundation; version 3.
713+ *
714+ * This program is distributed in the hope that it will be useful,
715+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
716+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
717+ * GNU General Public License for more details.
718+ *
719+ * You should have received a copy of the GNU General Public License
720+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
721+ */
722+
723+import QtQuick 2.4
724+import Ubuntu.Components 1.3
725+import Utils 0.1
726+import "../../Components"
727+import ".."
728+import "../../Components/flickableUtils.js" as FlickableUtilsJS
729+
730+ScrollView {
731+ id: contents
732+ objectName: "DashboardViewContents"
733+
734+ // write API
735+ property alias model: journal.model
736+ property bool editMode: false
737+ property int columnCount: 3
738+ property int contentSpacing
739+
740+ // read-write API
741+ property var indexesToClose: []
742+
743+ flickableItem {
744+ flickableDirection: Flickable.VerticalFlick
745+ flickDeceleration: FlickableUtilsJS.getFlickDeceleration(units.gridUnit)
746+ maximumFlickVelocity: FlickableUtilsJS.getMaximumFlickVelocity(units.gridUnit)
747+ boundsBehavior: Flickable.StopAtBounds
748+ }
749+ horizontalScrollbar.enabled: false
750+
751+ Autoscroller {
752+ id: autoscroller
753+ flickable: contents.flickableItem
754+ }
755+
756+ DropArea {
757+ id: dropArea
758+ anchors.fill: parent
759+ keys: "unity8-dashboard"
760+
761+ onDropped: {
762+ var fromIndex = drop.source.visualIndex;
763+ print("Drop from:", drop.source, ", index:", fromIndex);
764+
765+ function matchDelegate(obj) { return String(obj.objectName).indexOf("dashboardDelegate") >= 0 &&
766+ obj.objectName !== drag.source.objectName; }
767+ var delegateAtCenter = Functions.itemAt(journal.view, drop.x, drop.y, matchDelegate);
768+
769+ if (!delegateAtCenter) {
770+ print("Invalid drop, bailing out");
771+ journal.view.relayout();
772+ return;
773+ }
774+
775+ var toIndex = delegateAtCenter.visualIndex;
776+ print("Dropped on", delegateAtCenter, ", index:", toIndex);
777+
778+ if (delegateAtCenter) {
779+ journal.model.move(fromIndex, toIndex, 1);
780+
781+ Array.prototype.move = function (old_index, new_index) {
782+ if (new_index >= this.length) {
783+ var k = new_index - this.length;
784+ while ((k--) + 1) {
785+ this.push(undefined);
786+ }
787+ }
788+ this.splice(new_index, 0, this.splice(old_index, 1)[0]);
789+ return this; // for testing purposes
790+ };
791+ indexesToClose.move(fromIndex, toIndex);
792+
793+ drop.acceptProposedAction();
794+ }
795+ }
796+ }
797+
798+ ResponsiveVerticalJournal {
799+ id: journal
800+ width: contents.width
801+
802+ rowSpacing: contents.contentSpacing
803+ minimumColumnSpacing: contents.contentSpacing
804+ columnWidth: (contents.width - root.leftMargin*2 - contents.verticalScrollbar.width) / contents.columnCount
805+
806+ delegate: DashboardDelegate {
807+ editMode: contents.editMode
808+ shouldClose: indexesToClose.indexOf(index) !== -1
809+ width: parent.columnWidth
810+ height: model.height
811+
812+ onClose: {
813+ print("Closing index:", index);
814+ indexesToClose.push(index);
815+ shouldClose = true;
816+ }
817+ onUndoClose: {
818+ print("Undoing close:", index);
819+ indexesToClose.splice(indexesToClose.indexOf(index), 1);
820+ shouldClose = false;
821+ }
822+
823+ onItemDragging: autoscroller.autoscroll(dragging, dragItem)
824+ }
825+ }
826+}
827
828=== added file 'qml/Dash/Dashboard/DashboardViewLocation.qml'
829--- qml/Dash/Dashboard/DashboardViewLocation.qml 1970-01-01 00:00:00 +0000
830+++ qml/Dash/Dashboard/DashboardViewLocation.qml 2017-02-10 14:04:06 +0000
831@@ -0,0 +1,194 @@
832+/*
833+ * Copyright (C) 2017 Canonical, Ltd.
834+ *
835+ * This program is free software; you can redistribute it and/or modify
836+ * it under the terms of the GNU General Public License as published by
837+ * the Free Software Foundation; version 3.
838+ *
839+ * This program is distributed in the hope that it will be useful,
840+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
841+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
842+ * GNU General Public License for more details.
843+ *
844+ * You should have received a copy of the GNU General Public License
845+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
846+ */
847+
848+import QtQuick 2.4
849+import QtPositioning 5.6
850+import QtLocation 5.6
851+import Ubuntu.Components 1.3
852+import Ubuntu.Components.ListItems 1.3
853+import Ubuntu.SystemSettings.LanguagePlugin 1.0
854+
855+Column {
856+ id: root
857+ objectName: "DashboardViewLocation"
858+ spacing: root.contentSpacing
859+
860+ // write API
861+ property int contentSpacing
862+
863+ // read API
864+ readonly property string currentLanguage: langPlugin.languageCodes[langListView.currentIndex] // locale code
865+ readonly property var location: src.position.coordinate // QtPositioning.coordinate
866+
867+ UbuntuLanguagePlugin {
868+ id: langPlugin
869+ }
870+
871+ Plugin {
872+ id: osmPlugin
873+ name: "osm"
874+ PluginParameter { name: "osm.useragent"; value: "Unity 8 Dashboard" }
875+ }
876+
877+ GeocodeModel {
878+ id: geo
879+ autoUpdate: false
880+ plugin: osmPlugin
881+ query: src.position.coordinate.isValid ? src.position.coordinate : undefined
882+ onStatusChanged: {
883+ print("Reverse geocoder status changed:", status)
884+ if (status === GeocodeModel.Ready) {
885+ print("Ready, results:", count)
886+ } else if (status === GeocodeModel.Error) {
887+ console.error("Reverse geocoder error:", errorString, error);
888+ }
889+ }
890+ onLocationsChanged: {
891+ if (count > 0) {
892+ var address = get(0).address;
893+ print("LOCATION:", address.text)
894+ var parts = [address.district, address.city, address.state, address.country]
895+ locationEdit.text = parts.filter(Boolean).join(", ");
896+ }
897+ }
898+ }
899+
900+ PositionSource {
901+ id: src
902+ active: false
903+
904+ onPositionChanged: {
905+ var coord = position.coordinate;
906+ console.log("Coordinate:", coord, "(", coord.latitude, coord.longitude, ")");
907+ geo.update();
908+ }
909+ }
910+
911+ Component.onCompleted: {
912+ if (src.valid) {
913+ src.update(); // Request a single update of the position
914+ } else {
915+ console.warn("No supported positioning methods")
916+ }
917+ }
918+
919+ Label {
920+ anchors {
921+ left: parent.left
922+ right: parent.right
923+ }
924+ text: i18n.tr("Location and language")
925+ textSize: Label.Large
926+ wrapMode: Label.WordWrap
927+ maximumLineCount: 2
928+ color: "white"
929+ }
930+
931+ Column {
932+ anchors {
933+ left: parent.left
934+ right: parent.right
935+ }
936+ spacing: root.contentSpacing
937+
938+ Image {
939+ anchors.horizontalCenter: parent.horizontalCenter
940+ width: units.gu(15)
941+ height: width
942+ source: "image://theme/language-chooser"
943+ }
944+
945+ TextField {
946+ id: locationEdit
947+ enabled: !checkbox.checked
948+ anchors {
949+ left: parent.left
950+ right: parent.right
951+ }
952+ placeholderText: i18n.tr("Select your location")
953+ primaryItem: Icon {
954+ name: "location-active"
955+ height: parent.height * 0.7
956+ width: height
957+ }
958+ secondaryItem: Icon {
959+ name: "reset"
960+ height: parent.height * 0.7
961+ width: height
962+ visible: src.valid
963+ MouseArea {
964+ anchors.fill: parent
965+ onClicked: src.update();
966+ }
967+ }
968+ }
969+
970+ ComboButton {
971+ id: langCombo
972+ anchors {
973+ left: parent.left
974+ right: parent.right
975+ }
976+ text: langPlugin.languageNames[langListView.currentIndex]
977+ ListView {
978+ id: langListView
979+ anchors {
980+ left: parent.left
981+ right: parent.right
982+ }
983+ model: langPlugin.languageNames
984+ currentIndex: langPlugin.currentLanguage
985+ delegate: Standard {
986+ highlightWhenPressed: false
987+ __foregroundColor: UbuntuColors.jet
988+ text: modelData
989+ onClicked: {
990+ langListView.currentIndex = index;
991+ langCombo.expanded = false;
992+ }
993+ }
994+ }
995+ onClicked: expanded = !expanded
996+ }
997+
998+ Item {
999+ anchors {
1000+ left: parent.left
1001+ right: parent.right
1002+ }
1003+ height: childrenRect.height
1004+ CheckBox {
1005+ id: checkbox
1006+ anchors.left: parent.left
1007+ }
1008+ Label {
1009+ anchors {
1010+ left: checkbox.right
1011+ right: parent.right
1012+ leftMargin: root.contentSpacing
1013+ }
1014+ text: i18n.tr("Do not allow Scopes to use your location and language")
1015+ wrapMode: Label.WordWrap
1016+ maximumLineCount: 2
1017+ color: "white"
1018+ MouseArea {
1019+ anchors.fill: parent
1020+ onClicked: checkbox.trigger()
1021+ }
1022+ }
1023+ }
1024+ }
1025+}
1026
1027=== modified file 'qml/Dash/ScopesList.qml'
1028--- qml/Dash/ScopesList.qml 2016-12-01 23:26:45 +0000
1029+++ qml/Dash/ScopesList.qml 2017-02-10 14:04:06 +0000
1030@@ -37,92 +37,9 @@
1031 signal requestFavoriteMoveTo(string scopeId, int index)
1032 signal requestRestore(string scopeId)
1033
1034- Item {
1035+ Autoscroller {
1036 id: autoscroller
1037-
1038- property bool dragging: false
1039- property var dragItem: new Object();
1040-
1041- readonly property bool fuzzyAtYEnd: {
1042- var contentHeight = root.scopesListFlickable.contentHeight
1043- var contentY = root.scopesListFlickable.contentY
1044- var dragItemHeight = dragItem ? autoscroller.dragItem.height : 0
1045- var flickableHeight = root.scopesListFlickable.height
1046-
1047- if (!dragItem) {
1048- return true;
1049- } else {
1050- return contentY >= (contentHeight - flickableHeight) - dragItemHeight
1051- }
1052- }
1053-
1054- readonly property real bottomBoundary: {
1055- var contentHeight = root.scopesListFlickable.contentHeight
1056- var contentY = root.scopesListFlickable.contentY
1057- var dragItemHeight = dragItem ? autoscroller.dragItem.height : 0
1058- var heightRatio = root.scopesListFlickable.visibleArea.heightRatio
1059-
1060- if (!dragItem) {
1061- return true;
1062- } else {
1063- return (heightRatio * contentHeight) -
1064- (1.5 * dragItemHeight) + contentY
1065- }
1066- }
1067-
1068- readonly property int delayMs: 32
1069- readonly property real topBoundary: dragItem ? root.scopesListFlickable.contentY + (.5 * dragItem.height) : 0
1070-
1071- visible: false
1072- readonly property real maxStep: units.dp(10)
1073- function stepSize(scrollingUp) {
1074- var delta, step;
1075- if (scrollingUp) {
1076- delta = dragItem.y - topBoundary;
1077- delta /= (1.5 * dragItem.height);
1078- } else {
1079- delta = dragItem.y - bottomBoundary;
1080- delta /= (1.5 * dragItem.height);
1081- }
1082-
1083- step = Math.abs(delta) * autoscroller.maxStep
1084- return Math.ceil(step);
1085- }
1086-
1087-
1088- Timer {
1089- interval: autoscroller.delayMs
1090- running: autoscroller.dragItem ? (autoscroller.dragging &&
1091- autoscroller.dragItem.y < autoscroller.topBoundary &&
1092- !root.scopesListFlickable.atYBeginning) : false
1093- repeat: true
1094- onTriggered: {
1095- root.scopesListFlickable.contentY -= autoscroller.stepSize(true);
1096- autoscroller.dragItem.y -= autoscroller.stepSize(true);
1097- }
1098- }
1099-
1100- Timer {
1101- interval: autoscroller.delayMs
1102- running: autoscroller.dragItem ? (autoscroller.dragging &&
1103- autoscroller.dragItem.y >= autoscroller.bottomBoundary &&
1104- !autoscroller.fuzzyAtYEnd) : false
1105- repeat: true
1106- onTriggered: {
1107- root.scopesListFlickable.contentY += autoscroller.stepSize(false);
1108- autoscroller.dragItem.y += autoscroller.stepSize(false);
1109- }
1110- }
1111- }
1112-
1113- function autoscroll(dragging, dragItem) {
1114- if (dragging) {
1115- autoscroller.dragItem = dragItem
1116- autoscroller.dragging = true;
1117- } else {
1118- autoscroller.dragItem = null;
1119- autoscroller.dragging = false
1120- }
1121+ flickable: scopesListFlickable
1122 }
1123
1124 state: "browse"
1125@@ -173,33 +90,33 @@
1126 }
1127 clip: true
1128 model: scope ? scope.categories : null
1129- delegate: Loader {
1130- asynchronous: true
1131- width: root.width
1132- active: results.count > 0
1133- visible: active
1134- sourceComponent: ScopesListCategory {
1135- objectName: "scopesListCategory" + categoryId
1136-
1137- model: results
1138-
1139- title: {
1140- if (isFavoritesFeed) return i18n.tr("Home");
1141- else if (isAlsoInstalled) return i18n.tr("Also installed");
1142- else return name;
1143- }
1144-
1145- editMode: root.state == "edit"
1146- scopeStyle: root.scopeStyle
1147- isFavoritesFeed: categoryId == "favorites"
1148- isAlsoInstalled: categoryId == "other"
1149-
1150- onItemDragging: autoscroll(dragging, dragItem);
1151- onRequestFavorite: root.requestFavorite(scopeId, favorite);
1152- onRequestEditMode: root.state = "edit";
1153- onRequestScopeMoveTo: root.requestFavoriteMoveTo(scopeId, index);
1154- onRequestActivate: root.scope.activate(result, categoryId);
1155- onRequestRestore: root.requestRestore(scopeId);
1156+ delegate: Loader {
1157+ asynchronous: true
1158+ width: root.width
1159+ active: results.count > 0
1160+ visible: active
1161+ sourceComponent: ScopesListCategory {
1162+ objectName: "scopesListCategory" + categoryId
1163+
1164+ model: results
1165+
1166+ title: {
1167+ if (isFavoritesFeed) return i18n.tr("Home");
1168+ else if (isAlsoInstalled) return i18n.tr("Also installed");
1169+ else return name;
1170+ }
1171+
1172+ editMode: root.state == "edit"
1173+ scopeStyle: root.scopeStyle
1174+ isFavoritesFeed: categoryId == "favorites"
1175+ isAlsoInstalled: categoryId == "other"
1176+
1177+ onItemDragging: autoscroller.autoscroll(dragging, dragItem);
1178+ onRequestFavorite: root.requestFavorite(scopeId, favorite);
1179+ onRequestEditMode: root.state = "edit";
1180+ onRequestScopeMoveTo: root.requestFavoriteMoveTo(scopeId, index);
1181+ onRequestActivate: root.scope.activate(result, categoryId);
1182+ onRequestRestore: root.requestRestore(scopeId);
1183 }
1184 }
1185 }
1186
1187=== modified file 'qml/Greeter/ShimGreeter.qml'
1188--- qml/Greeter/ShimGreeter.qml 2015-07-15 15:07:19 +0000
1189+++ qml/Greeter/ShimGreeter.qml 2017-02-10 14:04:06 +0000
1190@@ -1,5 +1,5 @@
1191 /*
1192- * Copyright (C) 2015 Canonical, Ltd.
1193+ * Copyright (C) 2015-2017 Canonical, Ltd.
1194 *
1195 * This program is free software; you can redistribute it and/or modify
1196 * it under the terms of the GNU General Public License as published by
1197@@ -22,7 +22,7 @@
1198 *
1199 */
1200
1201-/* FIXME: this shuld be fine as a QtObject, but bug lp:1447391
1202+/* FIXME: this should be fine as a QtObject, but bug lp:1447391
1203 * dictates wrapping as an item instead
1204 */
1205 Item {
1206@@ -41,5 +41,6 @@
1207 property var notifyAboutToFocusApp: (function(appId) { return; })
1208 property var notifyAppFocused: (function(appId) { return; })
1209 property var notifyShowingDashFromDrag: (function(appId) { return false; })
1210-
1211+ property var notifyUserRequestedApp: (function() { return; })
1212+ property var notifyAppFocusRequested: (function() { return; })
1213 }
1214
1215=== modified file 'qml/Shell.qml'
1216--- qml/Shell.qml 2017-01-24 07:43:54 +0000
1217+++ qml/Shell.qml 2017-02-10 14:04:06 +0000
1218@@ -44,6 +44,7 @@
1219 import Unity.Indicators 0.1 as Indicators
1220 import Cursor 1.1
1221 import WindowManager 1.0
1222+import Wizard 0.1
1223
1224
1225 StyledItem {
1226@@ -319,6 +320,12 @@
1227 panel.indicators.hide();
1228 panel.applicationMenus.hide();
1229 }
1230+
1231+ Binding {
1232+ target: stage.dashboard
1233+ property: "available"
1234+ value: !panel.focusedSurfaceIsFullscreen && !greeter.shown && !wizard.active
1235+ }
1236 }
1237
1238 TouchGestureArea {
1239
1240=== modified file 'qml/Stage/DecoratedWindow.qml'
1241--- qml/Stage/DecoratedWindow.qml 2017-01-26 11:10:01 +0000
1242+++ qml/Stage/DecoratedWindow.qml 2017-02-10 14:04:06 +0000
1243@@ -197,7 +197,6 @@
1244
1245 WindowDecoration {
1246 id: decoration
1247- closeButtonVisible: root.application.appId !== "unity8-dash"
1248 objectName: "appWindowDecoration"
1249
1250 anchors { left: parent.left; top: parent.top; right: parent.right }
1251
1252=== modified file 'qml/Stage/Spread/Spread.qml'
1253--- qml/Stage/Spread/Spread.qml 2016-11-07 12:37:56 +0000
1254+++ qml/Stage/Spread/Spread.qml 2017-02-10 14:04:06 +0000
1255@@ -43,6 +43,12 @@
1256
1257 // Calculated stuff
1258 readonly property int totalItemCount: model.count
1259+ onTotalItemCountChanged: {
1260+ if (totalItemCount == 0) {
1261+ root.leaveSpread();
1262+ }
1263+ }
1264+
1265 readonly property real leftStackXPos: 0.03 * root.width + leftMargin
1266 readonly property real rightStackXPos: root.width - 1.5 * leftStackXPos + leftMargin
1267
1268
1269=== modified file 'qml/Stage/Stage.qml'
1270--- qml/Stage/Stage.qml 2017-01-26 11:10:01 +0000
1271+++ qml/Stage/Stage.qml 2017-02-10 14:04:06 +0000
1272@@ -25,6 +25,7 @@
1273 import GSettings 1.0
1274 import "Spread"
1275 import "Spread/MathUtils.js" as MathUtils
1276+import "../Dash/Dashboard"
1277
1278 FocusScope {
1279 id: root
1280@@ -56,6 +57,9 @@
1281 // used by the snap windows (edge maximize) feature
1282 readonly property alias previewRectangle: fakeRectangle
1283
1284+ // for shell & dash
1285+ readonly property alias dashboard: dashboard
1286+
1287 readonly property bool spreadShown: state == "spread"
1288 readonly property var mainApp: priv.focusedAppDelegate ? priv.focusedAppDelegate.application : null
1289
1290@@ -160,7 +164,7 @@
1291 id: closeFocusedShortcut
1292 shortcut: Qt.AltModifier|Qt.Key_F4
1293 onTriggered: {
1294- if (priv.focusedAppDelegate && !priv.focusedAppDelegate.isDash) {
1295+ if (priv.focusedAppDelegate) {
1296 priv.focusedAppDelegate.close();
1297 }
1298 }
1299@@ -327,7 +331,7 @@
1300 }
1301 }
1302
1303- property int nextInStack: {
1304+ readonly property int nextInStack: {
1305 var mainStageIndex = priv.mainStageDelegate ? priv.mainStageDelegate.itemIndex : -1;
1306 var sideStageIndex = priv.sideStageDelegate ? priv.sideStageDelegate.itemIndex : -1;
1307 if (sideStageIndex == -1) {
1308@@ -398,7 +402,7 @@
1309 Binding {
1310 target: PanelState
1311 property: "closeButtonShown"
1312- value: priv.focusedAppDelegate && priv.focusedAppDelegate.maximized && !priv.focusedAppDelegate.isDash
1313+ value: priv.focusedAppDelegate && priv.focusedAppDelegate.maximized
1314 }
1315
1316 Component.onDestruction: {
1317@@ -411,7 +415,6 @@
1318 model: root.applicationManager
1319 delegate: QtObject {
1320 property var stateBinding: Binding {
1321- readonly property bool isDash: model.application ? model.application.appId == "unity8-dash" : false
1322 target: model.application
1323 property: "requestedState"
1324
1325@@ -421,7 +424,6 @@
1326 // in staged mode, when it switches to Windowed mode it will suddenly
1327 // resume all those apps at once. We might want to avoid that.
1328 value: root.mode === "windowed"
1329- || isDash
1330 || (!root.suspended && model.application && priv.focusedAppDelegate &&
1331 (priv.focusedAppDelegate.appId === model.application.appId ||
1332 priv.mainStageAppId === model.application.appId ||
1333@@ -450,6 +452,7 @@
1334 PropertyChanges { target: cancelSpreadMouseArea; enabled: true }
1335 PropertyChanges { target: blurLayer; visible: true; blurRadius: 32; brightness: .65; opacity: 1 }
1336 PropertyChanges { target: wallpaper; visible: false }
1337+ PropertyChanges { target: dashboard; available: false }
1338 },
1339 State {
1340 name: "stagedRightEdge"; when: (rightEdgeDragArea.dragging || edgeBarrier.progress > 0) && root.mode == "staged"
1341@@ -460,13 +463,14 @@
1342 brightness: .65
1343 opacity: 1
1344 }
1345+ PropertyChanges { target: dashboard; available: false }
1346 },
1347 State {
1348 name: "sideStagedRightEdge"; when: (rightEdgeDragArea.dragging || edgeBarrier.progress > 0) && root.mode == "stagedWithSideStage"
1349 extend: "stagedRightEdge"
1350 PropertyChanges {
1351 target: sideStage
1352- opacity: priv.sideStageDelegate.x === sideStage.x ? 1 : 0
1353+ opacity: priv.sideStageDelegate && priv.sideStageDelegate.x === sideStage.x ? 1 : 0
1354 visible: true
1355 }
1356 },
1357@@ -479,18 +483,21 @@
1358 brightness: .65
1359 opacity: MathUtils.linearAnimation(spreadItem.rightEdgeBreakPoint, 1, 0, 1, Math.max(rightEdgeDragArea.dragging ? rightEdgeDragArea.progress : 0, edgeBarrier.progress))
1360 }
1361+ PropertyChanges { target: dashboard; available: false }
1362 },
1363 State {
1364 name: "staged"; when: root.mode === "staged"
1365- PropertyChanges { target: wallpaper; visible: false }
1366+ PropertyChanges { target: dashboard; available: true; columnCount: 1 }
1367 },
1368 State {
1369 name: "stagedWithSideStage"; when: root.mode === "stagedWithSideStage"
1370 PropertyChanges { target: triGestureArea; enabled: priv.sideStageEnabled }
1371 PropertyChanges { target: sideStage; visible: true }
1372+ PropertyChanges { target: dashboard; available: true; columnCount: 3 }
1373 },
1374 State {
1375 name: "windowed"; when: root.mode === "windowed"
1376+ PropertyChanges { target: dashboard; available: true; columnCount: 5 }
1377 }
1378 ]
1379 transitions: [
1380@@ -510,6 +517,7 @@
1381 ScriptAction {
1382 script: {
1383 var item = appRepeater.itemAt(Math.max(0, spreadItem.highlightedIndex));
1384+ if (!item) return;
1385 if (item.stage == ApplicationInfoInterface.SideStage && !sideStage.shown) {
1386 sideStage.show();
1387 }
1388@@ -527,7 +535,6 @@
1389 to: "stagedWithSideStage"
1390 ScriptAction { script: priv.updateMainAndSideStageIndexes(); }
1391 }
1392-
1393 ]
1394
1395 MouseArea {
1396@@ -552,6 +559,13 @@
1397 z: -2
1398 }
1399
1400+ Dashboard {
1401+ id: dashboard
1402+ anchors.fill: parent
1403+ anchors.leftMargin: root.leftMargin
1404+ anchors.topMargin: PanelState.panelHeight
1405+ }
1406+
1407 BlurLayer {
1408 id: blurLayer
1409 anchors.fill: parent
1410@@ -578,7 +592,6 @@
1411 onListChanged: priv.updateMainAndSideStageIndexes()
1412 }
1413
1414-
1415 DropArea {
1416 objectName: "MainStageDropArea"
1417 anchors {
1418@@ -1643,7 +1656,6 @@
1419 objectName: "dragArea"
1420 anchors.fill: decoratedWindow
1421 enabled: false
1422- closeable: !appDelegate.isDash
1423
1424 onClicked: {
1425 spreadItem.highlightedIndex = index;
1426@@ -1687,7 +1699,7 @@
1427 objectName: "closeMouseArea"
1428 anchors { left: parent.left; top: parent.top; leftMargin: -height / 2; topMargin: -height / 2 + spreadMaths.closeIconOffset }
1429 readonly property var mousePos: hoverMouseArea.mapToItem(appDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
1430- visible: !appDelegate.isDash && dragArea.distance == 0
1431+ visible: dragArea.distance == 0
1432 && index == spreadItem.highlightedIndex
1433 && mousePos.y < (decoratedWindow.height / 3)
1434 && mousePos.y > -units.gu(4)
1435
1436=== modified file 'tests/mocks/Unity/Application/ApplicationManager.cpp'
1437--- tests/mocks/Unity/Application/ApplicationManager.cpp 2017-01-26 11:10:01 +0000
1438+++ tests/mocks/Unity/Application/ApplicationManager.cpp 2017-02-10 14:04:06 +0000
1439@@ -125,7 +125,7 @@
1440 return nullptr;
1441 }
1442
1443-QModelIndex ApplicationManager::findIndex(ApplicationInfo* application)
1444+QModelIndex ApplicationManager::findIndex(ApplicationInfo* application) const
1445 {
1446 for (int i = 0; i < m_runningApplications.size(); ++i) {
1447 if (m_runningApplications.at(i) == application) {
1448@@ -253,7 +253,7 @@
1449 return application;
1450 }
1451
1452-ApplicationInfo* ApplicationManager::add(QString appId)
1453+ApplicationInfo* ApplicationManager::add(const QString &appId)
1454 {
1455 ApplicationInfo *application = nullptr;
1456
1457@@ -492,7 +492,7 @@
1458 }
1459
1460
1461-QStringList ApplicationManager::availableApplications()
1462+QStringList ApplicationManager::availableApplications() const
1463 {
1464 QStringList appIds;
1465 Q_FOREACH(ApplicationInfo *app, m_availableApplications) {
1466@@ -506,7 +506,7 @@
1467 return m_runningApplications.isEmpty();
1468 }
1469
1470-ApplicationInfo *ApplicationManager::findApplication(MirSurface* surface)
1471+ApplicationInfo *ApplicationManager::findApplication(MirSurface* surface) const
1472 {
1473 for (ApplicationInfo *app : m_runningApplications) {
1474 auto surfaceList = static_cast<MirSurfaceListModel*>(app->surfaceList());
1475@@ -517,13 +517,13 @@
1476 return nullptr;
1477 }
1478
1479-QString ApplicationManager::toString()
1480+QString ApplicationManager::toString() const
1481 {
1482 QString str;
1483 for (int i = 0; i < m_runningApplications.count(); ++i) {
1484 auto *application = m_runningApplications.at(i);
1485
1486- QString itemStr = QString("(index=%1,appId=%2)")
1487+ QString itemStr = QStringLiteral("(index=%1,appId=%2)")
1488 .arg(i)
1489 .arg(application->appId());
1490
1491
1492=== modified file 'tests/mocks/Unity/Application/ApplicationManager.h'
1493--- tests/mocks/Unity/Application/ApplicationManager.h 2016-12-07 11:19:17 +0000
1494+++ tests/mocks/Unity/Application/ApplicationManager.h 2017-02-10 14:04:06 +0000
1495@@ -66,10 +66,10 @@
1496 QString focusedApplicationId() const override;
1497
1498 // Only for testing
1499- QStringList availableApplications();
1500- Q_INVOKABLE ApplicationInfo* add(QString appId);
1501+ QStringList availableApplications() const;
1502+ Q_INVOKABLE ApplicationInfo* add(const QString &appId);
1503
1504- QModelIndex findIndex(ApplicationInfo* application);
1505+ QModelIndex findIndex(ApplicationInfo* application) const;
1506
1507 bool isEmpty() const;
1508
1509@@ -85,8 +85,8 @@
1510 bool add(ApplicationInfo *application);
1511 void remove(ApplicationInfo* application);
1512 void buildListOfAvailableApplications();
1513- QString toString();
1514- ApplicationInfo *findApplication(MirSurface* surface);
1515+ QString toString() const;
1516+ ApplicationInfo *findApplication(MirSurface* surface) const;
1517 QList<ApplicationInfo*> m_runningApplications;
1518 QList<ApplicationInfo*> m_availableApplications;
1519 bool m_modelBusy{false};
1520
1521=== modified file 'tests/qmltests/CMakeLists.txt'
1522--- tests/qmltests/CMakeLists.txt 2017-01-18 14:31:42 +0000
1523+++ tests/qmltests/CMakeLists.txt 2017-02-10 14:04:06 +0000
1524@@ -29,6 +29,7 @@
1525 add_unity8_qmltest(Components WallpaperResolver)
1526 add_unity8_qmltest(Components ZoomableImage)
1527 add_unity8_qmltest(Dash Dash)
1528+add_unity8_qmltest(Dash Dashboard)
1529 add_unity8_qmltest(Dash DashContent)
1530 add_unity8_qmltest(Dash DashShell)
1531 add_unity8_qmltest(Dash Card)
1532
1533=== added file 'tests/qmltests/Dash/tst_Dashboard.qml'
1534--- tests/qmltests/Dash/tst_Dashboard.qml 1970-01-01 00:00:00 +0000
1535+++ tests/qmltests/Dash/tst_Dashboard.qml 2017-02-10 14:04:06 +0000
1536@@ -0,0 +1,433 @@
1537+/*
1538+ * Copyright (C) 2013-2016 Canonical, Ltd.
1539+ *
1540+ * This program is free software; you can redistribute it and/or modify
1541+ * it under the terms of the GNU General Public License as published by
1542+ * the Free Software Foundation; version 3.
1543+ *
1544+ * This program is distributed in the hope that it will be useful,
1545+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1546+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1547+ * GNU General Public License for more details.
1548+ *
1549+ * You should have received a copy of the GNU General Public License
1550+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1551+ */
1552+
1553+import QtQuick 2.4
1554+import QtTest 1.0
1555+import AccountsService 0.1
1556+import GSettings 1.0
1557+import LightDM.IntegratedLightDM 0.1 as LightDM
1558+import SessionBroadcast 0.1
1559+import Ubuntu.Components 1.3
1560+import Ubuntu.Components.ListItems 1.3 as ListItem
1561+import Unity.Application 0.1
1562+import Unity.ApplicationMenu 0.1
1563+import Unity.Launcher 0.1
1564+import Unity.Test 0.1
1565+import Powerd 0.1
1566+import Wizard 0.1 as Wizard
1567+import Utils 0.1
1568+
1569+import "../../../qml"
1570+import "../../../qml/Components"
1571+import "../../../qml/Components/PanelState"
1572+import "../Stage"
1573+import ".."
1574+
1575+Rectangle {
1576+ id: root
1577+ color: "grey"
1578+ width: units.gu(100) + controls.width
1579+ height: units.gu(71)
1580+
1581+ Component.onCompleted: {
1582+ // must set the mock mode before loading the Shell
1583+ LightDM.Greeter.mockMode = "single";
1584+ LightDM.Users.mockMode = "single";
1585+ shellLoader.active = true;
1586+ }
1587+
1588+ ApplicationMenuDataLoader {
1589+ id: appMenuData
1590+ }
1591+
1592+ property var shell: shellLoader.item ? shellLoader.item : null
1593+ onShellChanged: {
1594+ if (shell) {
1595+ topLevelSurfaceList = testCase.findInvisibleChild(shell, "topLevelSurfaceList");
1596+ appMenuData.surfaceManager = testCase.findInvisibleChild(shell, "surfaceManager");
1597+ dashboard = testCase.findChild(shell, "dashboard");
1598+ } else {
1599+ topLevelSurfaceList = null;
1600+ appMenuData.surfaceManager = null;
1601+ dashboard = null;
1602+ }
1603+ }
1604+
1605+ property var topLevelSurfaceList: null
1606+ property var dashboard: null
1607+
1608+ Item {
1609+ id: shellContainer
1610+ anchors.left: root.left
1611+ anchors.right: controls.left
1612+ anchors.top: root.top
1613+ anchors.bottom: root.bottom
1614+ Loader {
1615+ id: shellLoader
1616+ focus: true
1617+
1618+ anchors.centerIn: parent
1619+
1620+ property int shellOrientation: Qt.PortraitOrientation
1621+ property int nativeOrientation: Qt.PortraitOrientation
1622+ property int primaryOrientation: Qt.PortraitOrientation
1623+ property string mode: "shell"
1624+
1625+ state: "phone"
1626+ states: [
1627+ State {
1628+ name: "phone"
1629+ PropertyChanges {
1630+ target: shellLoader
1631+ width: units.gu(40)
1632+ height: units.gu(71)
1633+ }
1634+ },
1635+ State {
1636+ name: "tablet"
1637+ PropertyChanges {
1638+ target: shellLoader
1639+ width: units.gu(100)
1640+ height: units.gu(71)
1641+ shellOrientation: Qt.LandscapeOrientation
1642+ nativeOrientation: Qt.LandscapeOrientation
1643+ primaryOrientation: Qt.LandscapeOrientation
1644+ }
1645+ },
1646+ State {
1647+ name: "desktop"
1648+ PropertyChanges {
1649+ target: shellLoader
1650+ width: shellContainer.width
1651+ height: shellContainer.height
1652+ }
1653+ PropertyChanges {
1654+ target: mouseEmulation
1655+ checked: false
1656+ }
1657+ }
1658+ ]
1659+
1660+ active: false
1661+ property bool itemDestroyed: false
1662+ sourceComponent: Shell {
1663+ id: __shell
1664+ objectName: "shell"
1665+ usageScenario: usageScenarioSelector.model[usageScenarioSelector.selectedIndex]
1666+ onUsageScenarioChanged: columnCountSelector.selectedIndex = usageScenarioSelector.selectedIndex;
1667+ nativeWidth: width
1668+ nativeHeight: height
1669+ orientation: shellLoader.shellOrientation
1670+ orientations: Orientations {
1671+ native_: shellLoader.nativeOrientation
1672+ primary: shellLoader.primaryOrientation
1673+ }
1674+ mode: "shell"
1675+
1676+ Component.onDestruction: {
1677+ shellLoader.itemDestroyed = true;
1678+ }
1679+ }
1680+ }
1681+ }
1682+
1683+ Flickable {
1684+ id: controls
1685+ contentHeight: controlRect.height
1686+
1687+ anchors.top: root.top
1688+ anchors.bottom: root.bottom
1689+ anchors.right: root.right
1690+ width: units.gu(30)
1691+
1692+ Rectangle {
1693+ id: controlRect
1694+ anchors { left: parent.left; right: parent.right }
1695+ color: "darkgrey"
1696+ height: childrenRect.height + units.gu(2)
1697+
1698+ Column {
1699+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
1700+ spacing: units.gu(1)
1701+
1702+ Flow {
1703+ spacing: units.gu(1)
1704+ anchors { left: parent.left; right: parent.right }
1705+
1706+ Button {
1707+ text: "Show Launcher"
1708+ activeFocusOnPress: false
1709+ activeFocusOnTab: false
1710+ enabled: !autohideLauncherCheckbox.checked
1711+ onClicked: {
1712+ if (shellLoader.status !== Loader.Ready)
1713+ return;
1714+
1715+ var launcher = testCase.findChild(shell, "launcher");
1716+ launcher.state = "visible";
1717+ }
1718+ }
1719+ Button {
1720+ text: "Print focused"
1721+ activeFocusOnPress: false
1722+ activeFocusOnTab: false
1723+ onClicked: {
1724+ var childs = [];
1725+ childs.push(shell)
1726+ while (childs.length > 0) {
1727+ if (childs[0].activeFocus && childs[0].focus && childs[0].objectName != "shell") {
1728+ console.log("Active focus is on item:", childs[0]);
1729+ return;
1730+ }
1731+ for (var i in childs[0].children) {
1732+ childs.push(childs[0].children[i])
1733+ }
1734+ childs.splice(0, 1);
1735+ }
1736+ console.log("No active focused item found within shell.")
1737+ }
1738+ }
1739+ }
1740+
1741+ ListItem.ItemSelector {
1742+ id: columnCountSelector
1743+ anchors { left: parent.left; right: parent.right }
1744+ activeFocusOnPress: false
1745+ text: "Dashboard column count"
1746+ model: [1, 3, 5]
1747+ onSelectedIndexChanged: {
1748+ if (dashboard) dashboard.columnCount = model[selectedIndex];
1749+ }
1750+ }
1751+
1752+ ListItem.ItemSelector {
1753+ id: sizeSelector
1754+ anchors { left: parent.left; right: parent.right }
1755+ activeFocusOnPress: false
1756+ text: "Size"
1757+ model: ["phone", "tablet", "desktop"]
1758+ selectedIndex: 2
1759+ onSelectedIndexChanged: {
1760+ shellLoader.state = model[selectedIndex];
1761+ }
1762+ }
1763+
1764+ ListItem.ItemSelector {
1765+ id: usageScenarioSelector
1766+ anchors { left: parent.left; right: parent.right }
1767+ activeFocusOnPress: false
1768+ text: "Usage scenario"
1769+ selectedIndex: 2
1770+ model: ["phone", "tablet", "desktop"]
1771+ }
1772+
1773+ MouseTouchEmulationCheckbox {
1774+ id: mouseEmulation
1775+ checked: true
1776+ }
1777+
1778+ ListItem.ItemSelector {
1779+ id: ctrlModifier
1780+ anchors { left: parent.left; right: parent.right }
1781+ activeFocusOnPress: false
1782+ text: "Ctrl key as"
1783+ model: ["Ctrl", "Alt", "Super"]
1784+ onSelectedIndexChanged: {
1785+ var keyMapper = testCase.findChild(shellContainer, "physicalKeysMapper");
1786+ keyMapper.controlInsteadOfAlt = selectedIndex == 1;
1787+ keyMapper.controlInsteadOfSuper = selectedIndex == 2;
1788+ }
1789+ }
1790+
1791+ Row {
1792+ anchors { left: parent.left; right: parent.right }
1793+ CheckBox {
1794+ id: autohideLauncherCheckbox
1795+ activeFocusOnPress: false
1796+ activeFocusOnTab: false
1797+ onCheckedChanged: {
1798+ GSettingsController.setAutohideLauncher(checked)
1799+ }
1800+ }
1801+ Label {
1802+ text: "Autohide launcher"
1803+ }
1804+ }
1805+ }
1806+ }
1807+ }
1808+
1809+ SignalSpy {
1810+ id: launcherShowDashHomeSpy
1811+ signalName: "showDashHome"
1812+ }
1813+
1814+ SignalSpy {
1815+ id: sessionSpy
1816+ signalName: "sessionStarted"
1817+ }
1818+
1819+ SignalSpy {
1820+ id: dashCommunicatorSpy
1821+ signalName: "setCurrentScopeCalled"
1822+ }
1823+
1824+ SignalSpy {
1825+ id: broadcastUrlSpy
1826+ target: SessionBroadcast
1827+ signalName: "startUrl"
1828+ }
1829+
1830+ SignalSpy {
1831+ id: broadcastHomeSpy
1832+ target: SessionBroadcast
1833+ signalName: "showHome"
1834+ }
1835+
1836+ Item {
1837+ id: fakeDismissTimer
1838+ property bool running: false
1839+ signal triggered
1840+
1841+ function stop() {
1842+ running = false;
1843+ }
1844+
1845+ function restart() {
1846+ running = true;
1847+ }
1848+ }
1849+
1850+ StageTestCase {
1851+ id: testCase
1852+ name: "Shell"
1853+ when: windowShown
1854+
1855+ property Item shell: shellLoader.status === Loader.Ready ? shellLoader.item : null
1856+
1857+ function init() {
1858+ if (shellLoader.active) {
1859+ // happens for the very first test function as shell
1860+ // is loaded by default
1861+ tearDown();
1862+ }
1863+ }
1864+
1865+ function cleanup() {
1866+ waitForRendering(shell);
1867+ mouseEmulation.checked = true;
1868+ tryCompare(shell, "waitingOnGreeter", false); // make sure greeter didn't leave us in disabled state
1869+ tearDown();
1870+ WindowStateStorage.clear();
1871+ }
1872+
1873+ function loadShell(formFactor) {
1874+ shellLoader.state = formFactor;
1875+ shellLoader.active = true;
1876+ tryCompare(shellLoader, "status", Loader.Ready);
1877+ removeTimeConstraintsFromSwipeAreas(shellLoader.item);
1878+ tryCompare(shell, "waitingOnGreeter", false); // reset by greeter when ready
1879+
1880+ sessionSpy.target = findChild(shell, "greeter")
1881+ dashCommunicatorSpy.target = findInvisibleChild(shell, "dashCommunicator");
1882+
1883+ var launcher = findChild(shell, "launcher");
1884+ launcherShowDashHomeSpy.target = launcher;
1885+
1886+ var panel = findChild(launcher, "launcherPanel");
1887+ verify(!!panel);
1888+
1889+ panel.dismissTimer = fakeDismissTimer;
1890+
1891+ waitForGreeterToStabilize();
1892+
1893+ // from StageTestCase
1894+ topLevelSurfaceList = findInvisibleChild(shell, "topLevelSurfaceList");
1895+ verify(topLevelSurfaceList);
1896+ stage = findChild(shell, "stage");
1897+ }
1898+
1899+ function waitForGreeterToStabilize() {
1900+ var greeter = findChild(shell, "greeter");
1901+ verify(greeter);
1902+
1903+ var loginList = findChild(greeter, "loginList", 0 /* timeout */);
1904+ // Only present in WideView
1905+ if (loginList) {
1906+ var userList = findChild(loginList, "userList");
1907+ verify(userList);
1908+ tryCompare(userList, "movingInternally", false);
1909+ }
1910+ }
1911+
1912+ function tearDown() {
1913+ launcherShowDashHomeSpy.target = null;
1914+
1915+ shellLoader.itemDestroyed = false;
1916+
1917+ shellLoader.active = false;
1918+
1919+ tryCompare(shellLoader, "status", Loader.Null);
1920+ tryCompare(shellLoader, "item", null);
1921+ // Loader.status might be Loader.Null and Loader.item might be null but the Loader
1922+ // item might still be alive. So if we set Loader.active back to true
1923+ // again right now we will get the very same Shell instance back. So no reload
1924+ // actually took place. Likely because Loader waits until the next event loop
1925+ // iteration to do its work. So to ensure the reload, we will wait until the
1926+ // Shell instance gets destroyed.
1927+ tryCompare(shellLoader, "itemDestroyed", true);
1928+
1929+ setLightDMMockMode("single"); // back to the default value
1930+
1931+ AccountsService.demoEdges = false;
1932+ AccountsService.demoEdgesCompleted = [];
1933+ AccountsService.backgroundFile = "";
1934+ Wizard.System.wizardEnabled = false;
1935+ shellLoader.mode = "shell";
1936+
1937+ // kill all (fake) running apps
1938+ killApps();
1939+
1940+ LightDM.Greeter.authenticate(""); // reset greeter
1941+
1942+ sessionSpy.clear();
1943+ broadcastUrlSpy.clear();
1944+ broadcastHomeSpy.clear();
1945+
1946+ GSettingsController.setLifecycleExemptAppids([]);
1947+ GSettingsController.setPictureUri("");
1948+ }
1949+
1950+ function setLightDMMockMode(mode) {
1951+ LightDM.Greeter.mockMode = mode;
1952+ LightDM.Users.mockMode = mode;
1953+ }
1954+
1955+ function showGreeter() {
1956+ var greeter = findChild(shell, "greeter");
1957+ LightDM.Greeter.showGreeter();
1958+ waitForRendering(greeter);
1959+ tryCompare(greeter, "fullyShown", true);
1960+
1961+ // greeter unloads its internal components when hidden
1962+ // and reloads them when shown. Thus we have to do this
1963+ // again before interacting with it otherwise any
1964+ // SwipeAreas in there won't be easily fooled by
1965+ // fake swipes.
1966+ removeTimeConstraintsFromSwipeAreas(greeter);
1967+ }
1968+ }
1969+}
1970
1971=== modified file 'tests/qmltests/Stage/ApplicationCheckBox.qml'
1972--- tests/qmltests/Stage/ApplicationCheckBox.qml 2017-01-26 11:10:01 +0000
1973+++ tests/qmltests/Stage/ApplicationCheckBox.qml 2017-02-10 14:04:06 +0000
1974@@ -30,8 +30,6 @@
1975 }
1976 }
1977
1978- enabled: appId !== "unity8-dash"
1979-
1980 onCheckedChanged: {
1981 if (d.bindGuard) { return; }
1982 d.bindGuard = true;
1983
1984=== modified file 'tests/utils/modules/Unity/Test/MouseTouchEmulationCheckbox.qml'
1985--- tests/utils/modules/Unity/Test/MouseTouchEmulationCheckbox.qml 2016-10-28 13:11:53 +0000
1986+++ tests/utils/modules/Unity/Test/MouseTouchEmulationCheckbox.qml 2017-02-10 14:04:06 +0000
1987@@ -35,6 +35,7 @@
1988 id: checkbox
1989 checked: true
1990 activeFocusOnPress: false
1991+ activeFocusOnTab: false
1992 }
1993 Label {
1994 id: label
1995@@ -42,6 +43,8 @@
1996 anchors.verticalCenter: parent.verticalCenter
1997 AbstractButton {
1998 anchors.fill: parent
1999+ activeFocusOnPress: false
2000+ activeFocusOnTab: false
2001 onClicked: checkbox.checked = !checkbox.checked
2002 }
2003 }

Subscribers

People subscribed via source and target branches