Merge lp:~tpeeters/ubuntu-ui-toolkit/tabfix into lp:ubuntu-ui-toolkit

Proposed by Tim Peeters
Status: Merged
Approved by: Zsombor Egri
Approved revision: 469
Merged at revision: 480
Proposed branch: lp:~tpeeters/ubuntu-ui-toolkit/tabfix
Merge into: lp:ubuntu-ui-toolkit
Diff against target: 483 lines (+236/-83)
6 files modified
modules/Ubuntu/Components/Tab.qml (+2/-0)
modules/Ubuntu/Components/Tabs.qml (+50/-11)
tests/resources/tabs/MyCustomPage.qml (+50/-0)
tests/resources/tabs/Tabs.qml (+99/-0)
themes/Ambiance/qmltheme/NewTabBar.qml (+30/-19)
themes/Ambiance/qmltheme/NewTabsDelegate.qml (+5/-53)
To merge this branch: bzr merge lp:~tpeeters/ubuntu-ui-toolkit/tabfix
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Approve
Zsombor Egri Approve
Review via email: mp+161902@code.launchpad.net

Commit message

Remove VisualItemModel from Tabs internals, and make it work with a Repeater to define the tabs.

Description of the change

Remove VisualItemModel from Tabs internals, and make it work with a Repeater to define the tabs.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Zsombor Egri (zsombi) wrote :

You have not documented the Repeater limitation in the Tabs.

review: Needs Fixing
Revision history for this message
Zsombor Egri (zsombi) wrote :

