on xenial help sign in
Add TabBar to the Dash header navigation

Changes this comes with: * DashBar at the bottom is gone * PageHeader doesn't have a Label anymore, it has the childItem property where you add which thing it has to contain * New: PageHeaderLabel mimics the old behaviour of PageHeader * The header of the LVWPH of GenericScopeView is now fake and only used for positioning. There is a single global floating header in DashContent (which is a PageHeader with a TabBar as childItem) * The GenericScopeView previewLoader and OpenEffect have been also moved to the DashContent so that the openEffect includes the floating header in the "animation" . Approved by PS Jenkins bot, Michael Zanetti.

commit99cc5b529735a8473d777175bd55e7e34e6766c9 authorAlbert Astals <albert.astals@canonical.com> dateTue 7 Jan 2014, 19:25 (13 years ago) parent04de85d7f38d22
23 files changed+640−633
plugins/Utils/qsortfilterproxymodelqml.cpp+12 plugins/Utils/qsortfilterproxymodelqml.h+1 qml/Components/PageHeader.qml+13−37 qml/Components/PageHeaderLabel.qml+41 qml/Dash/Dash.qml−18 qml/Dash/DashApps.qml−3 qml/Dash/DashBar.qml−128 qml/Dash/DashContent.qml+123−62 qml/Dash/DashContentOpenEffect.qml+57 qml/Dash/DashContentTabBarStyle.qml+21 qml/Dash/GenericScopeView.qml+28−228 qml/Dash/PreviewListView.qml+212 qml/Dash/graphics/tabbarchevron@27.png qml/Hud/HudParametrizedActionsPage.qml+1−1 tests/mocks/Unity/fake_scopes.cpp+3 tests/mocks/Unity/fake_scopes.h+2−1 tests/qmltests/CMakeLists.txt+1−2 tests/qmltests/Components/tst_PageHeader.qml → tests/qmltests/Components/tst_PageHeaderLabel.qml+2−2 tests/qmltests/Dash/qml/FakeScopeView.qml+1 tests/qmltests/Dash/tst_Dash.qml+6−8 tests/qmltests/Dash/tst_DashBar.qml−111 tests/qmltests/Dash/tst_DashContent.qml+67−4 tests/qmltests/Dash/tst_GenericScopeView.qml+49−28
plugins/Utils/qsortfilterproxymodelqml.cpp+12
@@ -68,6 +68,18 @@ QSortFilterProxyModelQML::setModel(QAbstractItemModel *itemModel)
6868 }
6969}
7070
71+QVariantMap
72+QSortFilterProxyModelQML::get(int row)
73+{
74+ QVariantMap res;
75+ const QHash<int, QByteArray> roles = roleNames();
76+ auto it = roles.begin();
77+ for ( ; it != roles.end(); ++it) {
78+ res[*it] = data(row, it.key());
79+ }
80+ return res;
81+}
82+
7183QVariant
7284QSortFilterProxyModelQML::data(int row, int role)
7385{
plugins/Utils/qsortfilterproxymodelqml.h+1
@@ -31,6 +31,7 @@ class QSortFilterProxyModelQML : public QSortFilterProxyModel
3131public:
3232 explicit QSortFilterProxyModelQML(QObject *parent = 0);
3333
34+ Q_INVOKABLE QVariantMap get(int row); // Use with caution, it can be slow to query all the roles
3435 Q_INVOKABLE QVariant data(int row, int role);
3536 Q_INVOKABLE int count();
3637 Q_INVOKABLE int findFirst(int role, const QVariant& value) const;
qml/Components/PageHeader.qml+13−37
@@ -21,17 +21,12 @@ import Ubuntu.Components.ListItems 0.1 as ListItem
2121import Unity 0.1
2222
2323Item {
24− /*!
25− \preliminary
26− The text that is shown inside the Page Header
27− \qmlproperty string text
28− */
29− property alias text: label.text
30−
24+ id: root
3125 property bool searchEntryEnabled: false
3226 property alias searchQuery: searchField.text
3327 property ListModel searchHistory
3428 property Scope scope
29+ property alias childItem: itemContainer.children
3530
3631 height: units.gu(8.5)
3732 implicitHeight: units.gu(8.5)
@@ -82,29 +77,10 @@ Item {
8277 height: childrenRect.height
8378
8479 Item {
85− id: textContainer
80+ id: itemContainer
8681
87− width: header.width
82+ width: searchContainer.narrowMode ? header.width : header.width - searchContainer.width
8883 height: header.height
89−
90− Label {
91− id: label
92− anchors {
93− left: parent.left
94− leftMargin: units.gu(2)
95− right: parent.right
96− verticalCenter: parent.verticalCenter
97− }
98−
99− color: Theme.palette.selected.backgroundText
100− opacity: 0.8
101− font.family: "Ubuntu"
102− font.weight: Font.Light
103− fontSize: "x-large"
104− elide: Text.ElideRight
105− style: Text.Raised
106− styleColor: "black"
107− }
10884 }
10985
11086 Item {
@@ -115,12 +91,12 @@ Item {
11591 property bool popoverShouldOpen: false
11692 property bool popoverShouldClose: false
11793
118− property bool narrowMode: parent.width < label.contentWidth + units.gu(50)
94+ property bool narrowMode: parent.width < units.gu(80)
11995
12096 property bool active: searchField.text != "" || searchField.activeFocus
12197 property var popover: null
12298
123− anchors.right: textContainer.right
99+ anchors.right: headerContainer.right
124100 height: header.height
125101
126102 state:
@@ -255,13 +231,13 @@ Item {
255231 states: [
256232 State {
257233 name: "wide"
258− AnchorChanges { target: textContainer; anchors.top: headerContainer.top }
259− AnchorChanges { target: searchContainer; anchors.left: undefined; anchors.top: textContainer.top }
234+ AnchorChanges { target: itemContainer; anchors.top: headerContainer.top }
235+ AnchorChanges { target: searchContainer; anchors.left: undefined; anchors.top: itemContainer.top }
260236 },
261237 State {
262238 name: "narrow"
263239 PropertyChanges { target: searchField; highlighted: true }
264− AnchorChanges { target: textContainer; anchors.top: searchContainer.bottom }
240+ AnchorChanges { target: itemContainer; anchors.top: searchContainer.bottom }
265241 AnchorChanges { target: searchContainer; anchors.left: headerContainer.left; anchors.top: headerContainer.top }
266242 },
267243 State {
@@ -299,7 +275,7 @@ Item {
299275 ParallelAnimation {
300276 NumberAnimation { targets: [searchContainer, searchField]; property: "width"; duration: 200; easing.type: Easing.InOutQuad }
301277 PropertyAction { target: primaryImage; property: "source" }
302− AnchorAnimation { targets: [searchContainer, textContainer]; duration: 200; easing.type: Easing.InOutQuad }
278+ AnchorAnimation { targets: [searchContainer, itemContainer]; duration: 200; easing.type: Easing.InOutQuad }
303279 }
304280 ScriptAction { script: if (searchContainer.popoverShouldOpen) { searchContainer.openPopover(); } }
305281 }
@@ -308,14 +284,14 @@ Item {
308284 to: "inactive"
309285 ScriptAction { script: if (searchContainer.popoverShouldClose) { searchContainer.closePopover(); } }
310286 NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.InOutQuad }
311− AnchorAnimation { targets: [searchContainer, textContainer]; duration: 200; easing.type: Easing.InOutQuad }
287+ AnchorAnimation { targets: [searchContainer, itemContainer]; duration: 200; easing.type: Easing.InOutQuad }
312288 },
313289 Transition {
314290 to: "narrowActive"
315291 SequentialAnimation {
316292 ParallelAnimation {
317293 NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.OutQuad }
318− AnchorAnimation { targets: [searchContainer, textContainer]; duration: 200; easing.type: Easing.InOutQuad }
294+ AnchorAnimation { targets: [searchContainer, itemContainer]; duration: 200; easing.type: Easing.InOutQuad }
319295 }
320296 ScriptAction { script: if (searchContainer.popoverShouldOpen) { searchContainer.openPopover(); } }
321297 }
@@ -324,7 +300,7 @@ Item {
324300 to: "narrowInactive"
325301 ScriptAction { script: if (searchContainer.popoverShouldClose) { searchContainer.closePopover(); } }
326302 NumberAnimation { targets: [searchContainer, searchField] ; property: "width"; duration: 200; easing.type: Easing.OutQuad }
327− AnchorAnimation { targets: [searchContainer, textContainer]; duration: 200; easing.type: Easing.InOutQuad }
303+ AnchorAnimation { targets: [searchContainer, itemContainer]; duration: 200; easing.type: Easing.InOutQuad }
328304 }
329305 ]
330306
qml/Components/PageHeaderLabel.qmlnew file+41
@@ -0,0 +1,41 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import Ubuntu.Components 0.1
19+
20+PageHeader {
21+ property alias text: label.text
22+
23+ childItem: Label {
24+ id: label
25+ anchors {
26+ left: parent.left
27+ leftMargin: units.gu(2)
28+ right: parent.right
29+ verticalCenter: parent.verticalCenter
30+ }
31+
32+ color: Theme.palette.selected.backgroundText
33+ opacity: 0.8
34+ font.family: "Ubuntu"
35+ font.weight: Font.Light
36+ fontSize: "x-large"
37+ elide: Text.ElideRight
38+ style: Text.Raised
39+ styleColor: "black"
40+ }
41+}
qml/Dash/Dash.qml−18
@@ -66,11 +66,6 @@ Showable {
6666 model: filteredScopes
6767 scopes: scopes
6868 searchHistory: dash.searchHistory
69− onMovementStarted: dashbar.startNavigation()
70− onMovementEnded: dashbar.stopNavigation()
71− onContentFlickStarted: dashbar.finishNavigation()
72− onContentEndReached: dashbar.finishNavigation()
73− onPreviewShown: dashbar.finishNavigation()
7469 onScopeLoaded: {
7570 if (scopeId == dash.showScopeOnLoaded) {
7671 dash.setCurrentScope(scopeId, false, false)
@@ -80,17 +75,4 @@ Showable {
8075 scale: dash.contentScale
8176 clip: scale != 1.0
8277 }
83−
84− DashBar {
85− id: dashbar
86− objectName: "dashbar"
87− anchors.bottom: parent.bottom
88− anchors.left: parent.left
89− anchors.right: parent.right
90− model: filteredScopes
91− currentIndex: dashContent.currentIndex
92− onItemSelected: dashContent.setCurrentScopeAtIndex(index, true, false)
93− opacity: dash.contentScale == 1.0 ? 1.0 : 0.0
94− Behavior on opacity { NumberAnimation { easing.type: Easing.OutQuad; duration: 150 } }
95− }
9678}
qml/Dash/DashApps.qml−3
@@ -29,9 +29,6 @@ GenericScopeView {
2929 property var mainStageApplicationsModel: shell.applicationManager.mainStageApplications
3030 property var sideStageApplicationModel: shell.applicationManager.sideStageApplications
3131
32− //FIXME an alias to itself shouldn't be required. More than likely a Qt bug. Try removing with newer Qt.
33− property alias searchHistory: scopeView.searchHistory
34−
3532 ListModel {
3633 id: dummyVisibilityModifier
3734
qml/Dash/DashBar.qmldeleted−128
@@ -1,128 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import Ubuntu.Components 0.1
19−import Unity 0.1
20−
21−Item {
22− id: dashBar
23−
24− property var model
25− property alias currentIndex: row.currentIndex
26−
27− property int lineHeight: units.dp(2)
28− property int itemSize: units.gu(7)
29− property int iconSize: units.gu(3.5)
30−
31− signal itemSelected(int index)
32−
33− width: units.gu(40)
34− height: units.gu(6)
35−
36− function startNavigation() {
37− timeout.stop()
38− panel.open()
39− }
40−
41− function stopNavigation() {
42− timeout.restart()
43− }
44−
45− function finishNavigation() {
46− panel.close()
47− }
48−
49− Timer {
50− id: timeout
51− interval: 1500
52− running: false
53− repeat: false
54− onTriggered: finishNavigation()
55− }
56−
57− Panel {
58− id: panel
59− objectName: "panel"
60− anchors.fill: parent
61−
62− locked: true // TODO: remove this when lp bug #1179569 will be fixed
63−
64− Rectangle {
65− color: "black"
66− anchors.fill: parent
67−
68− ListView {
69− id: row
70− objectName: "row"
71− model: dashBar.model
72− orientation: ListView.Horizontal
73− width: Math.min(Math.max(dashBar.width/2, units.gu(40)), count * itemSize)
74− height: parent.height
75− anchors.horizontalCenter: parent.horizontalCenter
76− onMovingChanged: if (moving) { timeout.stop() } else { timeout.restart() }
77− interactive: visibleArea.widthRatio < 1 && panel.opened
78− highlightFollowsCurrentItem: false
79−
80− onCurrentItemChanged: {
81− highlightLine.width = currentItem.width
82− highlightLine.x = x + currentItem.x
83− }
84−
85− delegate:
86− // FIXME: Use Panel's integrated trigger() once that works again.
87− MouseArea {
88− width: itemSize
89− height: dashBar.height
90− anchors.top: parent.top
91−
92− onClicked: {
93− dashBar.itemSelected(index)
94− timeout.restart()
95− }
96−
97− Image {
98− anchors.centerIn: parent
99− source: scope.iconHint
100− sourceSize { width: iconSize; height: iconSize }
101− // opacity: index == currentIndex ? 1 : 1 // same opacity for now
102− }
103− }
104− }
105− }
106− }
107−
108− Rectangle {
109− anchors {
110− left: parent.left
111− right: parent.right
112− bottom: parent.bottom
113− }
114− height: dashBar.lineHeight
115− color: "black"
116−
117− Rectangle {
118− id: highlightLine
119− color: Theme.palette.selected.foreground
120− height: parent.height
121− anchors.bottom: parent.bottom
122− z: 1
123−
124− Behavior on x {NumberAnimation { duration: 150; easing.type: Easing.OutCubic}}
125− Behavior on width {NumberAnimation { duration: 150; easing.type: Easing.OutCubic}}
126− }
127− }
128−}
qml/Dash/DashContent.qml+123−62
@@ -17,6 +17,7 @@
1717import QtQuick 2.0
1818import Ubuntu.Components 0.1
1919import Unity 0.1
20+import "../Components"
2021
2122Item {
2223 id: dashContent
@@ -33,7 +34,6 @@ Item {
3334 signal movementEnded()
3435 signal contentFlickStarted()
3536 signal contentEndReached()
36− signal previewShown()
3737 signal scopeLoaded(string scopeId)
3838 signal positionedAtBeginning()
3939
@@ -80,77 +80,138 @@ Item {
8080 }
8181 }
8282
83− ListView {
84− id: dashContentList
85− objectName: "dashContentList"
86−
87− interactive: dashContent.scopes.loaded && !currentItem.previewShown && !currentItem.moving
88−
83+ Item {
84+ id: dashContentListHolder
8985 anchors.fill: parent
90− model: dashContent.model
91− orientation: ListView.Horizontal
92− boundsBehavior: Flickable.DragAndOvershootBounds
93− flickDeceleration: units.gu(625)
94− maximumFlickVelocity: width * 5
95− snapMode: ListView.SnapOneItem
96− highlightMoveDuration: 250
97− highlightRangeMode: ListView.StrictlyEnforceRange
98− /* FIXME: workaround rendering issue due to use of ShaderEffectSource in
99− UbuntuShape. While switching from the home scope to the People scope the
100− rendering would block midway.
101− */
102− cacheBuffer: 2147483647
103− onMovementStarted: dashContent.movementStarted()
104− onMovementEnded: dashContent.movementEnded()
105−
106− // If the number of items is less than the current index, then need to reset to another item.
107− onCountChanged: {
108− if (count > 0) {
109− if (currentIndex >= count) {
110− dashContent.setCurrentScopeAtIndex(count-1, true, true)
111− } else if (currentIndex < 0) {
112− // setting currentIndex directly, cause we don't want to loose set_current_index
113− dashContent.currentIndex = 0
86+
87+ ListView {
88+ id: dashContentList
89+ objectName: "dashContentList"
90+
91+ interactive: dashContent.scopes.loaded && !previewListView.onScreen && !currentItem.moving
92+
93+ anchors.fill: parent
94+ model: dashContent.model
95+ orientation: ListView.Horizontal
96+ boundsBehavior: Flickable.DragAndOvershootBounds
97+ flickDeceleration: units.gu(625)
98+ maximumFlickVelocity: width * 5
99+ snapMode: ListView.SnapOneItem
100+ highlightMoveDuration: 250
101+ highlightRangeMode: ListView.StrictlyEnforceRange
102+ // TODO Investigate if we can switch to a smaller cache buffer when/if UbuntuShape gets more performant
103+ cacheBuffer: 1073741823
104+ onMovementStarted: dashContent.movementStarted()
105+ onMovementEnded: dashContent.movementEnded()
106+
107+ // If the number of items is less than the current index, then need to reset to another item.
108+ onCountChanged: {
109+ if (count > 0) {
110+ if (currentIndex >= count) {
111+ dashContent.setCurrentScopeAtIndex(count-1, true, true)
112+ } else if (currentIndex < 0) {
113+ // setting currentIndex directly, cause we don't want to loose set_current_index
114+ dashContent.currentIndex = 0
115+ }
114116 }
115117 }
118+
119+ delegate:
120+ Loader {
121+ width: ListView.view.width
122+ height: ListView.view.height
123+ asynchronous: true
124+ source: scopeMapper.map(scope.id)
125+ objectName: scope.id + " loader"
126+
127+ readonly property bool moving: item ? item.moving : false
128+ readonly property var categoryView: item ? item.categoryView : null
129+ readonly property Scope theScope: scope
130+
131+ // these are needed for autopilot tests
132+ readonly property string scopeId: scope.id
133+ readonly property bool isCurrent: ListView.isCurrentItem
134+ readonly property bool isLoaded: status == Loader.Ready
135+
136+ onLoaded: {
137+ item.scope = Qt.binding(function() { return scope })
138+ item.isCurrent = Qt.binding(function() { return visible && ListView.isCurrentItem })
139+ item.tabBarHeight = pageHeader.implicitHeight;
140+ item.pageHeader = pageHeader;
141+ item.openEffect = openEffect;
142+ item.previewListView = previewListView;
143+ dashContentList.movementStarted.connect(item.movementStarted)
144+ dashContent.positionedAtBeginning.connect(item.positionedAtBeginning)
145+ dashContent.scopeLoaded(item.scope.id)
146+ }
147+ Connections {
148+ target: item
149+ ignoreUnknownSignals: true
150+ onEndReached: contentEndReached()
151+ }
152+
153+ Component.onDestruction: active = false
154+ }
116155 }
117156
118− delegate:
119− Loader {
120− width: ListView.view.width
121− height: ListView.view.height
122− asynchronous: true
123− source: scopeMapper.map(scope.id)
124− objectName: scope.id + " loader"
125−
126− readonly property bool previewShown: item ? item.previewShown : false
127− readonly property bool moving: item ? item.moving : false
128−
129− // these are needed for autopilot tests
130− readonly property string scopeId: scope.id
131− readonly property bool isCurrent: ListView.isCurrentItem
132− readonly property bool isLoaded: status == Loader.Ready
133−
134− onLoaded: {
135− item.scope = Qt.binding(function() { return scope })
136− item.isCurrent = Qt.binding(function() { return visible && ListView.isCurrentItem })
137− item.searchHistory = Qt.binding(function() { return dashContent.searchHistory })
138− dashContentList.movementStarted.connect(item.movementStarted)
139− dashContent.positionedAtBeginning.connect(item.positionedAtBeginning)
140− dashContent.scopeLoaded(item.scope.id)
157+ PageHeader {
158+ id: pageHeader
159+ width: parent.width
160+ searchEntryEnabled: true
161+ searchHistory: dashContent.searchHistory
162+
163+ childItem: TabBar {
164+ id: tabBar
165+ objectName: "tabbar"
166+ height: units.gu(7)
167+ width: parent.width
168+ selectionMode: false
169+ style: DashContentTabBarStyle {}
170+
171+ model: dashContentList.model
172+
173+ onSelectedIndexChanged: {
174+ dashContentList.currentIndex = selectedIndex;
141175 }
176+
142177 Connections {
143− target: item
144− ignoreUnknownSignals: true
145− onEndReached: contentEndReached()
146− onPreviewShownChanged: {
147− if (item.previewShown) {
148− dashContent.previewShown()
149− }
178+ target: dashContentList
179+ onCurrentIndexChanged: {
180+ tabBar.selectedIndex = dashContentList.currentIndex
150181 }
151182 }
152183
153− Component.onDestruction: active = false
184+ Connections {
185+ target: model
186+ onCountChanged: {
187+ if (tabBar.selectedIndex < 0 && model.count > 0)
188+ tabBar.selectedIndex = 0;
189+ }
190+ }
191+
192+ Component.onCompleted: {
193+ __styleInstance.headerTextStyle = Text.Raised
194+ __styleInstance.headerTextStyleColor = "black"
195+ }
154196 }
197+ }
198+ }
199+
200+ DashContentOpenEffect {
201+ id: openEffect
202+ anchors {
203+ fill: parent
204+ bottomMargin: -bottomOverflow
205+ }
206+ sourceItem: dashContentListHolder
207+ previewListView: previewListView
208+ }
209+
210+ PreviewListView {
211+ id: previewListView
212+ openEffect: openEffect
213+ categoryView: dashContentList.currentItem ? dashContentList.currentItem.categoryView : null
214+ scope: dashContentList.currentItem ? dashContentList.currentItem.theScope : null
215+ anchors.fill: parent
155216 }
156217}
qml/Dash/DashContentOpenEffect.qmlnew file+57
@@ -0,0 +1,57 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import Ubuntu.Components 0.1
19+import "../Components"
20+
21+OpenEffect {
22+ property PreviewListView previewListView: null
23+
24+ objectName: "openEffect"
25+ anchors {
26+ fill: parent
27+ bottomMargin: -bottomOverflow
28+ }
29+
30+ enabled: gap > 0.0
31+
32+ topGapPx: (1 - gap) * positionPx
33+ topOpacity: (1 - gap * 1.2)
34+ bottomGapPx: positionPx + gap * (targetBottomGapPx - positionPx)
35+ bottomOverflow: units.gu(20)
36+ live: !expansionAnimation.running
37+
38+ property int targetBottomGapPx: height - units.gu(8) - bottomOverflow
39+ property real gap: previewListView.open ? 1.0 : 0.0
40+
41+ Behavior on gap {
42+ NumberAnimation {
43+ id: expansionAnimation
44+ duration: 200
45+ easing.type: Easing.InOutQuad
46+ onRunningChanged: {
47+ if (!previewListView.open && !running) {
48+ previewListView.onScreen = false
49+ }
50+ }
51+ }
52+ }
53+ Behavior on positionPx {
54+ enabled: previewListView.open
55+ UbuntuNumberAnimation {}
56+ }
57+}
qml/Dash/DashContentTabBarStyle.qmlnew file+21
@@ -0,0 +1,21 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import Ubuntu.Components.Themes.Ambiance 0.1
18+
19+TabBarStyle {
20+ indicatorImageSource: "graphics/tabbarchevron.png"
21+}
qml/Dash/GenericScopeView.qml+28−228
@@ -27,15 +27,18 @@ FocusScope {
2727 property Scope scope
2828 property SortFilterProxyModel categories: categoryFilter
2929 property bool isCurrent
30− property ListModel searchHistory
3130 property alias moving: categoryView.moving
31+ property int tabBarHeight: 0
32+ property PageHeader pageHeader: null
33+ property OpenEffect openEffect: null
34+ property Item previewListView: null
3235
3336 signal endReached
3437 signal movementStarted
3538 signal positionedAtBeginning
3639
37− readonly property alias previewShown: previewListView.onScreen
3840 property bool enableHeightBehaviorOnNextCreation: false
41+ property var categoryView: categoryView
3942
4043 // FIXME delay the search so that daemons have time to settle, note that
4144 // removing this will break ScopeView::test_changeScope
@@ -84,12 +87,14 @@ FocusScope {
8487 target: scopeView.scope
8588 property: "searchQuery"
8689 value: pageHeader.searchQuery
90+ when: isCurrent
8791 }
8892
8993 Binding {
9094 target: pageHeader
9195 property: "searchQuery"
9296 value: scopeView.scope.searchQuery
97+ when: isCurrent
9398 }
9499
95100 Connections {
@@ -119,6 +124,10 @@ FocusScope {
119124 property string expandedCategoryId: ""
120125 signal correctExpandedCategory();
121126
127+ onContentYChanged: pageHeader.positionRealHeader();
128+ onOriginYChanged: pageHeader.positionRealHeader();
129+ onContentHeightChanged: pageHeader.positionRealHeader();
130+
122131 Behavior on contentY {
123132 enabled: previewListView.open
124133 UbuntuNumberAnimation {}
@@ -176,7 +185,7 @@ FocusScope {
176185 target: rendererLoader.item
177186 onClicked: {
178187 // Prepare the preview in case activate() triggers a preview only
179− effect.positionPx = Math.max(mapToItem(categoryView, 0, itemY).y, pageHeader.height + categoryView.stickyHeaderHeight);
188+ openEffect.positionPx = Math.max(mapToItem(categoryView, 0, itemY).y, pageHeader.height + categoryView.stickyHeaderHeight);
180189 previewListView.categoryId = categoryId
181190 previewListView.categoryDelegate = rendererLoader.item
182191 previewListView.model = target.model;
@@ -197,7 +206,7 @@ FocusScope {
197206 }
198207 }
199208 onPressAndHold: {
200− effect.positionPx = Math.max(mapToItem(categoryView, 0, itemY).y, pageHeader.height + categoryView.stickyHeaderHeight);
209+ openEffect.positionPx = Math.max(mapToItem(categoryView, 0, itemY).y, pageHeader.height + categoryView.stickyHeaderHeight);
201210 previewListView.categoryId = categoryId
202211 previewListView.categoryDelegate = rendererLoader.item
203212 previewListView.model = target.model;
@@ -288,14 +297,20 @@ FocusScope {
288297 categoryView.expandedCategoryId = "";
289298 }
290299 }
291− pageHeader: PageHeader {
292− id: pageHeader
293− objectName: "pageHeader"
294− width: categoryView.width
295− text: scopeView.scope.name
296− searchEntryEnabled: true
297− scope: scopeView.scope
298− searchHistory: scopeView.searchHistory
300+ pageHeader: Item {
301+ implicitHeight: scopeView.tabBarHeight
302+ onHeightChanged: {
303+ if (scopeView.pageHeader) {
304+ scopeView.pageHeader.height = height;
305+ }
306+ }
307+ onYChanged: positionRealHeader();
308+
309+ function positionRealHeader() {
310+ if (scopeView.pageHeader) {
311+ scopeView.pageHeader.y = y + parent.y;
312+ }
313+ }
299314 }
300315 }
301316
@@ -346,44 +361,6 @@ FocusScope {
346361 }
347362 }
348363
349− OpenEffect {
350− id: effect
351− objectName: "openEffect"
352− anchors {
353− fill: parent
354− bottomMargin: -bottomOverflow
355− }
356− sourceItem: categoryView
357−
358− enabled: gap > 0.0
359−
360− topGapPx: (1 - gap) * positionPx
361− topOpacity: (1 - gap * 1.2)
362− bottomGapPx: positionPx + gap * (targetBottomGapPx - positionPx)
363− bottomOverflow: units.gu(20)
364− live: !expansionAnimation.running
365−
366− property int targetBottomGapPx: height - units.gu(8) - bottomOverflow
367− property real gap: previewListView.open ? 1.0 : 0.0
368−
369− Behavior on gap {
370− NumberAnimation {
371− id: expansionAnimation
372− duration: 200
373− easing.type: Easing.InOutQuad
374− onRunningChanged: {
375− if (!previewListView.open && !running) {
376− previewListView.onScreen = false
377− }
378− }
379− }
380− }
381− Behavior on positionPx {
382− enabled: previewListView.open
383− UbuntuNumberAnimation {}
384− }
385− }
386−
387364 Connections {
388365 target: scopeView.scope
389366 onPreviewReady: {
@@ -402,190 +379,13 @@ FocusScope {
402379 }
403380 }
404381
405− PreviewDelegateMapper {
406− id: previewDelegateMapper
407− }
408−
409− ListView {
410− id: previewListView
411− objectName: "previewListView"
412− height: effect.bottomGapPx - effect.topGapPx
413− anchors {
414− top: parent.top
415− topMargin: effect.topGapPx
416− left: parent.left
417− right: parent.right
418− }
419− orientation: ListView.Horizontal
420− highlightRangeMode: ListView.StrictlyEnforceRange
421− snapMode: ListView.SnapOneItem
422− boundsBehavior: Flickable.DragAndOvershootBounds
423− highlightMoveDuration: 250
424− flickDeceleration: units.gu(625)
425− maximumFlickVelocity: width * 5
426− cacheBuffer: 0
427−
428− // To be set before opening the preview
429− property string categoryId: ""
430− property var categoryDelegate
431−
432− // because the ListView is built asynchronous, setting the
433− // currentIndex directly won't work. We need to refresh it
434− // when the first preview is ready to be displayed.
435− property bool init: true
436−
437− onCurrentIndexChanged: {
438− var row = Math.floor(currentIndex / categoryDelegate.columns);
439− if (categoryDelegate.collapsedRowCount <= row) {
440− categoryView.expandedCategoryId = categoryId
441− }
442−
443− if (open) {
444− categoryDelegate.highlightIndex = currentIndex
445− }
446−
447− if (!init && model !== undefined) {
448− var item = model.get(currentIndex)
449− scopeView.scope.preview(item.uri, item.icon, item.category, 0, item.mimetype, item.title, item.comment, item.dndUri, item.metadata)
450− }
451−
452− // Adjust contentY in case we need to change to it to show the next row
453− if (categoryDelegate.rows > 1) {
454− var itemY = categoryView.contentItem.mapFromItem(categoryDelegate.currentItem).y;
455−
456− // Find new contentY and effect.postionPx
457− var newContentY = itemY - effect.positionPx - categoryDelegate.verticalSpacing;
458−
459− // Make sure the item is not covered by a header. Move the effect split down if necessary
460− var headerHeight = pageHeader.height + categoryView.stickyHeaderHeight;
461− var effectAdjust = Math.max(effect.positionPx, headerHeight);
462−
463− // Make sure we don't overscroll the listview. If yes, adjust effect position
464− if (newContentY < 0) {
465− effectAdjust += newContentY;
466− newContentY = 0;
467− }
468− if (newContentY > Math.max(0, categoryView.contentHeight - categoryView.height)) {
469− effectAdjust += -(categoryView.contentHeight - categoryView.height) + newContentY
470− newContentY = categoryView.contentHeight - categoryView.height;
471− }
472−
473− effect.positionPx = effectAdjust;
474− categoryView.contentY = newContentY;
475− }
476− }
477−
478− property bool open: false
479− property bool onScreen: false
480−
481− onOpenChanged: {
482− if (open) {
483− onScreen = true;
484− categoryDelegate.highlightIndex = currentIndex;
485− pageHeader.unfocus();
486− } else {
487− // Cancel any pending preview requests or actions
488− if (previewListView.currentItem.previewData !== undefined) {
489− previewListView.currentItem.previewData.cancelAction();
490− }
491− scopeView.scope.cancelActivation();
492− model = undefined;
493− categoryView.correctExpandedCategory();
494− categoryDelegate.highlightIndex = -1;
495− }
496− }
497−
498− Rectangle {
499− anchors.fill: parent
500− color: Qt.rgba(0, 0, 0, .3)
501− z: -1
502− }
503−
504− delegate: Loader {
505− id: previewLoader
506− objectName: "previewLoader" + index
507− height: previewListView.height
508− width: previewListView.width
509− asynchronous: true
510− source: previewListView.onScreen ?
511− (previewData !== undefined ? previewDelegateMapper.map(previewData.rendererName) : "DashPreviewPlaceholder.qml") : ""
512−
513− onPreviewDataChanged: {
514− if (previewData !== undefined && source.toString().indexOf("DashPreviewPlaceholder.qml") != -1) {
515− previewLoader.opacity = 0;
516− }
517− }
518−
519− onSourceChanged: {
520− if (previewData !== undefined) {
521− fadeIn.start()
522− }
523− }
524−
525− PropertyAnimation {
526− id: fadeIn
527− target: previewLoader
528− property: "opacity"
529− from: 0.0
530− to: 1.0
531− duration: UbuntuAnimation.BriskDuration
532− }
533−
534− property var previewData
535− property bool valid: item !== null
536−
537− onLoaded: {
538− if (previewListView.onScreen && previewData !== undefined) {
539− item.previewData = Qt.binding(function() { return previewData })
540− item.isCurrent = Qt.binding(function() { return ListView.isCurrentItem })
541− }
542− }
543−
544− Connections {
545− ignoreUnknownSignals: true
546− target: item
547− onClose: {
548− previewListView.open = false
549− }
550− }
551−
552− function closePreviewSpinner() {
553− if (item) {
554− item.showProcessingAction = false;
555− }
556− }
557− }
558− }
559−
560− Image {
561− objectName: "pointerArrow"
562− anchors {
563− top: previewListView.bottom
564− left: parent.left
565− leftMargin: previewListView.categoryDelegate !== undefined && previewListView.categoryDelegate.currentItem ?
566− previewListView.categoryDelegate.currentItem.center + (-width + margins) / 2 : 0
567−
568− Behavior on leftMargin {
569− SmoothedAnimation {
570− duration: UbuntuAnimation.FastDuration
571− }
572− }
573− }
574− height: units.gu(1)
575− width: units.gu(2)
576− property int margins: previewListView.categoryDelegate ? previewListView.categoryDelegate.margins : 0
577− opacity: previewListView.open ? .5 : 0
578−
579− source: "graphics/tooltip_arrow.png"
580− }
581−
582382 // TODO: Move as InverseMouseArea to DashPreview
583383 MouseArea {
584384 objectName: "closePreviewMouseArea"
585385 enabled: previewListView.onScreen
586386 anchors {
587387 fill: parent
588− topMargin: effect.bottomGapPx
388+ topMargin: openEffect.bottomGapPx
589389 }
590390 onClicked: {
591391 previewListView.open = false;
qml/Dash/PreviewListView.qmlnew file+212
@@ -0,0 +1,212 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import Ubuntu.Components 0.1
19+import Unity 0.1
20+import "../Components"
21+
22+Item {
23+ property OpenEffect openEffect: null
24+ property ScopeListView categoryView: null
25+ property Scope scope: null
26+
27+ property alias open: previewListView.open
28+ property alias onScreen: previewListView.onScreen
29+ property alias categoryId: previewListView.categoryId
30+ property alias categoryDelegate: previewListView.categoryDelegate
31+ property alias init: previewListView.init
32+ property alias model: previewListView.model
33+ property alias currentIndex: previewListView.currentIndex
34+ property alias currentItem: previewListView.currentItem
35+
36+ Image {
37+ objectName: "pointerArrow"
38+ anchors {
39+ top: previewListView.bottom
40+ left: parent.left
41+ leftMargin: previewListView.categoryDelegate !== undefined && previewListView.categoryDelegate.currentItem ?
42+ previewListView.categoryDelegate.currentItem.center + (-width + margins) / 2 : 0
43+
44+ Behavior on leftMargin {
45+ SmoothedAnimation {
46+ duration: UbuntuAnimation.FastDuration
47+ }
48+ }
49+ }
50+ height: units.gu(1)
51+ width: units.gu(2)
52+ property int margins: previewListView.categoryDelegate ? previewListView.categoryDelegate.margins : 0
53+ opacity: previewListView.open ? .5 : 0
54+
55+ source: "graphics/tooltip_arrow.png"
56+ }
57+
58+ ListView {
59+ id: previewListView
60+ objectName: "previewListView"
61+ height: openEffect.bottomGapPx - openEffect.topGapPx
62+ anchors {
63+ top: parent.top
64+ topMargin: openEffect.topGapPx
65+ left: parent.left
66+ right: parent.right
67+ }
68+ orientation: ListView.Horizontal
69+ highlightRangeMode: ListView.StrictlyEnforceRange
70+ snapMode: ListView.SnapOneItem
71+ boundsBehavior: Flickable.DragAndOvershootBounds
72+ highlightMoveDuration: 250
73+ flickDeceleration: units.gu(625)
74+ maximumFlickVelocity: width * 5
75+ cacheBuffer: 0
76+
77+ // To be set before opening the preview
78+ property string categoryId: ""
79+ property var categoryDelegate
80+
81+ // because the ListView is built asynchronous, setting the
82+ // currentIndex directly won't work. We need to refresh it
83+ // when the first preview is ready to be displayed.
84+ property bool init: true
85+
86+ PreviewDelegateMapper {
87+ id: previewDelegateMapper
88+ }
89+
90+ onCurrentIndexChanged: {
91+ var row = Math.floor(currentIndex / categoryDelegate.columns);
92+ if (categoryDelegate.collapsedRowCount <= row) {
93+ categoryView.expandedCategoryId = categoryId
94+ }
95+
96+ if (open) {
97+ categoryDelegate.highlightIndex = currentIndex
98+ }
99+
100+ if (!init && model !== undefined) {
101+ var item = model.get(currentIndex)
102+ scope.preview(item.uri, item.icon, item.category, 0, item.mimetype, item.title, item.comment, item.dndUri, item.metadata)
103+ }
104+
105+ // Adjust contentY in case we need to change to it to show the next row
106+ if (categoryDelegate.rows > 1) {
107+ var itemY = categoryView.contentItem.mapFromItem(categoryDelegate.currentItem).y;
108+
109+ // Find new contentY and effect.postionPx
110+ var newContentY = itemY - openEffect.positionPx - categoryDelegate.verticalSpacing;
111+
112+ // Make sure the item is not covered by a header. Move the effect split down if necessary
113+ var headerHeight = pageHeader.height + categoryView.stickyHeaderHeight;
114+ var effectAdjust = Math.max(openEffect.positionPx, headerHeight);
115+
116+ // Make sure we don't overscroll the listview. If yes, adjust effect position
117+ if (newContentY < 0) {
118+ effectAdjust += newContentY;
119+ newContentY = 0;
120+ }
121+ if (newContentY > Math.max(0, categoryView.contentHeight - categoryView.height)) {
122+ effectAdjust += -(categoryView.contentHeight - categoryView.height) + newContentY
123+ newContentY = categoryView.contentHeight - categoryView.height;
124+ }
125+
126+ openEffect.positionPx = effectAdjust;
127+ categoryView.contentY = newContentY;
128+ }
129+ }
130+
131+ property bool open: false
132+ property bool onScreen: false
133+
134+ onOpenChanged: {
135+ if (open) {
136+ onScreen = true;
137+ categoryDelegate.highlightIndex = currentIndex;
138+ pageHeader.unfocus();
139+ } else {
140+ // Cancel any pending preview requests or actions
141+ if (previewListView.currentItem.previewData !== undefined) {
142+ previewListView.currentItem.previewData.cancelAction();
143+ }
144+ scope.cancelActivation();
145+ model = undefined;
146+ categoryView.correctExpandedCategory();
147+ categoryDelegate.highlightIndex = -1;
148+ }
149+ }
150+
151+ Rectangle {
152+ anchors.fill: parent
153+ color: Qt.rgba(0, 0, 0, .3)
154+ z: -1
155+ }
156+
157+ delegate: Loader {
158+ id: previewLoader
159+ objectName: "previewLoader" + index
160+ height: previewListView.height
161+ width: previewListView.width
162+ asynchronous: true
163+ source: previewListView.onScreen ?
164+ (previewData !== undefined ? previewDelegateMapper.map(previewData.rendererName) : "DashPreviewPlaceholder.qml") : ""
165+
166+ onPreviewDataChanged: {
167+ if (previewData !== undefined && source.toString().indexOf("DashPreviewPlaceholder.qml") != -1) {
168+ previewLoader.opacity = 0;
169+ }
170+ }
171+
172+ onSourceChanged: {
173+ if (previewData !== undefined) {
174+ fadeIn.start()
175+ }
176+ }
177+
178+ PropertyAnimation {
179+ id: fadeIn
180+ target: previewLoader
181+ property: "opacity"
182+ from: 0.0
183+ to: 1.0
184+ duration: UbuntuAnimation.BriskDuration
185+ }
186+
187+ property var previewData
188+ property bool valid: item !== null
189+
190+ onLoaded: {
191+ if (previewListView.onScreen && previewData !== undefined) {
192+ item.previewData = Qt.binding(function() { return previewData })
193+ item.isCurrent = Qt.binding(function() { return ListView.isCurrentItem })
194+ }
195+ }
196+
197+ Connections {
198+ ignoreUnknownSignals: true
199+ target: item
200+ onClose: {
201+ previewListView.open = false
202+ }
203+ }
204+
205+ function closePreviewSpinner() {
206+ if (item) {
207+ item.showProcessingAction = false;
208+ }
209+ }
210+ }
211+ }
212+}
qml/Dash/graphics/tabbarchevron@27.pngnew file

Binary file not shown.

qml/Hud/HudParametrizedActionsPage.qml+1−1
@@ -89,7 +89,7 @@ Item {
8989 anchors.left: parent.left
9090 anchors.right: parent.right
9191
92− PageHeader {
92+ PageHeaderLabel {
9393 id: header
9494 anchors.top: parent.top
9595 anchors.left: parent.left
tests/mocks/Unity/fake_scopes.cpp+3
@@ -35,6 +35,7 @@ Scopes::Scopes(QObject *parent)
3535 m_roles[Scopes::RoleScope] = "scope";
3636 m_roles[Scopes::RoleId] = "id";
3737 m_roles[Scopes::RoleVisible] = "visible";
38+ m_roles[Scopes::RoleTitle] = "title";
3839
3940 QObject::connect(this, SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, SIGNAL(countChanged()));
4041 QObject::connect(this, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), this, SIGNAL(countChanged()));
@@ -110,6 +111,8 @@ QVariant Scopes::data(const QModelIndex& index, int role) const
110111 return QVariant::fromValue(scope->id());
111112 } else if (role == Scopes::RoleVisible) {
112113 return QVariant::fromValue(scope->visible());
114+ } else if (role == Scopes::RoleTitle) {
115+ return QVariant::fromValue(scope->name());
113116 } else {
114117 return QVariant();
115118 }
tests/mocks/Unity/fake_scopes.h+2−1
@@ -41,7 +41,8 @@ public:
4141 enum Roles {
4242 RoleScope,
4343 RoleId,
44− RoleVisible
44+ RoleVisible,
45+ RoleTitle
4546 };
4647
4748 Q_INVOKABLE int rowCount(const QModelIndex& parent = QModelIndex()) const;
tests/qmltests/CMakeLists.txt+1−2
@@ -37,9 +37,8 @@ add_qml_test(Components Stage IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS}
3737 ${CMAKE_BINARY_DIR}/tests/mocks
3838 ${CMAKE_BINARY_DIR}/plugins)
3939add_qml_test(Components Tile)
40−add_qml_test(Components PageHeader)
40+add_qml_test(Components PageHeaderLabel)
4141add_qml_test(Dash Dash IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
42−add_qml_test(Dash DashBar IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${CMAKE_CURRENT_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
4342add_qml_test(Dash DashContent IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
4443add_qml_test(Dash DashPreview)
4544add_qml_test(Dash GenericPreview)
tests/qmltests/Components/tst_PageHeader.qml → tests/qmltests/Components/tst_PageHeaderLabel.qmlrenamed+2−2
@@ -31,7 +31,7 @@ Item {
3131 }
3232
3333 UT.UnityTestCase {
34− name: "PageHeaderTest"
34+ name: "PageHeaderLabelTest"
3535 when: windowShown
3636
3737 property alias searchEnabled : pageHeader.searchEntryEnabled
@@ -204,7 +204,7 @@ Item {
204204 anchors.fill: parent
205205 spacing: units.gu(1)
206206
207− PageHeader {
207+ PageHeaderLabel {
208208 id: pageHeader
209209 anchors {
210210 left: parent.left
tests/qmltests/Dash/qml/FakeScopeView.qml+1
@@ -23,6 +23,7 @@ GenericScopeView {
2323 id: fakeScopeView
2424
2525 property alias backColor : back.color
26+ property var categoryView: null
2627
2728 onEndReached: {
2829 if (scope != undefined && shell != undefined && shell.scopeStatus != undefined) {
tests/qmltests/Dash/tst_Dash.qml+6−8
@@ -25,8 +25,6 @@ Item {
2525 width: units.gu(40)
2626 height: units.gu(80)
2727
28− property ListModel searchHistory : ListModel {}
29−
3028 Dash {
3129 id: dash
3230 anchors.fill: parent
@@ -83,14 +81,14 @@ Item {
8381 // wait for scopes to load
8482 tryCompare(scopes, "loaded", true);
8583
86− var dashbar = findChild(dash, "dashbar");
87− verify(dashbar != undefined)
84+ var tabbar = findChild(dash, "tabbar");
85+ verify(tabbar != undefined)
8886 var dashContent = findChild(dash, "dashContent");
8987 var current_index = dashContent.currentIndex;
9088
9189 dash.setCurrentScope(data.tag, true /* animate */, false /* reset */);
9290 compare(dashContent.currentIndex, data.shouldBeVisible ? data.visualIndex : current_index);
93− compare(dashbar.currentIndex, data.shouldBeVisible ? data.visualIndex : current_index);
91+ compare(tabbar.selectedIndex, data.shouldBeVisible ? data.visualIndex : current_index);
9492 }
9593
9694 function test_show_scope_on_load_data() {
@@ -127,12 +125,12 @@ Item {
127125 // wait for scopes to load
128126 tryCompare(scopes, "loaded", true);
129127
130− var dashbar = findChild(dash, "dashbar");
131− verify(dashbar != undefined)
128+ var tabbar = findChild(dash, "tabbar");
129+ verify(tabbar != undefined)
132130 var dashContent = findChild(dash, "dashContent");
133131 var current_index = dashContent.currentIndex;
134132
135− dashbar.itemSelected(data.visualIndex);
133+ tabbar.selectedIndex = data.visualIndex;
136134 compare(dashContent.currentIndex, data.shouldBeVisible ? data.visualIndex : current_index);
137135 }
138136 }
tests/qmltests/Dash/tst_DashBar.qmldeleted−111
@@ -1,111 +0,0 @@
1−/*
2− * Copyright 2013 Canonical Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import QtTest 1.0
19−import Unity 0.1
20−import ".."
21−import "../../../qml/Dash"
22−import Ubuntu.Components 0.1
23−import Unity.Test 0.1 as UT
24−
25−Item {
26− width: units.gu(40)
27− height: units.gu(71)
28−
29− DashBar {
30− id: dashBar
31−
32− anchors {
33− left: parent.left
34− right: parent.right
35− bottom: parent.bottom
36− }
37− model: scopes
38− onItemSelected: currentIndex = index
39− }
40−
41− Scopes {
42− id: scopes
43− }
44−
45− SignalSpy {
46− id: signalSpy
47− signalName: "itemSelected"
48− target: dashBar
49− }
50−
51− UT.UnityTestCase {
52− name: "DashBar"
53− when: scopes.loaded
54−
55− property alias model: dashBar.model
56− property alias currentIndex: dashBar.currentIndex
57−
58− readonly property alias lineHeight: dashBar.lineHeight
59− readonly property alias itemSize: dashBar.itemSize
60− readonly property alias iconSize: dashBar.iconSize
61− readonly property var panel: findChild(dashBar, "panel");
62−
63− function initTestCase() {
64− currentIndex = 2
65− }
66−
67− function waitForAnimationToEnd() {
68− compare(panel.animating, true) // check the animation started
69− tryCompare(panel, "animating", false) // wait till the animation ends
70− }
71−
72− function closePanel() {
73− dashBar.finishNavigation()
74− waitForAnimationToEnd()
75− tryCompare(panel, "opened", false)
76− }
77−
78− function openPanel() {
79− dashBar.startNavigation()
80− waitForAnimationToEnd()
81− tryCompare(panel, "opened", true)
82− }
83−
84− function test_navigationAndHide() {
85− openPanel()
86− closePanel()
87− }
88−
89− function test_itemSelected() {
90− openPanel()
91−
92− var row = findChild(dashBar, "row");
93− tryCompareFunction(function(){return row.width > 0;}, true);
94− for (var i = 0; i < model.rowCount(); i++) {
95− // coordinate x in the middle of item with index 'i'
96− var x = row.x + (row.width / model.rowCount()) * i + itemSize / 2
97−
98− // FIXME workaround for a bug in SignalSpy
99− signalSpy.clear()
100−
101− // (item, x, y, button, modifiers, delay)
102− mouseClick(panel, x, row.height / 2, Qt.LeftButton, Qt.NoModifier, 0)
103− compare(signalSpy.count > 0, true, "signal itemSelected not triggered")
104− compare(signalSpy.signalArguments[0][0], i, "signal itemSelected emitted unexpected index");
105− tryCompare(dashBar, "currentIndex", i);
106− }
107−
108− closePanel()
109− }
110− }
111−}
tests/qmltests/Dash/tst_DashContent.qml+67−4
@@ -17,9 +17,11 @@
1717import QtQuick 2.0
1818import QtTest 1.0
1919import "../../../qml/Dash"
20+import "../../../qml/Components"
2021import Ubuntu.Components 0.1
2122import Unity 0.1
2223import Unity.Test 0.1 as UT
24+import Utils 0.1
2325
2426Item {
2527 id: shell
@@ -54,11 +56,13 @@ Item {
5456 id: dashContent
5557 anchors.fill: parent
5658
57− model: scopesModel
59+ model: SortFilterProxyModel {
60+ model: scopesModel
61+ }
5862 scopes : scopesModel
5963
6064 scopeMapper : scopeDelegateMapper
61− searchHistory: ListModel {}
65+ searchHistory: SearchHistoryModel {}
6266 }
6367
6468 ScopeDelegateMapper {
@@ -109,9 +113,10 @@ Item {
109113
110114 UT.UnityTestCase {
111115 name: "DashContent"
112− when: windowShown
116+ when: scopesModel.loaded
113117
114118 function init() {
119+ scopesModel.clear();
115120 scopesModel.load();
116121 }
117122
@@ -268,6 +273,65 @@ Item {
268273 tryCompare(scopesModel.get(2), "isActive", data.active2);
269274 }
270275
276+ function doFindMusicButton(parent) {
277+ for (var i = 0; i < parent.children.length; i++) {
278+ var c = parent.children[i];
279+ if (UT.Util.isInstanceOf(c, "AbstractButton") && parent.x >= 0) {
280+ for (var ii = 0; ii < c.children.length; ii++) {
281+ var cc = c.children[ii];
282+ if (UT.Util.isInstanceOf(cc, "Label") && cc.text == "Music") {
283+ return c;
284+ }
285+ }
286+ }
287+ var r = doFindMusicButton(c);
288+ if (r !== undefined) {
289+ return r;
290+ }
291+ }
292+ return undefined;
293+ }
294+
295+ function findMusicButton() {
296+ // We need to find a AbstractButton that has a Label child
297+ // with text Music and it's parent x is >= 0
298+ var tabbar = findChild(dashContent, "tabbar");
299+ return doFindMusicButton(tabbar);
300+ }
301+
302+ function test_tabBar_index_change() {
303+ tryCompare(scopesModel, "loaded", true);
304+ var tabbar = findChild(dashContent, "tabbar");
305+
306+ compare(dashContent.currentIndex, 0);
307+ tryCompare(tabbar, "selectedIndex", 0);
308+ tryCompare(tabbar, "selectionMode", false);
309+
310+ mouseClick(tabbar, units.gu(5), units.gu(5))
311+
312+ tryCompare(tabbar, "selectionMode", true);
313+ tryCompare(tabbar, "selectedIndex", 0);
314+ tryCompare(dashContent, "currentIndex", 0);
315+
316+ var button;
317+ tryCompareFunction(function() { button = findMusicButton(); return button != undefined; }, true);
318+ waitForRendering(button);
319+
320+ mouseClick(button, button.width / 2, button.height / 2)
321+
322+ tryCompare(tabbar, "selectionMode", false);
323+ tryCompare(tabbar, "selectedIndex", 1);
324+ tryCompare(dashContent, "currentIndex", 1);
325+ }
326+
327+ function test_tabBar_listens_to_index_change() {
328+ var tabbar = findChild(dashContent, "tabbar");
329+ tryCompare(dashContent, "currentIndex", 0);
330+ compare(tabbar.selectedIndex, 0);
331+ dashContent.currentIndex = 1;
332+ compare(tabbar.selectedIndex, 1);
333+ }
334+
271335 function checkFlickMovingAndNotInteractive()
272336 {
273337 var dashContentList = findChild(dashContent, "dashContentList");
@@ -282,7 +346,6 @@ Item {
282346 return dashContentList.currentItem.moving && !dashContentList.interactive;
283347 }
284348
285−
286349 function test_hswipe_disabled_vswipe() {
287350 var dashContentList = findChild(dashContent, "dashContentList");
288351
tests/qmltests/Dash/tst_GenericScopeView.qml+49−28
@@ -41,54 +41,83 @@ Item {
4141 signal mainStageFocusedApplicationChanged()
4242 }
4343
44+ PreviewListView {
45+ id: previewListView
46+ anchors.fill: parent
47+ openEffect: openEffect
48+ categoryView: genericScopeView.categoryView
49+ scope: genericScopeView.scope
50+ }
51+
52+ DashContentOpenEffect {
53+ id: openEffect
54+ previewListView: previewListView
55+ }
56+
57+ PageHeaderLabel {
58+ id: pageHeader
59+ searchHistory: SearchHistoryModel {}
60+ }
61+
4462 GenericScopeView {
4563 id: genericScopeView
4664 anchors.fill: parent
47− searchHistory: SearchHistoryModel {}
65+ previewListView: previewListView
66+ openEffect: openEffect
67+ pageHeader: pageHeader
68+ tabBarHeight: pageHeader.implicitHeight
4869
4970 UT.UnityTestCase {
5071 name: "GenericScopeView"
5172 when: scopes.loaded
5273
53− function test_isCurrent() {
54− var pageHeader = findChild(genericScopeView, "pageHeader");
55− var previewListView = findChild(genericScopeView, "previewListView");
74+ function test_isCurrent() {
5675 genericScopeView.isCurrent = true
5776 pageHeader.searchQuery = "test"
5877 previewListView.open = true
5978 genericScopeView.isCurrent = false
6079 tryCompare(pageHeader, "searchQuery", "")
61− tryCompare(genericScopeView, "previewShown", false);
80+ tryCompare(previewListView, "open", false);
6281 }
6382
6483 function test_showDash() {
65− var previewListView = findChild(genericScopeView, "previewListView");
6684 previewListView.open = true;
85+ tryCompare(openEffect, "live", true);
6786 scopes.get(0).showDash();
68− tryCompare(genericScopeView, "previewShown", false);
87+ tryCompare(previewListView, "open", false);
88+ tryCompare(openEffect, "live", true);
6989 }
7090
7191 function test_hideDash() {
72− var previewListView = findChild(genericScopeView, "previewListView");
7392 previewListView.open = true;
7493 scopes.get(0).hideDash();
75− tryCompare(genericScopeView, "previewShown", false);
94+ tryCompare(previewListView, "open", false);
7695 }
7796
7897 function openPreview() {
7998 var categoryListView = findChild(genericScopeView, "categoryListView");
8099 categoryListView.positionAtBeginning();
81100
101+ tryCompareFunction(function() {
102+ var tile = findChild(findChild(genericScopeView, "0"), "delegate0");
103+ return tile != undefined;
104+ },
105+ true);
82106 var tile = findChild(findChild(genericScopeView, "0"), "delegate0");
83107 mouseClick(tile, tile.width / 2, tile.height / 2);
84− var openEffect = findChild(genericScopeView, "openEffect");
108+ tryCompare(previewListView, "open", true);
85109 tryCompare(openEffect, "gap", 1);
86110 }
87111
88112 function checkArrowPosition(index) {
113+ tryCompareFunction(function() {
114+ var tile = findChild(findChild(genericScopeView, "0"), "delegate" + index);
115+ return tile != undefined;
116+ },
117+ true);
89118 var tile = findChild(findChild(genericScopeView, "0"), "delegate" + index);
90119 var tileCenter = tile.x + tile.width/2;
91− var pointerArrow = findChild(genericScopeView, "pointerArrow");
120+ var pointerArrow = findChild(previewListView, "pointerArrow");
92121 var pointerArrowCenter = pointerArrow.x + pointerArrow.width/2;
93122 compare(pointerArrowCenter, tileCenter, "Pointer did not move to tile");
94123 }
@@ -97,9 +126,7 @@ Item {
97126 var closePreviewMouseArea = findChild(genericScopeView, "closePreviewMouseArea");
98127 mouseClick(closePreviewMouseArea, closePreviewMouseArea.width / 2, closePreviewMouseArea.height / 2);
99128
100− var previewListView = findChild(genericScopeView, "previewListView");
101129 tryCompare(previewListView, "open", false);
102− var openEffect = findChild(genericScopeView, "openEffect");
103130 tryCompare(openEffect, "gap", 0);
104131
105132 var categoryListView = findChild(genericScopeView, "categoryListView");
@@ -108,13 +135,12 @@ Item {
108135 }
109136
110137 function test_previewOpenClose() {
111− var previewListView = findChild(genericScopeView, "previewListView");
112138 tryCompare(previewListView, "open", false);
113139
114140 openPreview();
115141
116142 // check for it opening successfully
117− var currentPreviewItem = findChild(genericScopeView, "previewLoader0");
143+ var currentPreviewItem = findChild(previewListView, "previewLoader0");
118144 tryCompareFunction(function() {
119145 var parts = currentPreviewItem.source.toString().split("/");
120146 var name = parts[parts.length - 1];
@@ -138,26 +164,23 @@ Item {
138164 var categoryListView = findChild(genericScopeView, "categoryListView");
139165 categoryListView.positionAtBeginning();
140166 waitForRendering(categoryListView);
141− categoryListView.flick(0, -units.gu(80));
167+ categoryListView.flick(0, -units.gu(60));
142168 tryCompare(categoryListView.flicking, false);
143169
144170 var tile = findChild(findChild(genericScopeView, "0"), "delegate0");
145171 mouseClick(tile, tile.width / 2, tile.height - 1);
146− var openEffect = findChild(genericScopeView, "openEffect");
147172 tryCompare(openEffect, "gap", 1);
148173
149− var pageHeader = findChild(genericScopeView, "pageHeader");
150174 verify(openEffect.positionPx >= pageHeader.height + categoryListView.stickyHeaderHeight);
151175 }
152176
153177 function test_previewCycle() {
154− var previewListView = findChild(genericScopeView, "previewListView");
155178 tryCompare(previewListView, "open", false);
156179
157180 openPreview();
158181
159182 // wait for it to be loaded
160− var currentPreviewItem = findChild(genericScopeView, "previewLoader0");
183+ var currentPreviewItem = findChild(previewListView, "previewLoader0");
161184 tryCompareFunction(function() {
162185 var parts = currentPreviewItem.source.toString().split("/");
163186 var name = parts[parts.length - 1];
@@ -179,7 +202,7 @@ Item {
179202 previewListView.height / 2);
180203
181204 // wait for it to be loaded
182− var nextPreviewItem = findChild(genericScopeView, "previewLoader" + i);
205+ var nextPreviewItem = findChild(previewListView, "previewLoader" + i);
183206 tryCompareFunction(function() {
184207 var parts = nextPreviewItem.source.toString().split("/");
185208 var name = parts[parts.length - 1];
@@ -206,11 +229,12 @@ Item {
206229
207230 function test_show_spinner() {
208231 openPreview();
209− var previewListView = findChild(genericScopeView, "previewListView");
210− var previewLoader = findChild(genericScopeView, "previewLoader0");
232+ var previewLoader = findChild(previewListView, "previewLoader0");
233+ tryCompare(previewLoader, "progress", 1.0);
234+ tryCompareFunction(function() { return previewLoader.item != undefined; }, true);
211235
212236 previewLoader.item.showProcessingAction = true;
213− var waitingForAction = findChild(genericScopeView, "waitingForActionMouseArea");
237+ var waitingForAction = findChild(previewListView, "waitingForActionMouseArea");
214238 tryCompare(waitingForAction, "enabled", true);
215239 previewLoader.closePreviewSpinner();
216240 tryCompare(waitingForAction, "enabled", false);
@@ -252,12 +276,10 @@ Item {
252276 tryCompareFunction(function() { return findChild(genericScopeView, "carouselDelegate") != undefined; }, true);
253277 var tile = findChild(genericScopeView, "carouselDelegate");
254278 mouseClick(tile, tile.width / 2, tile.height / 2);
255− var openEffect = findChild(genericScopeView, "openEffect");
256279 tryCompare(openEffect, "gap", 1);
257280
258281 // check for it opening successfully
259− var previewListView = findChild(genericScopeView, "previewListView");
260− var currentPreviewItem = findChild(genericScopeView, "previewLoader0");
282+ var currentPreviewItem = findChild(previewListView, "previewLoader0");
261283 tryCompareFunction(function() {
262284 var parts = currentPreviewItem.source.toString().split("/");
263285 var name = parts[parts.length - 1];
@@ -303,7 +325,6 @@ Item {
303325 tryCompareFunction(
304326 function() {
305327 var header0 = findChild(genericScopeView, "dashSectionHeader0")
306− var pageHeader = findChild(genericScopeView, "pageHeader");
307328 return header0.y == pageHeader.height;
308329 },
309330 true);