on xenial help sign in
Merge upstream
e1e431fAlbert Astals
debian/changelog+7
@@ -1,3 +1,10 @@
1+unity8 (7.89+14.10.20140619.2-0ubuntu1) utopic; urgency=low
2+
3+ [ Albert Astals ]
4+ * Departments support (LP: #1320847)
5+
6+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Thu, 19 Jun 2014 11:17:40 +0000
7+
1unity8 (7.89+14.10.20140616.1-0ubuntu1) utopic; urgency=low
8unity8 (7.89+14.10.20140616.1-0ubuntu1) utopic; urgency=low
2
9
3 [ Pawel Stolowski ]
10 [ Pawel Stolowski ]
debian/control+1−1
@@ -17,7 +17,7 @@ Build-Depends: cmake,
17 libpulse-dev,
17 libpulse-dev,
18 libqmenumodel-dev (>= 0.2.7),
18 libqmenumodel-dev (>= 0.2.7),
19 libqt5xmlpatterns5-dev,
19 libqt5xmlpatterns5-dev,
20− libunity-api-dev (>= 7.80.6),
20+ libunity-api-dev (>= 7.82),
21 libunity-mir-dev,
21 libunity-mir-dev,
22 libusermetricsoutput1-dev,
22 libusermetricsoutput1-dev,
23 libxcb1-dev,
23 libxcb1-dev,
plugins/Dash/listviewwithpageheader.cpp+25−5
@@ -258,6 +258,8 @@ void ListViewWithPageHeader::setHeader(QQuickItem *headerItem)
258 if (m_headerItem) {
258 if (m_headerItem) {
259 m_headerItem->setParentItem(contentItem());
259 m_headerItem->setParentItem(contentItem());
260 m_headerItem->setZ(1);
260 m_headerItem->setZ(1);
261+ m_previousHeaderImplicitHeight = m_headerItem->implicitHeight();
262+ QQuickItemPrivate::get(m_headerItem)->addItemChangeListener(this, QQuickItemPrivate::ImplicitHeight);
261 }
263 }
262 qreal newHeaderHeight = m_headerItem ? m_headerItem->height() : 0;
264 qreal newHeaderHeight = m_headerItem ? m_headerItem->height() : 0;
263 if (!m_visibleItems.isEmpty() && newHeaderHeight != oldHeaderHeight) {
265 if (!m_visibleItems.isEmpty() && newHeaderHeight != oldHeaderHeight) {
@@ -496,7 +498,15 @@ void ListViewWithPageHeader::viewportMoved(Qt::Orientations orient)
496
498
497 QQuickFlickable::viewportMoved(orient);
499 QQuickFlickable::viewportMoved(orient);
498// qDebug() << "ListViewWithPageHeader::viewportMoved" << contentY();
500// qDebug() << "ListViewWithPageHeader::viewportMoved" << contentY();
499− qreal diff = m_previousContentY - contentY();
501+ const qreal diff = m_previousContentY - contentY();
502+ adjustHeader(diff);
503+ m_previousContentY = contentY();
504+ layout();
505+ polish();
506+}
507+
508+void ListViewWithPageHeader::adjustHeader(qreal diff)
509+{
500 const bool showHeaderAnimationRunning = m_contentYAnimation->isRunning() && contentYAnimationType == ContentYAnimationShowHeader;
510 const bool showHeaderAnimationRunning = m_contentYAnimation->isRunning() && contentYAnimationType == ContentYAnimationShowHeader;
501 if (m_headerItem) {
511 if (m_headerItem) {
502 const auto oldHeaderItemShownHeight = m_headerItemShownHeight;
512 const auto oldHeaderItemShownHeight = m_headerItemShownHeight;
@@ -558,10 +568,6 @@ void ListViewWithPageHeader::viewportMoved(Qt::Orientations orient)
558 adjustMinYExtent();
568 adjustMinYExtent();
559 }
569 }
560 }
570 }
561−
562− m_previousContentY = contentY();
563− layout();
564− polish();
565}
571}
566
572
567void ListViewWithPageHeader::createDelegateModel()
573void ListViewWithPageHeader::createDelegateModel()
@@ -1071,6 +1077,20 @@ void ListViewWithPageHeader::itemGeometryChanged(QQuickItem * /*item*/, const QR
1071 }
1077 }
1072}
1078}
1073
1079
1080+void ListViewWithPageHeader::itemImplicitHeightChanged(QQuickItem *item)
1081+{
1082+ if (item == m_headerItem) {
1083+ const qreal diff = m_headerItem->implicitHeight() - m_previousHeaderImplicitHeight;
1084+ if (diff != 0) {
1085+ adjustHeader(diff);
1086+ m_previousHeaderImplicitHeight = m_headerItem->implicitHeight();
1087+ layout();
1088+ polish();
1089+ m_contentHeightDirty = true;
1090+ }
1091+ }
1092+}
1093+
1074void ListViewWithPageHeader::headerHeightChanged(qreal newHeaderHeight, qreal oldHeaderHeight, qreal oldHeaderY)
1094void ListViewWithPageHeader::headerHeightChanged(qreal newHeaderHeight, qreal oldHeaderHeight, qreal oldHeaderY)
1075{
1095{
1076 const qreal heightDiff = newHeaderHeight - oldHeaderHeight;
1096 const qreal heightDiff = newHeaderHeight - oldHeaderHeight;
plugins/Dash/listviewwithpageheader.h+3
@@ -106,6 +106,7 @@ protected:
106 void viewportMoved(Qt::Orientations orient) override;
106 void viewportMoved(Qt::Orientations orient) override;
107 qreal minYExtent() const override;
107 qreal minYExtent() const override;
108 void itemGeometryChanged(QQuickItem *item, const QRectF &newGeometry, const QRectF &oldGeometry) override;
108 void itemGeometryChanged(QQuickItem *item, const QRectF &newGeometry, const QRectF &oldGeometry) override;
109+ void itemImplicitHeightChanged(QQuickItem *item) override;
109 void updatePolish() override;
110 void updatePolish() override;
110
111
111private Q_SLOTS:
112private Q_SLOTS:
@@ -146,6 +147,7 @@ private:
146 bool removeNonVisibleItems(qreal bufferFrom, qreal bufferTo);
147 bool removeNonVisibleItems(qreal bufferFrom, qreal bufferTo);
147 ListItem *createItem(int modelIndex, bool asynchronous);
148 ListItem *createItem(int modelIndex, bool asynchronous);
148
149
150+ void adjustHeader(qreal diff);
149 void adjustMinYExtent();
151 void adjustMinYExtent();
150 void updateClipItem();
152 void updateClipItem();
151 void headerHeightChanged(qreal newHeaderHeight, qreal oldHeaderHeight, qreal oldHeaderY);
153 void headerHeightChanged(qreal newHeaderHeight, qreal oldHeaderHeight, qreal oldHeaderY);
@@ -178,6 +180,7 @@ private:
178
180
179 QQuickItem *m_headerItem;
181 QQuickItem *m_headerItem;
180 qreal m_previousContentY;
182 qreal m_previousContentY;
183+ qreal m_previousHeaderImplicitHeight;
181 qreal m_headerItemShownHeight; // The height of header shown when the header is shown outside its topmost position
184 qreal m_headerItemShownHeight; // The height of header shown when the header is shown outside its topmost position
182 // i.e. it's being shown after dragging down in the middle of the list
185 // i.e. it's being shown after dragging down in the middle of the list
183 enum ContentYAnimationType { ContentYAnimationShowHeader, ContentYAnimationMaximizeVisibleArea };
186 enum ContentYAnimationType { ContentYAnimationShowHeader, ContentYAnimationMaximizeVisibleArea };
qml/Components/PageHeader.qml+15−3
@@ -27,12 +27,13 @@ Item {
27 property ListModel searchHistory
27 property ListModel searchHistory
28 property var scope: null
28 property var scope: null
29 property alias childItem: itemContainer.children
29 property alias childItem: itemContainer.children
30+ property alias bottomItem: bottomContainer.children
30 property alias showBackButton: backButton.visible
31 property alias showBackButton: backButton.visible
31
32
32 signal backClicked
33 signal backClicked
33
34
34− height: units.gu(8.5)
35+ height: header.height + units.gu(2) + bottomContainer.height
35− implicitHeight: units.gu(8.5)
36+ implicitHeight: header.height + units.gu(2) + bottomContainer.height
36
37
37 function triggerSearch() {
38 function triggerSearch() {
38 if (searchEntryEnabled) searchField.forceActiveFocus()
39 if (searchEntryEnabled) searchField.forceActiveFocus()
@@ -404,9 +405,20 @@ Item {
404 top: header.bottom
405 top: header.bottom
405 left: parent.left
406 left: parent.left
406 right: parent.right
407 right: parent.right
407− bottom: parent.bottom
408+ bottom: bottomContainer.top
408 }
409 }
409
410
410 source: "graphics/PageHeaderBaseDivider.sci"
411 source: "graphics/PageHeaderBaseDivider.sci"
411 }
412 }
413+
414+ Item {
415+ id: bottomContainer
416+
417+ anchors {
418+ left: parent.left
419+ right: parent.right
420+ bottom: parent.bottom
421+ }
422+ height: childrenRect.height
423+ }
412}
424}
qml/Dash/DashContent.qml+9−1
@@ -151,7 +151,7 @@ Item {
151 item.previewListView = previewListView;
151 item.previewListView = previewListView;
152 item.scope = Qt.binding(function() { return scope })
152 item.scope = Qt.binding(function() { return scope })
153 item.isCurrent = Qt.binding(function() { return visible && ListView.isCurrentItem })
153 item.isCurrent = Qt.binding(function() { return visible && ListView.isCurrentItem })
154− item.tabBarHeight = dashPageHeader.implicitHeight;
154+ item.tabBarHeight = Qt.binding(function() { return dashPageHeader.implicitHeight; })
155 dashContent.scopeLoaded(item.scope.id)
155 dashContent.scopeLoaded(item.scope.id)
156 }
156 }
157 Connections {
157 Connections {
@@ -217,6 +217,14 @@ Item {
217 onTriggered: tabBar.selectionMode = false
217 onTriggered: tabBar.selectionMode = false
218 }
218 }
219 }
219 }
220+
221+ bottomItem: DashDepartments {
222+ scope: dashContentList.currentItem ? dashContentList.currentItem.theScope : null
223+ width: parent.width <= units.gu(60) ? parent.width : units.gu(40)
224+ anchors.right: parent.right
225+ windowHeight: dashContent.height
226+ windowWidth: dashContent.width
227+ }
220 }
228 }
221 }
229 }
222
230
qml/Dash/DashDepartments.qmlnew file+202
@@ -0,0 +1,202 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.2
18+import Ubuntu.Components 0.1
19+
20+AbstractButton {
21+ id: root
22+ objectName: "dashDepartments"
23+
24+ property var scope: null
25+
26+ property bool showList: false
27+
28+ readonly property var currentDepartment: scope && scope.hasDepartments ? scope.getDepartment(scope.currentDepartmentId) : null
29+
30+ property alias windowWidth: blackRect.width
31+ property alias windowHeight: blackRect.height
32+
33+ // Are we drilling down the tree or up?
34+ property bool isGoingBack: false
35+
36+ visible: root.currentDepartment != null
37+
38+ height: visible ? units.gu(5) : 0
39+
40+ onClicked: {
41+ root.showList = !root.showList;
42+ }
43+
44+ Rectangle {
45+ id: blackRect
46+ color: "black"
47+ opacity: departmentListView.currentItem && departmentListView.currentItem.visible ? 0.3 : 0
48+ Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
49+ anchors.top: departmentListView.top
50+ anchors.right: parent.right
51+ visible: opacity != 0
52+ }
53+
54+ Image {
55+ anchors {
56+ top: parent.top
57+ left: parent.left
58+ right: parent.right
59+ }
60+ fillMode: Image.Stretch
61+ source: "graphics/dash_divider_top_lightgrad.png"
62+ z: -1
63+ }
64+
65+ Image {
66+ anchors {
67+ bottom: parent.bottom
68+ left: parent.left
69+ right: parent.right
70+ }
71+ fillMode: Image.Stretch
72+ source: "graphics/dash_divider_top_darkgrad.png"
73+ z: -1
74+ }
75+
76+ Label {
77+ anchors.fill: parent
78+ anchors.margins: units.gu(2)
79+ verticalAlignment: Text.AlignVCenter
80+ text: root.currentDepartment ? root.currentDepartment.label : ""
81+ color: "gray" // TODO remove once we're a separate app
82+ }
83+
84+ Image {
85+ anchors.verticalCenter: parent.verticalCenter
86+ anchors.right: parent.right
87+ anchors.rightMargin: units.gu(2)
88+ rotation: showList ? 180 : 0
89+ source: "image://theme/dropdown-menu"
90+ sourceSize.height: parent.height - units.gu(2)
91+ sourceSize.width: units.gu(2)
92+ fillMode: Image.PreserveAspectFit
93+ }
94+
95+ // departmentListView is outside root
96+ ListView {
97+ id: departmentListView
98+ objectName: "departmentListView"
99+ orientation: ListView.Horizontal
100+ interactive: false
101+ clip: root.width != windowWidth
102+ model: ListModel {
103+ id: departmentModel
104+ // We have two roles
105+ // departmentId: the department id of the department the list represents
106+ // nullifyDepartment: overrides departmentId to be null
107+ // This is used to "clear" the delegate when going "right" on the tree
108+ }
109+ anchors {
110+ left: parent.left
111+ right: parent.right
112+ top: root.bottom
113+ }
114+ readonly property int maxHeight: (windowHeight - mapToItem(null, root.x, root.y).y) - units.gu(8)
115+ property int prevHeight: maxHeight
116+ height: currentItem ? currentItem.height : maxHeight
117+ onHeightChanged: {
118+ if (root.showList) {
119+ prevHeight = currentItem.desiredHeight;
120+ }
121+ }
122+ highlightMoveDuration: UbuntuAnimation.FastDuration
123+ delegate: DashDepartmentsList {
124+ objectName: "department" + index
125+ visible: height != 0
126+ width: departmentListView.width
127+ property real desiredHeight: {
128+ if (root.showList) {
129+ if (department && department.loaded && x == departmentListView.contentX)
130+ {
131+ return Math.min(implicitHeight, departmentListView.maxHeight);
132+ } else {
133+ return departmentListView.prevHeight;
134+ }
135+ } else {
136+ return 0;
137+ }
138+ }
139+ height: desiredHeight
140+ department: (nullifyDepartment || !scope) ? null : scope.getDepartment(departmentId)
141+ currentDepartment: root.currentDepartment
142+ onEnterDepartment: {
143+ scope.loadDepartment(newDepartmentId);
144+ // We only need to add a new item to the model
145+ // if we have children, otherwise just load it
146+ if (hasChildren) {
147+ isGoingBack = false;
148+ departmentModel.append({"departmentId": newDepartmentId, "nullifyDepartment": false});
149+ departmentListView.currentIndex++;
150+ } else {
151+ showList = false;
152+ }
153+ }
154+ onGoBackToParentClicked: {
155+ scope.loadDepartment(department.parentDepartmentId);
156+ isGoingBack = true;
157+ departmentModel.setProperty(departmentListView.currentIndex - 1, "nullifyDepartment", false);
158+ departmentListView.currentIndex--;
159+ }
160+ onAllDepartmentClicked: {
161+ showList = false;
162+ if (root.currentDepartment.parentDepartmentId == department.departmentId) {
163+ // For leaves we have to go to the parent too
164+ scope.loadDepartment(root.currentDepartment.parentDepartmentId);
165+ }
166+ }
167+ }
168+ onContentXChanged: {
169+ if (contentX == width * departmentListView.currentIndex) {
170+ if (isGoingBack) {
171+ departmentModel.remove(departmentListView.currentIndex + 1);
172+ } else {
173+ departmentModel.setProperty(departmentListView.currentIndex - 1, "nullifyDepartment", true);
174+ }
175+ }
176+ }
177+ }
178+
179+ InverseMouseArea {
180+ anchors.fill: departmentListView
181+ enabled: root.showList
182+ onClicked: root.showList = false
183+ }
184+
185+ onScopeChanged: {
186+ departmentModel.clear();
187+ if (scope && scope.hasDepartments) {
188+ departmentModel.append({"departmentId": scope.currentDepartmentId, "nullifyDepartment": false});
189+ }
190+ }
191+
192+ Connections {
193+ target: scope
194+ onHasDepartmentsChanged: {
195+ if (scope.hasDepartments) {
196+ departmentModel.append({"departmentId": scope.currentDepartmentId, "nullifyDepartment": false});
197+ } else {
198+ departmentModel.clear();
199+ }
200+ }
201+ }
202+}
qml/Dash/DashDepartmentsList.qmlnew file+195
@@ -0,0 +1,195 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.2
18+import Ubuntu.Components 0.1
19+
20+Item {
21+ id: root
22+ property var department: null
23+ property var currentDepartment: null
24+ signal enterDepartment(var newDepartmentId, bool hasChildren)
25+ signal goBackToParentClicked()
26+ signal allDepartmentClicked()
27+
28+ readonly property int itemHeight: units.gu(5)
29+ implicitHeight: flickable.contentHeight
30+
31+ Rectangle {
32+ color: "white"
33+ anchors.fill: parent
34+ }
35+
36+ ActivityIndicator {
37+ id: loadingIndicator
38+ anchors.centerIn: parent
39+ running: !(department && department.loaded)
40+ }
41+ clip: true
42+
43+ Behavior on height {
44+ UbuntuNumberAnimation {
45+ id: heightAnimation
46+ duration: UbuntuAnimation.SnapDuration
47+ }
48+ }
49+
50+ Flickable {
51+ id: flickable
52+
53+ anchors.fill: parent
54+
55+ contentHeight: column.height
56+ contentWidth: width
57+
58+ Column {
59+ id: column
60+ width: parent.width
61+
62+ // TODO: check if SDK ListItems could be used here
63+ // and if not make them be useful since this is a quite common pattern
64+
65+ AbstractButton {
66+ id: backButton
67+ objectName: "backButton"
68+ width: parent.width
69+ visible: department && !department.isRoot || false
70+ height: itemHeight
71+
72+ onClicked: root.goBackToParentClicked();
73+
74+ Image {
75+ id: backImage
76+ anchors {
77+ verticalCenter: parent.verticalCenter
78+ left: parent.left
79+ leftMargin: units.gu(2)
80+ }
81+ source: "image://theme/back"
82+ sourceSize.height: parent.height - units.gu(3)
83+ sourceSize.width: units.gu(3)
84+ fillMode: Image.PreserveAspectFit
85+ }
86+
87+ Label {
88+ anchors {
89+ verticalCenter: parent.verticalCenter
90+ left: backImage.right
91+ leftMargin: units.gu(0.5)
92+ }
93+ text: department ? department.parentLabel : ""
94+ color: "gray" // TODO remove once we're a separate app
95+ }
96+
97+ Rectangle {
98+ anchors {
99+ bottom: parent.bottom
100+ left: parent.left
101+ right: parent.right
102+ leftMargin: units.gu(2)
103+ rightMargin: units.gu(2)
104+ }
105+ color: "gray"
106+ opacity: 0.2
107+ height: units.dp(1)
108+ }
109+ }
110+
111+ AbstractButton {
112+ id: allButton
113+ objectName: "allButton"
114+ width: parent.width
115+ visible: department && (!department.isRoot || (root.currentDepartment && !root.currentDepartment.isRoot && root.currentDepartment.parentDepartmentId == department.departmentId)) || false
116+ height: itemHeight
117+
118+ Label {
119+ anchors {
120+ verticalCenter: parent.verticalCenter
121+ left: parent.left
122+ leftMargin: units.gu(2)
123+ }
124+ text: department ? (department.allLabel != "" ? department.allLabel : department.label) : ""
125+ font.bold: true
126+ color: "gray" // TODO remove once we're a separate app
127+ }
128+
129+ Rectangle {
130+ anchors {
131+ bottom: parent.bottom
132+ left: parent.left
133+ right: parent.right
134+ leftMargin: units.gu(2)
135+ rightMargin: units.gu(2)
136+ }
137+ color: "gray"
138+ opacity: 0.2
139+ height: units.dp(1)
140+ }
141+
142+ onClicked: root.allDepartmentClicked();
143+ }
144+
145+ Repeater {
146+ model: department && department.loaded ? department : null
147+ clip: true
148+ delegate: AbstractButton {
149+ objectName: root.objectName + "child" + index
150+ height: root.itemHeight
151+ width: root.width
152+
153+ onClicked: root.enterDepartment(departmentId, hasChildren)
154+
155+ Label {
156+ anchors {
157+ verticalCenter: parent.verticalCenter
158+ left: parent.left
159+ leftMargin: units.gu(2)
160+ }
161+ text: label
162+ color: "gray" // TODO remove once we're a separate app
163+ }
164+
165+ Image {
166+ anchors {
167+ verticalCenter: parent.verticalCenter
168+ right: parent.right
169+ rightMargin: units.gu(2)
170+ }
171+ source: hasChildren ? "image://theme/chevron" : "graphics/tick.png"
172+ sourceSize.height: parent.height - units.gu(3)
173+ sourceSize.width: units.gu(3)
174+ fillMode: Image.PreserveAspectFit
175+ visible: hasChildren || isActive
176+ }
177+
178+ Rectangle {
179+ anchors {
180+ bottom: parent.bottom
181+ left: parent.left
182+ right: parent.right
183+ leftMargin: units.gu(2)
184+ rightMargin: units.gu(2)
185+ }
186+ color: "gray"
187+ opacity: 0.1
188+ height: units.dp(1)
189+ visible: index != department.count - 1
190+ }
191+ }
192+ }
193+ }
194+ }
195+}
qml/Dash/ScopeItem.qml+12
@@ -31,6 +31,10 @@ Item {
31 signal gotoScope(string scopeId)
31 signal gotoScope(string scopeId)
32 signal openScope(var scope)
32 signal openScope(var scope)
33
33
34+ // TODO see how much code we can share
35+ // between here and other Dash parts, it's starting to have
36+ // too much duplicated code with the DashDepartments, etc
37+
34 Item {
38 Item {
35 id: scopeViewHolder
39 id: scopeViewHolder
36
40
@@ -79,6 +83,14 @@ Item {
79 fontSize: "x-large"
83 fontSize: "x-large"
80 elide: Text.ElideRight
84 elide: Text.ElideRight
81 }
85 }
86+
87+ bottomItem: DashDepartments {
88+ scope: root.scope
89+ width: parent.width <= units.gu(60) ? parent.width : units.gu(40)
90+ anchors.right: parent.right
91+ windowHeight: root.height
92+ windowWidth: root.width
93+ }
82 }
94 }
83 }
95 }
84
96
qml/Dash/graphics/tick@30.pngnew file

Binary file not shown.

tests/mocks/Unity/CMakeLists.txt+2
@@ -22,12 +22,14 @@ set(UnityQML_SOURCES
22 fake_scope.cpp
22 fake_scope.cpp
23 fake_scopes.cpp
23 fake_scopes.cpp
24 fake_categories.cpp
24 fake_categories.cpp
25+ fake_department.cpp
25 fake_resultsmodel.cpp
26 fake_resultsmodel.cpp
26 fake_previewmodel.cpp
27 fake_previewmodel.cpp
27 fake_previewstack.cpp
28 fake_previewstack.cpp
28 fake_previewwidgetmodel.cpp
29 fake_previewwidgetmodel.cpp
29 fake_unity_plugin.cpp
30 fake_unity_plugin.cpp
30 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/CategoriesInterface.h
31 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/CategoriesInterface.h
32+ ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/DepartmentInterface.h
31 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/PreviewModelInterface.h
33 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/PreviewModelInterface.h
32 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/PreviewStackInterface.h
34 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/PreviewStackInterface.h
33 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/PreviewWidgetModelInterface.h
35 ${SCOPES_API_INCLUDEDIR}/unity/shell/scopes/PreviewWidgetModelInterface.h
tests/mocks/Unity/fake_department.cppnew file+127
@@ -0,0 +1,127 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "fake_department.h"
18+
19+#include "fake_scope.h"
20+
21+#include <QTimer>
22+
23+Department::Department(const QString& departmentId, const QString& label, const QString& allLabel, const QString& parentId, const QString& parentLabel, Scope* scope)
24+ : m_departmentId(departmentId)
25+ , m_label(label)
26+ , m_allLabel(allLabel)
27+ , m_parentId(parentId)
28+ , m_parentLabel(parentLabel)
29+ , m_loaded(false)
30+ , m_scope(scope)
31+{
32+ QTimer::singleShot(1500, this, SLOT(slotLoaded()));
33+ connect(scope, SIGNAL(currentDepartmentIdChanged()), this, SLOT(slotCurrentDepartmentChanged()));
34+}
35+
36+QString Department::departmentId() const
37+{
38+ return m_departmentId;
39+}
40+
41+QString Department::label() const
42+{
43+ return m_label;
44+}
45+
46+QString Department::allLabel() const
47+{
48+ return m_allLabel;
49+}
50+
51+QString Department::parentDepartmentId() const
52+{
53+ return m_parentId;
54+}
55+
56+QString Department::parentLabel() const
57+{
58+ return m_parentLabel;
59+}
60+
61+void Department::slotLoaded()
62+{
63+ m_loaded = true;
64+ Q_EMIT loadedChanged();
65+}
66+
67+bool Department::loaded() const
68+{
69+ return m_loaded;
70+}
71+
72+int Department::count() const
73+{
74+ return rowCount();
75+}
76+
77+bool Department::isRoot() const
78+{
79+ return m_departmentId == "root";
80+}
81+
82+int Department::rowCount(const QModelIndex & /*parent*/) const
83+{
84+ if (!m_loaded || m_departmentId.startsWith("child") || m_departmentId == "middle3")
85+ return 0;
86+ else
87+ return 8;
88+}
89+
90+QHash<int, QByteArray> Department::roleNames() const
91+{
92+ QHash<int, QByteArray> res;
93+ res[RoleDepartmentId] = "departmentId";
94+ res[RoleLabel] = "label";
95+ res[RoleHasChildren] = "hasChildren";
96+ res[RoleIsActive] = "isActive";
97+ return res;
98+}
99+
100+QVariant Department::data(const QModelIndex &index, int role) const
101+{
102+ switch (role) {
103+ case RoleDepartmentId:
104+ if (m_departmentId == "root")
105+ return QString("middle%1").arg(index.row());
106+ else if (m_departmentId.startsWith("middle"))
107+ return QString("child%1%2").arg(m_departmentId).arg(index.row());
108+ break;
109+ case RoleLabel:
110+ return QString("%1Child%2").arg(m_departmentId).arg(index.row());
111+ break;
112+ case RoleHasChildren:
113+ return m_departmentId == "root" && index.row() != 3;
114+ break;
115+ case RoleIsActive:
116+ return m_scope->currentDepartmentId() == data(index, RoleDepartmentId);
117+ break;
118+ }
119+ return QVariant();
120+}
121+
122+void Department::slotCurrentDepartmentChanged()
123+{
124+ // This is wasteful, should only emit it if really something changed in this
125+ // deparment, but this is a mock, so no need to optimize
126+ Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), QVector<int>() << RoleIsActive);
127+}
tests/mocks/Unity/fake_department.hnew file+61
@@ -0,0 +1,61 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef FAKE_DEPARTMENT_H
18+#define FAKE_DEPARTMENT_H
19+
20+#include <unity/shell/scopes/DepartmentInterface.h>
21+
22+class Scope;
23+
24+class Department : public unity::shell::scopes::DepartmentInterface
25+{
26+ Q_OBJECT
27+
28+public:
29+ Department(const QString& departmentId, const QString& label, const QString& allLabel, const QString& parentId, const QString& parentLabel, Scope* scope);
30+
31+ QString departmentId() const override;
32+ QString label() const override;
33+ QString allLabel() const override;
34+ QString parentDepartmentId() const override;
35+ QString parentLabel() const override;
36+ bool loaded() const override;
37+ int count() const override;
38+ bool isRoot() const override;
39+
40+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
41+ QHash<int, QByteArray> roleNames() const;
42+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
43+
44+public Q_SLOTS:
45+ void slotCurrentDepartmentChanged();
46+
47+private Q_SLOTS:
48+ void slotLoaded();
49+
50+private:
51+ QString m_departmentId;
52+ QString m_label;
53+ QString m_allLabel;
54+ QString m_parentId;
55+ QString m_parentLabel;
56+ bool m_loaded;
57+ QString m_currentDepartment;
58+ Scope *m_scope;
59+};
60+
61+#endif // FAKE_DEPARTMENT_H
tests/mocks/Unity/fake_scope.cpp+36−1
@@ -15,6 +15,7 @@
15 */
15 */
16
16
17#include "fake_scope.h"
17#include "fake_scope.h"
18+#include "fake_department.h"
18#include "fake_resultsmodel.h"
19#include "fake_resultsmodel.h"
19
20
20Scope::Scope(QObject* parent) : Scope(QString(), QString(), false, parent)
21Scope::Scope(QObject* parent) : Scope(QString(), QString(), false, parent)
@@ -28,6 +29,7 @@ Scope::Scope(QString const& id, QString const& name, bool visible, QObject* pare
28 , m_visible(visible)
29 , m_visible(visible)
29 , m_searching(false)
30 , m_searching(false)
30 , m_isActive(false)
31 , m_isActive(false)
32+ , m_currentDeparment("root")
31 , m_previewRendererName("preview-generic")
33 , m_previewRendererName("preview-generic")
32 , m_categories(new Categories(20, this))
34 , m_categories(new Categories(20, this))
33{
35{
@@ -102,7 +104,7 @@ void Scope::setFormFactor(const QString &str) {
102void Scope::setActive(const bool active) {
104void Scope::setActive(const bool active) {
103 if (active != m_isActive) {
105 if (active != m_isActive) {
104 m_isActive = active;
106 m_isActive = active;
105− Q_EMIT isActiveChanged(active);
107+ Q_EMIT isActiveChanged();
106 }
108 }
107}
109}
108
110
@@ -142,3 +144,36 @@ void Scope::closeScope(unity::shell::scopes::ScopeInterface* /*scope*/)
142{
144{
143 qFatal("Scope::closeScope is not implemented");
145 qFatal("Scope::closeScope is not implemented");
144}
146}
147+
148+QString Scope::currentDepartmentId() const
149+{
150+ return m_currentDeparment;
151+}
152+
153+bool Scope::hasDepartments() const
154+{
155+ return true;
156+}
157+
158+unity::shell::scopes::DepartmentInterface* Scope::getDepartment(const QString& id)
159+{
160+ if (id.isEmpty())
161+ return nullptr;
162+
163+ QString parentId;
164+ QString parentLabel;
165+ if (id.startsWith("middle")) {
166+ parentId = "root";
167+ parentLabel = "root";
168+ } else if (id.startsWith("child")) {
169+ parentId = id.mid(5, 7);
170+ parentLabel = parentId;
171+ }
172+ return new Department(id, id, "all"+id, parentId, parentLabel, this);
173+}
174+
175+void Scope::loadDepartment(const QString& id)
176+{
177+ m_currentDeparment = id;
178+ Q_EMIT currentDepartmentIdChanged();
179+}
tests/mocks/Unity/fake_scope.h+6
@@ -59,6 +59,11 @@ public:
59 Q_INVOKABLE void cancelActivation() override;
59 Q_INVOKABLE void cancelActivation() override;
60 Q_INVOKABLE void closeScope(unity::shell::scopes::ScopeInterface* scope) override;
60 Q_INVOKABLE void closeScope(unity::shell::scopes::ScopeInterface* scope) override;
61
61
62+ QString currentDepartmentId() const override;
63+ bool hasDepartments() const override;
64+ Q_INVOKABLE unity::shell::scopes::DepartmentInterface* getDepartment(const QString& id) override;
65+ Q_INVOKABLE void loadDepartment(const QString& id) override;
66+
62protected:
67protected:
63
68
64 QString m_id;
69 QString m_id;
@@ -71,6 +76,7 @@ protected:
71 bool m_visible;
76 bool m_visible;
72 bool m_searching;
77 bool m_searching;
73 bool m_isActive;
78 bool m_isActive;
79+ QString m_currentDeparment;
74
80
75 QString m_previewRendererName;
81 QString m_previewRendererName;
76
82
tests/mocks/Unity/fake_scopes.cpp+2−2
@@ -49,7 +49,7 @@ void Scopes::updateScopes()
49
49
50 if (!m_loaded) {
50 if (!m_loaded) {
51 m_loaded = true;
51 m_loaded = true;
52− Q_EMIT loadedChanged(m_loaded);
52+ Q_EMIT loadedChanged();
53 }
53 }
54}
54}
55
55
@@ -65,7 +65,7 @@ void Scopes::clear()
65
65
66 if (m_loaded) {
66 if (m_loaded) {
67 m_loaded = false;
67 m_loaded = false;
68− Q_EMIT loadedChanged(m_loaded);
68+ Q_EMIT loadedChanged();
69 }
69 }
70}
70}
71
71
tests/mocks/Unity/fake_unity_plugin.cpp+2
@@ -22,6 +22,7 @@
22// local
22// local
23#include "fake_scopes.h"
23#include "fake_scopes.h"
24#include "fake_categories.h"
24#include "fake_categories.h"
25+#include "fake_department.h"
25#include "fake_previewmodel.h"
26#include "fake_previewmodel.h"
26#include "fake_previewwidgetmodel.h"
27#include "fake_previewwidgetmodel.h"
27#include "fake_resultsmodel.h"
28#include "fake_resultsmodel.h"
@@ -42,6 +43,7 @@ void FakeUnityPlugin::registerTypes(const char *uri)
42 qmlRegisterType<Scopes>(uri, 0, 2, "Scopes");
43 qmlRegisterType<Scopes>(uri, 0, 2, "Scopes");
43 qmlRegisterType<Scope>(uri, 0, 2, "MockScope");
44 qmlRegisterType<Scope>(uri, 0, 2, "MockScope");
44 qmlRegisterUncreatableType<unity::shell::scopes::ScopeInterface>(uri, 0, 2, "Scope", "Can't create Scope object in QML.");
45 qmlRegisterUncreatableType<unity::shell::scopes::ScopeInterface>(uri, 0, 2, "Scope", "Can't create Scope object in QML.");
46+ qmlRegisterUncreatableType<unity::shell::scopes::DepartmentInterface>(uri, 0, 2, "Department", "Can't create Department object in QML.");
45 qmlRegisterUncreatableType<unity::shell::scopes::CategoriesInterface>(uri, 0, 2, "Categories", "Can't create Categories object in QML.");
47 qmlRegisterUncreatableType<unity::shell::scopes::CategoriesInterface>(uri, 0, 2, "Categories", "Can't create Categories object in QML.");
46 qmlRegisterUncreatableType<ResultsModel>(uri, 0, 2, "ResultsModel", "Can't create ResultsModel object in QML.");
48 qmlRegisterUncreatableType<ResultsModel>(uri, 0, 2, "ResultsModel", "Can't create ResultsModel object in QML.");
47 qmlRegisterType<PreviewModel>(uri, 0, 2, "FakePreviewModel");
49 qmlRegisterType<PreviewModel>(uri, 0, 2, "FakePreviewModel");
tests/qmltests/Dash/tst_DashContent.qml+114−3
@@ -124,13 +124,13 @@ Item {
124
124
125 waitForRendering(categoryListView);
125 waitForRendering(categoryListView);
126
126
127− categoryListView.contentY = units.gu(11);
127+ categoryListView.contentY = units.gu(15);
128− console.log("contentY", categoryListView.contentY);
128+ tryCompare(categoryListView, "contentY", units.gu(15));
129
129
130 var startX = dashContentList.width/2;
130 var startX = dashContentList.width/2;
131 var startY = dashContentList.height/2;
131 var startY = dashContentList.height/2;
132 touchFlick(dashContentList, startX - units.gu(2), startY, startX, startY);
132 touchFlick(dashContentList, startX - units.gu(2), startY, startX, startY);
133− tryCompare(categoryListView, "contentY", units.gu(11) - categoryListView.pageHeader.height);
133+ tryCompare(categoryListView, "contentY", units.gu(15) - categoryListView.pageHeader.height);
134 }
134 }
135
135
136 function test_set_current_scope_reset() {
136 function test_set_current_scope_reset() {
@@ -407,5 +407,116 @@ Item {
407 var carouselLV = findChild(dashCategory1, "listView");
407 var carouselLV = findChild(dashCategory1, "listView");
408 verify(carouselLV.tileWidth / carouselLV.tileHeight == cardTool.components["art"]["aspect-ratio"]);
408 verify(carouselLV.tileWidth / carouselLV.tileHeight == cardTool.components["art"]["aspect-ratio"]);
409 }
409 }
410+
411+ function test_departments() {
412+ var dashDepartments = findChild(dashContent, "dashDepartments");
413+ compare(dashDepartments.visible, true);
414+ compare(dashDepartments.showList, false);
415+ waitForRendering(dashDepartments);
416+ mouseClick(dashDepartments, 0, 0);
417+ compare(dashDepartments.showList, true);
418+
419+ var departmentListView = findChild(dashDepartments, "departmentListView");
420+ compare(departmentListView.count, 1);
421+ tryCompare(departmentListView.currentItem.department, "loaded", true);
422+
423+ waitForRendering(departmentListView);
424+ waitForRendering(departmentListView.currentItem);
425+
426+ var allButton = findChild(dashDepartments, "allButton");
427+ compare(allButton.visible, false);
428+
429+ var department = findChild(dashDepartments, "department0child3");
430+ mouseClick(department, 0, 0);
431+ compare(dashDepartments.showList, false);
432+ tryCompare(dashDepartments.currentDepartment, "departmentId", "middle3");
433+ tryCompare(departmentListView.currentItem.department, "departmentId", "root");
434+
435+ mouseClick(dashDepartments, 0, 0);
436+ compare(dashDepartments.showList, true);
437+ waitForRendering(departmentListView);
438+ waitForRendering(departmentListView.currentItem);
439+ compare(allButton.visible, true);
440+
441+ mouseClick(allButton, 0, 0);
442+ compare(dashDepartments.showList, false);
443+ tryCompare(dashDepartments.currentDepartment, "departmentId", "root");
444+ tryCompare(departmentListView.currentItem.department, "departmentId", "root");
445+
446+ mouseClick(dashDepartments, 0, 0);
447+ compare(dashDepartments.showList, true);
448+ waitForRendering(departmentListView);
449+ waitForRendering(departmentListView.currentItem);
450+ compare(allButton.visible, false);
451+
452+ department = findChild(dashDepartments, "department0child2");
453+ mouseClick(department, 0, 0);
454+ compare(dashDepartments.showList, true);
455+ tryCompare(dashDepartments.currentDepartment, "departmentId", "middle2");
456+ tryCompare(departmentListView.currentItem.department, "departmentId", "middle2");
457+
458+ var departmentList1 = findChild(dashDepartments, "department1");
459+ allButton = findChild(departmentList1, "allButton");
460+ var backButton = findChild(findChild(departmentList1, "department1"), "backButton");
461+ compare(allButton.visible, true);
462+ compare(backButton.visible, true);
463+
464+ mouseClick(allButton, 0, 0);
465+ compare(dashDepartments.showList, false);
466+ tryCompare(dashDepartments.currentDepartment, "departmentId", "middle2");
467+ tryCompare(departmentListView.currentItem.department, "departmentId", "middle2");
468+
469+ mouseClick(dashDepartments, 0, 0);
470+ compare(dashDepartments.showList, true);
471+ waitForRendering(departmentListView);
472+ waitForRendering(departmentListView.currentItem);
473+ compare(allButton.visible, true);
474+ compare(backButton.visible, true);
475+
476+ tryCompare(departmentList1.department, "loaded", true);
477+ department = findChild(dashDepartments, "department1child2");
478+ mouseClick(department, 0, 0);
479+ compare(dashDepartments.showList, false);
480+ tryCompare(dashDepartments.currentDepartment, "departmentId", "childmiddle22");
481+ tryCompare(departmentListView.currentItem.department, "departmentId", "middle2");
482+
483+ mouseClick(dashDepartments, 0, 0);
484+ compare(dashDepartments.showList, true);
485+ waitForRendering(departmentListView);
486+ waitForRendering(departmentListView.currentItem);
487+
488+ tryCompare(departmentList1.department, "loaded", true);
489+ department = findChild(dashDepartments, "department1child3");
490+ mouseClick(department, 0, 0);
491+ compare(dashDepartments.showList, false);
492+ tryCompare(dashDepartments.currentDepartment, "departmentId", "childmiddle23");
493+ tryCompare(departmentListView.currentItem.department, "departmentId", "middle2");
494+
495+ mouseClick(dashDepartments, 0, 0);
496+ compare(dashDepartments.showList, true);
497+ waitForRendering(departmentListView);
498+ waitForRendering(departmentListView.currentItem);
499+ mouseClick(backButton, 0, 0);
500+
501+ tryCompare(dashDepartments.currentDepartment, "departmentId", "root");
502+ tryCompare(departmentListView.currentItem.department, "departmentId", "root");
503+ compare(dashDepartments.showList, true);
504+ mouseClick(dashDepartments, 0, 0);
505+ compare(dashDepartments.showList, false);
506+
507+ mouseClick(dashDepartments, 0, 0);
508+ compare(dashDepartments.showList, true);
509+ tryCompare(departmentListView.currentItem.department, "loaded", true);
510+ department = findChild(dashDepartments, "department0child2");
511+ mouseClick(department, 0, 0);
512+ compare(dashDepartments.showList, true);
513+ departmentList1 = findChild(dashDepartments, "department1");
514+ compare(departmentList1.department.loaded, false);
515+ tryCompare(dashDepartments.currentDepartment, "departmentId", "middle2");
516+ allButton = findChild(departmentList1, "allButton");
517+ tryCompare(dashDepartments.currentDepartment, "departmentId", "middle2");
518+ mouseClick(allButton, 0, 0);
519+ tryCompare(dashDepartments.currentDepartment, "departmentId", "middle2");
520+ }
410 }
521 }
411}
522}