good to go now

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'modules/Ubuntu/Components/Tab.qml'
--- modules/Ubuntu/Components/Tab.qml 2013-04-03 12:21:17 +0000
+++ modules/Ubuntu/Components/Tab.qml 2013-05-07 11:26:29 +0000
@@ -59,4 +59,6 @@
59 */59 */
60 active: parentNode && parentNode.active &&60 active: parentNode && parentNode.active &&
61 parentNode.hasOwnProperty("selectedTab") && parentNode.selectedTab === tab61 parentNode.hasOwnProperty("selectedTab") && parentNode.selectedTab === tab
62
63 visible: active
62}64}
6365
=== modified file 'modules/Ubuntu/Components/Tabs.qml'
--- modules/Ubuntu/Components/Tabs.qml 2013-04-08 13:32:34 +0000
+++ modules/Ubuntu/Components/Tabs.qml 2013-05-07 11:26:29 +0000
@@ -90,6 +90,10 @@
9090
91 \endqml91 \endqml
92 As the example above shows, an external \l Page inside a \l Tab can be loaded using a Loader.92 As the example above shows, an external \l Page inside a \l Tab can be loaded using a Loader.
93
94 It is possible to use a Repeater to generate tabs, but when doing so, ensure that the Repeater
95 is declared inside the Tabs at the end, because otherwise the shuffling of
96 the order of children by the Repeater can cause incorrect ordering of the tabs.
93*/97*/
9498
95PageTreeNode {99PageTreeNode {
@@ -104,13 +108,13 @@
104 The first tab is 0, and -1 means that no tab is selected.108 The first tab is 0, and -1 means that no tab is selected.
105 The initial value is 0 if Tabs has contents, or -1 otherwise.109 The initial value is 0 if Tabs has contents, or -1 otherwise.
106 */110 */
107 property int selectedTabIndex: tabsModel.count > 0 ? 0 : -1111 property int selectedTabIndex: tabs.__tabs.length > 0 ? 0 : -1
108112
109 /*!113 /*!
110 \preliminary114 \preliminary
111 The currently selected tab.115 The currently selected tab.
112 */116 */
113 readonly property Tab selectedTab: (selectedTabIndex < 0) || (tabsModel.count <= selectedTabIndex) ?117 readonly property Tab selectedTab: (selectedTabIndex < 0) || (__tabs.length <= selectedTabIndex) ?
114 null : __tabs[selectedTabIndex]118 null : __tabs[selectedTabIndex]
115119
116 /*!120 /*!
@@ -139,21 +143,56 @@
139 "Pages will automatically update the toolbar when activated. "+143 "Pages will automatically update the toolbar when activated. "+
140 "See CHANGES file, and use toolbar.tools instead when needed.");144 "See CHANGES file, and use toolbar.tools instead when needed.");
141145
142146 /*!
143 // FIXME: Using the VisualItemModel as a workaround for this bug:147 \internal
144 // "theming: contentItem does work when it is a VisualItemModel"148 Used by the delegate to create the tabs header.
145 // https://bugs.launchpad.net/tavastia/+bug/1080330149 */
146 // The workaround does not break the regular TabsDelegate.150 property alias __tabs: tabsModel.tabList
147 /*! \internal */151
148 default property alias __tabs: tabsModel.children152 /*!
153 Children are placed in a separate item that has functionality to extract the Tab items.
154 \qmlproperty list<Item> tabChildren
155 */
156 default property alias tabChildren: tabsModel.children
157
158 /*!
159 Used by the tabs delegate to update the tabs header with the titles of all the tabs.
160 This signal is used in an intermediate step in transitioning the tabs to a new
161 implementation and may be removed in the future.
162 */
163 signal modelChanged()
149164
150 /*!165 /*!
151 \internal166 \internal
152 required by NewTabsDelegate167 required by NewTabsDelegate
153 */168 */
154 property alias __tabsModel: tabsModel169 Item {
155 VisualItemModel {170 anchors.fill: parent
156 id: tabsModel171 id: tabsModel
172
173 property var tabList: []
174 onChildrenChanged: {
175 updateTabList();
176 }
177
178 function updateTabList() {
179 var list = [];
180 for (var i=0; i < children.length; i++) {
181 if (isTab(tabsModel.children[i])) list.push(tabsModel.children[i]);
182 }
183 tabList = list;
184 tabs.modelChanged();
185 }
186
187 function isTab(item) {
188 if (item && item.hasOwnProperty("__isPageTreeNode")
189 && item.__isPageTreeNode && item.hasOwnProperty("title")
190 && item.hasOwnProperty("page")) {
191 return true;
192 } else {
193 return false;
194 }
195 }
157 }196 }
158197
159 /*! \internal */198 /*! \internal */
160199
=== added directory 'tests/resources/tabs'
=== added file 'tests/resources/tabs/MyCustomPage.qml'
--- tests/resources/tabs/MyCustomPage.qml 1970-01-01 00:00:00 +0000
+++ tests/resources/tabs/MyCustomPage.qml 2013-05-07 11:26:29 +0000
@@ -0,0 +1,50 @@
1/*
2 * Copyright 2012 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.0
18import Ubuntu.Components 0.1
19
20Page {
21 title: i18n.tr("My custom page")
22
23 Flickable {
24 anchors.fill: parent
25 contentHeight: parent.height + units.gu(10)
26 Label {
27 anchors {
28 top: parent.top
29 topMargin: units.gu(16)
30 horizontalCenter: parent.horizontalCenter
31 }
32
33 text: i18n.tr("This is an external page\nwith a locked toolbar.")
34 color: "#757373"
35 }
36 }
37
38 tools: ToolbarActions {
39 Action {
40 text: "action 1"
41 iconSource: Qt.resolvedUrl("call_icon.png")
42 }
43 Action {
44 text: "action 2"
45 iconSource: Qt.resolvedUrl("call_icon.png")
46 }
47 opened: true
48 locked: true
49 }
50}
051
=== added file 'tests/resources/tabs/Tabs.qml'
--- tests/resources/tabs/Tabs.qml 1970-01-01 00:00:00 +0000
+++ tests/resources/tabs/Tabs.qml 2013-05-07 11:26:29 +0000
@@ -0,0 +1,99 @@
1/*
2 * Copyright 2012 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.0
18import Ubuntu.Components 0.1
19import Ubuntu.Components.ListItems 0.1 as ListItem
20
21MainView {
22 width: 800
23 height: 600
24 Tabs {
25 id: tabs
26 selectedTabIndex: 0
27 Item {
28 // does this mess up stuff? nope.
29 }
30
31 Tab {
32 title: i18n.tr("Simple page")
33 page: Page {
34 Label {
35 id: label
36 anchors.centerIn: parent
37 text: "A centered label"
38 }
39 tools: ToolbarActions {
40 Action {
41 text: "action"
42 iconSource: "call_icon.png"
43 onTriggered: print("action triggered")
44 }
45 }
46 }
47 }
48 Repeater {
49 model: 3
50 Tab {
51 title: "Extra " + index
52 page: Page {
53 Column {
54 anchors.centerIn: parent
55 width: units.gu(40)
56 Label {
57 anchors {
58 left: parent.left
59 right: parent.right
60 }
61 text: "Extra tab number "+index
62 }
63 Button {
64 anchors {
65 left: parent.left
66 right: parent.right
67 }
68 text: "Previous"
69 onClicked: if (tabs.selectedTabIndex > 0) tabs.selectedTabIndex--
70 }
71 }
72 }
73 }
74 }
75 Tab {
76 id: externalTab
77 title: i18n.tr("External")
78 page: Loader {
79 parent: externalTab
80 anchors.fill: parent
81 source: (tabs.selectedTab === externalTab) ? Qt.resolvedUrl("MyCustomPage.qml") : ""
82 }
83 }
84 Tab {
85 title: i18n.tr("List view")
86 page: Page {
87 ListView {
88 clip: true
89 anchors.fill: parent
90 model: 20
91 delegate: ListItem.Standard {
92 icon: Qt.resolvedUrl("call_icon.png")
93 text: "Item "+modelData
94 }
95 }
96 }
97 }
98 }
99}
0100
=== added file 'tests/resources/tabs/call_icon@8.png'
1Binary files tests/resources/tabs/call_icon@8.png 1970-01-01 00:00:00 +0000 and tests/resources/tabs/call_icon@8.png 2013-05-07 11:26:29 +0000 differ101Binary files tests/resources/tabs/call_icon@8.png 1970-01-01 00:00:00 +0000 and tests/resources/tabs/call_icon@8.png 2013-05-07 11:26:29 +0000 differ
=== modified file 'themes/Ambiance/qmltheme/NewTabBar.qml'
--- themes/Ambiance/qmltheme/NewTabBar.qml 2013-05-02 00:57:01 +0000
+++ themes/Ambiance/qmltheme/NewTabBar.qml 2013-05-07 11:26:29 +0000
@@ -66,6 +66,7 @@
66 Connections {66 Connections {
67 target: tabs67 target: tabs
68 onSelectedTabIndexChanged: buttonView.selectButton(tabs.selectedTabIndex)68 onSelectedTabIndexChanged: buttonView.selectButton(tabs.selectedTabIndex)
69 onModelChanged: buttonView.selectButton(tabs.selectedTabIndex)
69 }70 }
7071
71 Component {72 Component {
@@ -79,9 +80,17 @@
79 width: childrenRect.width80 width: childrenRect.width
80 property int rowNumber: modelData81 property int rowNumber: modelData
8182
83 Component.onCompleted: {
84 if (rowNumber === 0) {
85 buttonView.buttonRow1 = theRow;
86 } else {
87 buttonView.buttonRow2 = theRow;
88 }
89 }
90
82 Repeater {91 Repeater {
83 id: repeater92 id: repeater
84 model: tabs.__tabsModel.children93 model: tabs.__tabs
8594
86 AbstractButton {95 AbstractButton {
87 id: button96 id: button
@@ -97,7 +106,6 @@
97 property bool selected: (tabBar.active && buttonView.needsScrolling) ? tabs.selectedTabIndex === index : buttonView.selectedButtonIndex === button.buttonIndex106 property bool selected: (tabBar.active && buttonView.needsScrolling) ? tabs.selectedTabIndex === index : buttonView.selectedButtonIndex === button.buttonIndex
98 property real offset: theRow.rowNumber + 1 - button.x / theRow.width;107 property real offset: theRow.rowNumber + 1 - button.x / theRow.width;
99 property int buttonIndex: index + theRow.rowNumber*repeater.count108 property int buttonIndex: index + theRow.rowNumber*repeater.count
100 Component.onCompleted: buttonView.buttons.push(button)
101109
102 // Use opacity 0 to hide instead of setting visibility to false in order to110 // Use opacity 0 to hide instead of setting visibility to false in order to
103 // make fading work well, and not to mess up width/offset computations111 // make fading work well, and not to mess up width/offset computations
@@ -109,7 +117,7 @@
109117
110 // When we don't need scrolling, we want to avoid showing a button that is fading118 // When we don't need scrolling, we want to avoid showing a button that is fading
111 // while sliding in from the right side when a new button was selected119 // while sliding in from the right side when a new button was selected
112 var numTabs = tabs.__tabsModel.count;120 var numTabs = tabs.__tabs.length;
113 var minimum = buttonView.selectedButtonIndex;121 var minimum = buttonView.selectedButtonIndex;
114 var maximum = buttonView.selectedButtonIndex + numTabs - 1;122 var maximum = buttonView.selectedButtonIndex + numTabs - 1;
115 if (MathUtils.clamp(buttonIndex, minimum, maximum) === buttonIndex) return true;123 if (MathUtils.clamp(buttonIndex, minimum, maximum) === buttonIndex) return true;
@@ -176,7 +184,7 @@
176 // Select this button184 // Select this button
177 function select() {185 function select() {
178 buttonView.selectedButtonIndex = button.buttonIndex;186 buttonView.selectedButtonIndex = button.buttonIndex;
179 buttonView.updateOffset();187 buttonView.updateOffset(button.offset);
180 }188 }
181 }189 }
182 }190 }
@@ -192,12 +200,14 @@
192 }200 }
193201
194 // set to the width of one tabButtonRow in Component.onCompleted.202 // set to the width of one tabButtonRow in Component.onCompleted.
195 property real buttonRowWidth203 property real buttonRowWidth: buttonRow1 ? buttonRow1.width : 0
196204
197 property var buttons: []205 // set by the delegate when the components are completed.
206 property Row buttonRow1
207 property Row buttonRow2
198208
199 // Track which button was last clicked209 // Track which button was last clicked
200 property int selectedButtonIndex: 0210 property int selectedButtonIndex: -1
201211
202 delegate: tabButtonRow212 delegate: tabButtonRow
203 model: 2 // The second buttonRow shows the buttons that disappear on the left213 model: 2 // The second buttonRow shows the buttons that disappear on the left
@@ -222,21 +232,23 @@
222232
223 // Select the closest of the two buttons that represent the given tab index233 // Select the closest of the two buttons that represent the given tab index
224 function selectButton(tabIndex) {234 function selectButton(tabIndex) {
225 var b1 = buttons[tabIndex];235 if (tabIndex < 0 || tabIndex >= tabs.__tabs.length) return;
226 var b2 = buttons[tabIndex + tabs.__tabsModel.children.length];236 if (buttonView.buttonRow1 && buttonView.buttonRow2) {
237 var b1 = buttonView.buttonRow1.children[tabIndex];
238 var b2 = buttonView.buttonRow2.children[tabIndex];
227239
228 // find the button with the nearest offset240 // find the button with the nearest offset
229 var d1 = cyclicDistance(b1.offset, buttonView.offset, 2);241 var d1 = cyclicDistance(b1.offset, buttonView.offset, 2);
230 var d2 = cyclicDistance(b2.offset, buttonView.offset, 2);242 var d2 = cyclicDistance(b2.offset, buttonView.offset, 2);
231 if (d1 < d2) {243 if (d1 < d2) {
232 b1.select();244 b1.select();
233 } else {245 } else {
234 b2.select();246 b2.select();
247 }
235 }248 }
236 }249 }
237250
238 function updateOffset() {251 function updateOffset(newOffset) {
239 var newOffset = buttonView.buttons[buttonView.selectedButtonIndex].offset;
240 if (offset - newOffset < -1) newOffset = newOffset - 2;252 if (offset - newOffset < -1) newOffset = newOffset - 2;
241 offset = newOffset;253 offset = newOffset;
242 }254 }
@@ -250,7 +262,6 @@
250262
251 Component.onCompleted: {263 Component.onCompleted: {
252 selectButton(tabs.selectedTabIndex);264 selectButton(tabs.selectedTabIndex);
253 buttonRowWidth = currentItem.width;
254 }265 }
255266
256 onDragEnded: activatingTimer.stop()267 onDragEnded: activatingTimer.stop()
257268
=== modified file 'themes/Ambiance/qmltheme/NewTabsDelegate.qml'
--- themes/Ambiance/qmltheme/NewTabsDelegate.qml 2013-04-30 23:55:32 +0000
+++ themes/Ambiance/qmltheme/NewTabsDelegate.qml 2013-05-07 11:26:29 +0000
@@ -32,22 +32,23 @@
32 the next/previous tab.32 the next/previous tab.
33 */33 */
34 property bool swipeToSwitchTabs34 property bool swipeToSwitchTabs
35 onSwipeToSwitchTabsChanged: print("swipeToSwitchTabs property is DEPRECATED. Please do not rely on swiping the Page's contents to switch tabs, this functionality will be removed.")35 /*!
36 \deprecated
37 \internal
38 */
39 onSwipeToSwitchTabsChanged: print("swipeToSwitchTabs property is DEPRECATED.")
3640
37 property color headerTextColor41 property color headerTextColor
38 property color headerTextSelectedColor42 property color headerTextSelectedColor
39 property real headerTextOpacity43 property real headerTextOpacity
40 property real headerTextSelectedOpacity44 property real headerTextSelectedOpacity
41
42 property int headerTextFadeDuration45 property int headerTextFadeDuration
43 property string headerFontSize46 property string headerFontSize
44 property int headerFontWeight47 property int headerFontWeight
45 property real headerTextLeftMargin48 property real headerTextLeftMargin
46 property real headerTextRightMargin49 property real headerTextRightMargin
47 property real headerTextBottomMargin50 property real headerTextBottomMargin
48
49 property url indicatorImageSource51 property url indicatorImageSource
50
51 property real tabBarHeight52 property real tabBarHeight
5253
53 /*!54 /*!
@@ -64,8 +65,6 @@
64 id: tabsDelegate65 id: tabsDelegate
65 anchors.fill: parent66 anchors.fill: parent
6667
67 property VisualItemModel tabModel: item.__tabsModel
68
69 // use theTabs property because item gives problems in the loader68 // use theTabs property because item gives problems in the loader
70 property Tabs theTabs: item69 property Tabs theTabs: item
71 property Component headerContents: Component {70 property Component headerContents: Component {
@@ -94,54 +93,7 @@
94 }93 }
95 }94 }
9695
97 ListView {
98 id: tabView
99 anchors.fill: parent
100
101 interactive: tabsDelegate.swipeToSwitchTabs
102 model: tabsDelegate.tabModel
103 onModelChanged: tabView.updatePages()
104 currentIndex: item.selectedTabIndex
105 onCurrentIndexChanged: if (item.__tabsModel.count > 0) item.selectedTabIndex = tabView.currentIndex
106
107 orientation: ListView.Horizontal
108 snapMode: ListView.SnapOneItem
109 boundsBehavior: Flickable.DragOverBounds
110 highlightFollowsCurrentItem: true
111 highlightRangeMode: ListView.StrictlyEnforceRange
112
113 function updatePages() {
114 if (!tabsDelegate.tabModel) return; // not initialized yet
115
116 var tabList = tabsDelegate.tabModel.children
117 var tab;
118 for (var i=0; i < tabList.length; i++) {
119 tab = tabList[i];
120 tab.anchors.fill = undefined;
121 tab.width = tabView.width;
122 tab.height = tabView.height
123 }
124 tabView.updateSelectedTabIndex();
125 }
126
127 function updateSelectedTabIndex() {
128 if (tabView.currentIndex === item.selectedTabIndex) return;
129 // The view is automatically updated, because highlightFollowsCurrentItem
130 tabView.currentIndex = item.selectedTabIndex;
131 }
132 }
133
134 Connections {
135 target: item
136 onSelectedTabIndexChanged: {
137 tabView.updateSelectedTabIndex();
138 }
139 }
140
141 onWidthChanged: tabView.updatePages();
142 onHeightChanged: tabView.updatePages();
143 Component.onCompleted: {96 Component.onCompleted: {
144 item.__headerContents = headerContents;97 item.__headerContents = headerContents;
145 tabView.updatePages();
146 }98 }
147}99}

Subscribers

People subscribed via source and target branches

to status/vote changes: