Merge lp:~lukas-kde/unity8/dashboard into lp:unity8
- dashboard
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Unity Team | Pending | ||
Review via email: mp+315162@code.launchpad.net |
Commit message
Description of the change
WIP: Unity 8 Dashboard prototype & playground
- 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
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 | } |