Merge lp:~zsombi/ubuntu-ui-toolkit/dynamic-tabs-reloaded into lp:ubuntu-ui-toolkit/staging

Proposed by Zsombor Egri
Status: Merged
Approved by: Tim Peeters
Approved revision: 913
Merged at revision: 1003
Proposed branch: lp:~zsombi/ubuntu-ui-toolkit/dynamic-tabs-reloaded
Merge into: lp:ubuntu-ui-toolkit/staging
Diff against target: 865 lines (+586/-31)
10 files modified
CHANGES (+1/-0)
components.api (+6/-1)
modules/Ubuntu/Components/Tab.qml (+0/-7)
modules/Ubuntu/Components/Tabs.qml (+292/-20)
modules/Ubuntu/Components/Themes/Ambiance/TabBarStyle.qml (+9/-2)
modules/Ubuntu/Components/plugin/quickutils.cpp (+15/-0)
modules/Ubuntu/Components/plugin/quickutils.h (+1/-0)
tests/resources/navigation/Tabs.qml (+90/-1)
tests/unit_x11/tst_components/ExternalTab.qml (+21/-0)
tests/unit_x11/tst_components/tst_tabs.qml (+151/-0)
To merge this branch: bzr merge lp:~zsombi/ubuntu-ui-toolkit/dynamic-tabs-reloaded
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Approve
Tim Peeters Approve
Cris Dywan provided moveitembefore docs are fixed Pending
Review via email: mp+215156@code.launchpad.net

This proposal supersedes a proposal from 2013-12-19.

Commit message

Adding dynamic tab handling functionality to Tabs

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
Tim Peeters (tpeeters) wrote : Posted in a previous version of this proposal

637 + * Copyright 2012 Canonical Ltd.

2013

Revision history for this message
Cris Dywan (kalikiana) wrote : Posted in a previous version of this proposal

I'm seeing a number of errors while using the tabbar test:

modules/Ubuntu/Components/Tabs.qml:598: TypeError: Cannot call method 'indexOf' of undefined
coming from connectToRepeaters
modules/Ubuntu/Components/Tabs.qml:360: ReferenceError: MathUtils is not defined
coming from insertTab
modules/Ubuntu/Components/Themes/Ambiance/TabBarStyle.qml:119: TypeError: Cannot read property of null
coming from anchors.top on the Repeater, not sure what this is

review: Needs Fixing
Revision history for this message
Cris Dywan (kalikiana) wrote : Posted in a previous version of this proposal

I'm liking the documentation which is quite decent. I love that the API keeps the different types of tabs behind the scenes.

> // but move only if there are more than on eitems in the list
Typo.

// QQuickItem *parentItem = item->parentItem();
Please remove this one.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
Zsombor Egri (zsombi) wrote : Posted in a previous version of this proposal

> 637 + * Copyright 2012 Canonical Ltd.
>
> 2013

It's 2014 now :)

Revision history for this message
Zsombor Egri (zsombi) wrote : Posted in a previous version of this proposal

> I'm liking the documentation which is quite decent. I love that the API keeps
> the different types of tabs behind the scenes.
>
> > // but move only if there are more than on eitems in the list
> Typo.
>
> // QQuickItem *parentItem = item->parentItem();
> Please remove this one.

Fixed in revno 903

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
Tim Peeters (tpeeters) wrote : Posted in a previous version of this proposal

+/*!
463 + * \internal
464 + * Moves a given item to the specified index in its parent's child list.
465 + */
466 +void QuickUtils::moveItemBefore(QQuickItem *item, QQuickItem *before)

description doesn't exactly match the function specs.
* Moves a given item before the specified item in their parent's child list
?

Revision history for this message
Cris Dywan (kalikiana) wrote : Posted in a previous version of this proposal

That one escaped me; I'll give green light with the above sentence fixed.

review: Approve (provided moveitembefore docs are fixed)
Revision history for this message
Cris Dywan (kalikiana) wrote : Posted in a previous version of this proposal
Revision history for this message
Tim Peeters (tpeeters) wrote : Posted in a previous version of this proposal

note that you didn't fix the doc yet

Revision history for this message
Tim Peeters (tpeeters) wrote : Posted in a previous version of this proposal

I changed the status back to needs review, because we need to re-do the CI@home with qt52.

Revision history for this message
Zsombor Egri (zsombi) wrote : Posted in a previous version of this proposal

> note that you didn't fix the doc yet

Sorry, done now.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Tim Peeters (tpeeters) wrote : Posted in a previous version of this proposal

> modules/Ubuntu/Components/Tabs.qml:360: ReferenceError: MathUtils is not
> defined

This is still broken. Please add the include in Tabs.qml.

review: Needs Fixing
Revision history for this message
Tim Peeters (tpeeters) wrote : Posted in a previous version of this proposal

> I'm seeing a number of errors while using the tabbar test:
>
> modules/Ubuntu/Components/Tabs.qml:598: TypeError: Cannot call method
> 'indexOf' of undefined
> coming from connectToRepeaters
> modules/Ubuntu/Components/Tabs.qml:360: ReferenceError: MathUtils is not
> defined
> coming from insertTab
> modules/Ubuntu/Components/Themes/Ambiance/TabBarStyle.qml:119: TypeError:
> Cannot read property of null
> coming from anchors.top on the Repeater, not sure what this is

please double-check that all these are fixed

Revision history for this message
Zsombor Egri (zsombi) wrote : Posted in a previous version of this proposal

>
> > modules/Ubuntu/Components/Tabs.qml:360: ReferenceError: MathUtils is not
> > defined
>
> This is still broken. Please add the include in Tabs.qml.

I'm wondering why was it passing till now???

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
Tim Peeters (tpeeters) wrote : Posted in a previous version of this proposal

ok

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
Tim Peeters (tpeeters) wrote :

still good

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

PASSED: Continuous integration, rev:913
http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-ci/11/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-trusty-touch/176
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-trusty/4741
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-trusty-amd64-ci/11
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-trusty-armhf-ci/11
        deb: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-trusty-armhf-ci/11/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-trusty-i386-ci/11
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-runner-mako/169
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-trusty-armhf/4324
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-trusty-armhf/4324/artifact/work/output/*zip*/output.zip
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/5896
    SUCCESS: http://jenkins.qa.ubuntu.com/job/autopilot-testrunner-otto-trusty/4093
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-trusty-amd64/4870
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-trusty-amd64/4870/artifact/work/output/*zip*/output.zip

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/ubuntu-sdk-team-ubuntu-ui-toolkit-staging-ci/11/rebuild

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'CHANGES'
--- CHANGES 2014-02-24 22:03:55 +0000
+++ CHANGES 2014-04-10 11:35:42 +0000
@@ -9,6 +9,7 @@
99
10API Changes10API Changes
11***********11***********
12* DEPRECATED IN: Tabs: default property list<Item> tabChildren
12* ADDED IN: PickerDelegate: readonly property Picker picker13* ADDED IN: PickerDelegate: readonly property Picker picker
13* CHANGED IN: OptionSelector: readonly property bool currentlyExpanded TO property bool currentlyExpanded14* CHANGED IN: OptionSelector: readonly property bool currentlyExpanded TO property bool currentlyExpanded
14* CHANGED IN: ItemSelector: readonly property bool currentlyExpanded TO property bool currentlyExpanded15* CHANGED IN: ItemSelector: readonly property bool currentlyExpanded TO property bool currentlyExpanded
1516
=== modified file 'components.api'
--- components.api 2014-04-10 05:14:42 +0000
+++ components.api 2014-04-10 11:35:42 +0000
@@ -417,9 +417,14 @@
417 readonly property Tab selectedTab417 readonly property Tab selectedTab
418 readonly property Item currentPage418 readonly property Item currentPage
419 property TabBar tabBar419 property TabBar tabBar
420 default property list<Item> tabChildren420 property list<Item> tabChildren
421 readonly property int count421 readonly property int count
422 signal modelChanged()422 signal modelChanged()
423 function addTab(title, tab)
424 function insertTab(index, title, tab)
425 function getTab(index)
426 function moveTab(from, to)
427 function removeTab(index)
423modules/Ubuntu/Components/TextArea.qml428modules/Ubuntu/Components/TextArea.qml
424StyledItem429StyledItem
425 property bool highlighted430 property bool highlighted
426431
=== modified file 'modules/Ubuntu/Components/Tab.qml'
--- modules/Ubuntu/Components/Tab.qml 2013-11-07 07:26:01 +0000
+++ modules/Ubuntu/Components/Tab.qml 2014-04-10 11:35:42 +0000
@@ -105,12 +105,5 @@
105 Tab is destroyed upon removal.105 Tab is destroyed upon removal.
106 */106 */
107 property bool dynamic: false107 property bool dynamic: false
108
109 /*
110 This flag is used by the Tabs to determine whether the pre-declared Tab was removed
111 from the Tabs model or not. The flag guards adding back pre-declared tabs upon Tabs
112 component stack (children) change.
113 */
114 property bool removedFromTabs: false
115 }108 }
116}109}
117110
=== modified file 'modules/Ubuntu/Components/Tabs.qml'
--- modules/Ubuntu/Components/Tabs.qml 2014-04-08 12:38:35 +0000
+++ modules/Ubuntu/Components/Tabs.qml 2014-04-10 11:35:42 +0000
@@ -15,6 +15,7 @@
15 */15 */
1616
17import QtQuick 2.017import QtQuick 2.0
18import "mathUtils.js" as MathUtils
1819
19/*!20/*!
20 \qmltype Tabs21 \qmltype Tabs
@@ -149,6 +150,110 @@
149 }150 }
150 }151 }
151 \endqml152 \endqml
153
154 \section2 Dynamic tabs
155 So far all Tab elements were pre-declared, but there can be situations when
156 tabs need to be added dynamically. There are two ways to solve this, depending
157 on the output needed.
158
159 \section3 Using Repeaters
160 A Repeater can be used to create the necessary tabs depending on a given model.
161 In this way the number of tabs will be driven by the model itself.
162 An example of such a dynamic tab:
163 \qml
164 // DynamicTab.qml
165 import QtQuick 2.0
166 import Ubuntu.Components 0.1
167
168 Tabs {
169 property alias model: tabRepeater.model
170 Repeater {
171 id: tabRepeater
172 model: 5
173 Tab {
174 title: "Tab #" + index
175 page: Page {
176 // [...]
177 }
178 }
179 }
180 }
181 \endqml
182 Note that in the example above the Tabs will be re-created each time the model
183 changes. This will cause state losing of each Tab, which depending on the
184 content type can be solved at some extent using StateSaver. Using a Loader
185 or specifying the Tab instance/component in the model the state can be preserved,
186 however may increase code complexity.
187
188 \section3 Dynamic tabs
189 Tabs provides functions to add Tab elements dynamically on runtime, without
190 destroying the state of the existing tabs. You can add, move and remove any
191 kind of Tab element, pre-declared or dynamically created ones. When removing
192 pre-declared tabs, those will all be held back and hidden by Tabs, and can be
193 added back any time either to the same or to a different position.
194
195 \qml
196 import QtQuick 2.0
197 import Ubuntu.Components 0.1
198
199 MainView {
200 width: units.gu(40)
201 height: units.gu(71)
202
203 Component {
204 id: dynamicTab
205 Tab {
206 page: Page {
207 Label {
208 text: title
209 anchors.centerIn: parent
210 }
211 }
212 }
213 }
214 Tabs {
215 id: tabs
216 Tab {
217 title: "Main tab"
218 page: Page {
219 toolbar: ToolbarItems {
220 ToolbarButton {
221 text: "remove predeclared"
222 onTriggered: tabs.removeTab(preDeclared.index)
223 }
224 ToolbarButton {
225 text: "add new"
226 onTriggered: tabs.addTab("New tab", dynamicTab)
227 }
228 ToolbarButton {
229 text: "insert predeclared"
230 onTriggered: tabs.insertTab("", 0)
231 }
232 }
233 }
234 }
235 Tab {
236 id: preDeclared
237 title: "Pre-declared tab"
238 page: Page {
239 Label {
240 text: "This is a predeclared tab at index #" + index
241 anchors.centerIn: parent
242 }
243 }
244 }
245 }
246 }
247 \endqml
248
249 \section3 Using Repeater and functions together
250 Repeaters re-create their delegates as many times the model changes. Tabs added
251 or moved in between the tabs maintained by the Repeater, as well as reordered
252 through the Tabs functions will be re-arranged once the Repeater's model changes.
253 This should be taken into account when designing the application, and the use
254 of Repeater and functions toghether should be avoided if possible, or at least
255 Repeater should always add tabs to te tail of the tab stack, and no tab insertion
256 happens in that area.
152*/257*/
153PageTreeNode {258PageTreeNode {
154 id: tabs259 id: tabs
@@ -186,10 +291,14 @@
186 }291 }
187292
188 /*!293 /*!
189 Children are placed in a separate item that has functionality to extract the Tab items.294 \deprecated
295 Children are placed in a separate item that has functionality to extract
296 the Tab items.
297 Note: this property is deprecated. Tab components are directly parented
298 to Tabs' data property.
190 \qmlproperty list<Item> tabChildren299 \qmlproperty list<Item> tabChildren
191 */300 */
192 default property alias tabChildren: tabStack.data301 property alias tabChildren: tabs.data
193302
194 /*!303 /*!
195 \qmlproperty int count304 \qmlproperty int count
@@ -206,36 +315,180 @@
206 signal modelChanged()315 signal modelChanged()
207316
208 /*!317 /*!
318 Appends a Tab dynamically to the list of tabs. The \a title specifies the
319 title of the Tab. The \a tab can be either a Component, a URL to a Tab
320 component to be loaded or an instance of a pre-declared tab that has been
321 previously removed. The Tab's title will be replaced with the given \a title,
322 unless the value is an empty string or undefined.
323 Returns the instance of the added Tab.
324 */
325 function addTab(title, tab) {
326 return insertTab(count, title, tab);
327 }
328
329 /*!
330 Inserts a Tab at the given index. If the \a index is less or equal than 0,
331 the Tab will be added to the front, and to the end of the tab stack in case
332 the \a index is greater than \l count. \a title and \a tab are used in the
333 same way as with \l addTab().
334 Returns the instance of the inserted Tab.
335 */
336 function insertTab(index, title, tab) {
337 // check if the given component is a Tab instance
338 var tabObject = null;
339
340 if (typeof tab === "string") {
341 // we have a URL
342 var tabComponent = Qt.createComponent(tab);
343 if (tabComponent.status === Component.Error) {
344 console.error(tabComponent.errorString());
345 return null;
346 }
347 tabObject = tabComponent.createObject();
348 tabObject.__protected.dynamic = true;
349 } else if (tab.hasOwnProperty("createObject")) {
350 // we have a Component
351 tabObject = tab.createObject();
352 tabObject.__protected.dynamic = true;
353 } else if (tab.hasOwnProperty("parent") && tab.parent === trashedTabs) {
354 // we have a pre-declared tab that has been removed
355 tabObject = tab;
356 } else {
357 console.error(i18n.tr("The object is not a URL, Component or a removed Tab: ") + tab);
358 return null;
359 }
360
361 // fix title
362 if (title !== undefined && title !== "") {
363 tabObject.title = title;
364 }
365
366 // insert the created tab into the model
367 index = MathUtils.clamp(index, 0, count);
368 tabObject.__protected.inserted = true;
369 tabObject.__protected.index = index;
370 tabsModel.insertTab(tabObject, index);
371 if (tabs.selectedTabIndex >= index) {
372 // move the selected index to the next index
373 tabs.selectedTabIndex += 1;
374 } else {
375 internal.sync();
376 }
377 return tabObject;
378 }
379
380 /*!
381 The function returns the Tab from the given \a index, or null if the \a index
382 is invalid (less than \c 0 and greater than \l count).
383 */
384 function getTab(index) {
385 return (index >=0) && (index < count) ? tabsModel.get(index).tab : null;
386 }
387
388 /*!
389 Moves the tab from the given \a from position to the position given in \a to.
390 Returns true if the indexes were in 0..\l count - 1 boundary and if the operation
391 succeeds, and false otherwise. The \l selectedTabIndex is updated if it is
392 affected by the move (it is equal with \a from or falls between \a from and
393 \a to indexes).
394 */
395 function moveTab(from, to) {
396 if (from < 0 || from >= count || to < 0 || to >= count || from === to) return false;
397 var tabFrom = tabsModel.get(from).tab;
398 var tabTo = tabsModel.get(to).tab;
399
400 // move tab
401 QuickUtils.moveItemBefore(tabFrom, tabTo);
402 tabsModel.updateTabList(tabs.children);
403
404 // fix selected tab
405 if (selectedTabIndex === from) {
406 selectedTabIndex = to;
407 } else if (selectedTabIndex >= Math.min(from, to) && selectedTabIndex <= Math.max(from, to)) {
408 selectedTabIndex--;
409 } else {
410 internal.sync();
411 }
412
413 return true;
414 }
415
416 /*!
417 Removes the Tab from the given \a index. Returns true if the \a index falls
418 into 0..\l count - 1 boundary and the operation succeeds, and false on error.
419 The function removes also the pre-declared tabs. These can be added back using
420 \l addTab or \l insertTab by specifying the instance of the Tab to be added as
421 component. The \l selectedTabIndex is updated if is affected by the removal
422 (it is identical or greater than the tab index to be removed).
423 */
424 function removeTab(index) {
425 if (index < 0 || index >= count) return false;
426 var tab = tabsModel.get(index).tab;
427 var activeIndex = (selectedTabIndex >= index) ? MathUtils.clamp(selectedTabIndex, 0, count - 2) : -1;
428
429 // remove from Tabs; Tabs children change will remove the tab from the model
430 tab.parent = null;
431 if (tab.__protected.dynamic) {
432 tab.destroy();
433 } else {
434 // pre-declared tab, mark it as removed, so we don't update it next time
435 // the tabs stack children is updated
436 tab.parent = trashedTabs;
437 }
438
439 // move active tab if needed
440 if (activeIndex >= 0 && activeIndex !== selectedTabIndex) {
441 selectedTabIndex = activeIndex;
442 } else {
443 internal.sync();
444 }
445
446 return true;
447 }
448
449 /*! \internal */
450 onChildrenChanged: {
451 internal.connectToRepeaters(tabs.children);
452 tabsModel.updateTabList(tabs.children);
453 }
454
455 /*!
209 \internal456 \internal
210 required by TabsStyle457 required by TabsStyle
211 */458 */
212 ListModel {459 ListModel {
213 id: tabsModel460 id: tabsModel
214461
462 property bool updateDisabled: false
463
215 function listModel(tab) {464 function listModel(tab) {
216 return {"title": tab.title, "tab": tab};465 return {"title": tab.title, "tab": tab};
217 }466 }
218467
219 function updateTabList(tabsList) {468 function updateTabList(tabsList) {
469 if (updateDisabled) return;
220 var offset = 0;470 var offset = 0;
221 var tabIndex;471 var tabIndex = -1;
222 for (var i in tabsList) {472 for (var i in tabsList) {
223 var tab = tabsList[i];473 var tab = tabsList[i];
224 if (internal.isTab(tab)) {474 if (internal.isTab(tab)) {
225 tabIndex = i - offset;475 tabIndex = i - offset;
226 // make sure we have the right parent476 // make sure we have the right parent
227 tab.parent = tabStack;477 tab.parent = tabs;
228478
229 if (!tab.__protected.inserted) {479 if (!tab.__protected.inserted) {
230 tab.__protected.index = tabIndex;480 tab.__protected.index = tabIndex;
231 tab.__protected.inserted = true;481 tab.__protected.inserted = true;
232 insert(tabIndex, listModel(tab));482 insert(tabIndex, listModel(tab));
233 } else if (!tab.__protected.removedFromTabs && tabsModel.count > tab.index) {483 } else {
234 get(tab.index).title = tab.title;484 get(tab.index).title = tab.title;
235 }485 }
236486
237 // always makes sure that tabsModel has the same order as tabsList487 // always makes sure that tabsModel has the same order as tabsList
238 move(tab.__protected.index, tabIndex, 1);488 // but move only if there is more than one item in the list
489 if (count > 1) {
490 move(tab.__protected.index, tabIndex, 1);
491 }
239 reindex();492 reindex();
240 } else {493 } else {
241 // keep track of children that are not tabs so that we compute494 // keep track of children that are not tabs so that we compute
@@ -243,6 +496,10 @@
243 offset += 1;496 offset += 1;
244 }497 }
245 }498 }
499 // remove deleted tabs, those should be at the end of the list by now
500 if ((tabIndex >= 0) && (tabIndex + 1) < count) {
501 remove(tabIndex + 1, count - tabIndex - 1);
502 }
246 internal.sync();503 internal.sync();
247 }504 }
248505
@@ -257,18 +514,31 @@
257 tab.__protected.index = i;514 tab.__protected.index = i;
258 }515 }
259 }516 }
517
518 function insertTab(tab, index) {
519 // fix index
520 if (index < 0) {
521 index = 0;
522 }
523 // get the tab before which the item will be inserted
524 var itemAtIndex = ((index >= 0) && (index < count)) ? get(index).tab : null;
525 // disable update only if we insert, append can keep the logic rolling
526 updateDisabled = (itemAtIndex !== null);
527 insert(index, listModel(tab));
528 tab.parent = tabs;
529 updateDisabled = false;
530 if (itemAtIndex) {
531 QuickUtils.moveItemBefore(tab, itemAtIndex);
532 updateTabList(tabs.children);
533 }
534 }
260 }535 }
261536
262 // FIXME: this component is not really needed, as it doesn't really bring any537 // invisible component stacking removed pre-declared components
263 // value; should be removed in a later MR
264 Item {538 Item {
265 anchors.fill: parent539 id: trashedTabs
266 id: tabStack540 visible: false
267541 opacity: 0.0
268 onChildrenChanged: {
269 internal.connectToRepeaters(tabStack.children);
270 tabsModel.updateTabList(tabStack.children);
271 }
272 }542 }
273543
274 /*544 /*
@@ -283,7 +553,7 @@
283 interval: 1553 interval: 1
284 running: false554 running: false
285 onTriggered: {555 onTriggered: {
286 tabsModel.updateTabList(tabStack.children);556 tabsModel.updateTabList(tabs.children);
287 internal.sync();557 internal.sync();
288 }558 }
289 }559 }
@@ -295,8 +565,8 @@
295 Binding {565 Binding {
296 target: tabBar566 target: tabBar
297 property: "animate"567 property: "animate"
298 when: internal.header && internal.header.hasOwnProperty("animate")568 when: (internal.header !== null) && internal.header.hasOwnProperty("animate")
299 value: internal.header.animate569 value: internal.header ? internal.header.animate : "false"
300 }570 }
301571
302 /*572 /*
@@ -332,7 +602,9 @@
332 function connectToRepeaters(children) {602 function connectToRepeaters(children) {
333 for (var i = 0; i < children.length; i++) {603 for (var i = 0; i < children.length; i++) {
334 var child = children[i];604 var child = children[i];
335 if (internal.isRepeater(child) && (internal.repeaters.indexOf(child) < 0)) {605 if (internal.isRepeater(child) &&
606 (internal.repeaters !== undefined) &&
607 (internal.repeaters.indexOf(child) < 0)) {
336 internal.connectRepeater(child);608 internal.connectRepeater(child);
337 }609 }
338 }610 }
@@ -355,7 +627,7 @@
355 https://bugreports.qt-project.org/browse/QTBUG-32438627 https://bugreports.qt-project.org/browse/QTBUG-32438
356 */628 */
357 function updateTabsModel() {629 function updateTabsModel() {
358 tabsModel.updateTabList(tabStack.children);630 tabsModel.updateTabList(tabs.children);
359 }631 }
360632
361 /*633 /*
362634
=== modified file 'modules/Ubuntu/Components/Themes/Ambiance/TabBarStyle.qml'
--- modules/Ubuntu/Components/Themes/Ambiance/TabBarStyle.qml 2014-04-08 12:38:35 +0000
+++ modules/Ubuntu/Components/Themes/Ambiance/TabBarStyle.qml 2014-04-10 11:35:42 +0000
@@ -116,8 +116,8 @@
116 AbstractButton {116 AbstractButton {
117 id: button117 id: button
118 anchors {118 anchors {
119 top: parent.top119 top: parent ? parent.top : undefined
120 bottom: parent.bottom120 bottom: parent ? parent.bottom: undefined
121 }121 }
122 width: text.paintedWidth + text.anchors.leftMargin + text.anchors.rightMargin122 width: text.paintedWidth + text.anchors.leftMargin + text.anchors.rightMargin
123123
@@ -150,6 +150,13 @@
150 return false;150 return false;
151 }151 }
152152
153 // update the offset of the buttonRow
154 onOffsetChanged: {
155 if (selected) {
156 buttonView.updateOffset(button.offset);
157 }
158 }
159
153 Behavior on opacity {160 Behavior on opacity {
154 NumberAnimation {161 NumberAnimation {
155 duration: headerTextFadeDuration162 duration: headerTextFadeDuration
156163
=== modified file 'modules/Ubuntu/Components/plugin/quickutils.cpp'
--- modules/Ubuntu/Components/plugin/quickutils.cpp 2014-03-20 15:46:28 +0000
+++ modules/Ubuntu/Components/plugin/quickutils.cpp 2014-04-10 11:35:42 +0000
@@ -119,6 +119,21 @@
119 return result.left(result.indexOf("_QML"));119 return result.left(result.indexOf("_QML"));
120}120}
121121
122/*!
123 * \internal
124 * Moves a given \a item before the \a other one in the object stack. Both \a item
125 * and \a other must have the same parent item.
126 */
127void QuickUtils::moveItemBefore(QQuickItem *item, QQuickItem *other)
128{
129 Q_ASSERT(item);
130 Q_ASSERT(item->parentItem());
131 if (other) {
132 Q_ASSERT(other->parentItem() == item->parentItem());
133 item->stackBefore(other);
134 }
135}
136
122137
123/*!138/*!
124 * \internal139 * \internal
125140
=== modified file 'modules/Ubuntu/Components/plugin/quickutils.h'
--- modules/Ubuntu/Components/plugin/quickutils.h 2014-03-20 15:46:28 +0000
+++ modules/Ubuntu/Components/plugin/quickutils.h 2014-04-10 11:35:42 +0000
@@ -42,6 +42,7 @@
42 QString inputMethodProvider() const;42 QString inputMethodProvider() const;
4343
44 Q_INVOKABLE static QString className(QObject *item);44 Q_INVOKABLE static QString className(QObject *item);
45 Q_INVOKABLE void moveItemBefore(QQuickItem *item, QQuickItem *before);
45 QObject* createQmlObject(const QUrl &url, QQmlEngine *engine);46 QObject* createQmlObject(const QUrl &url, QQmlEngine *engine);
4647
47Q_SIGNALS:48Q_SIGNALS:
4849
=== modified file 'tests/resources/navigation/Tabs.qml'
--- tests/resources/navigation/Tabs.qml 2014-04-07 10:03:39 +0000
+++ tests/resources/navigation/Tabs.qml 2014-04-10 11:35:42 +0000
@@ -19,9 +19,47 @@
19import Ubuntu.Components.ListItems 0.1 as ListItem19import Ubuntu.Components.ListItems 0.1 as ListItem
2020
21MainView {21MainView {
22 id: root
22 width: 80023 width: 800
23 height: 60024 height: 600
2425
26 property var repeaterModel: 3
27
28 Component {
29 id: dynamicTab
30 Tab {
31 page: Page {
32 Label {
33 text: title + " at index " + index
34 anchors.centerIn: parent
35 }
36 tools: ToolbarItems {
37 ToolbarButton {
38 text: "move @1"
39 onTriggered: {
40 print("MOVE TAB TO #1")
41 tabs.moveTab(index, 1)
42 }
43 }
44 ToolbarButton {
45 text: "remove me"
46 onTriggered: {
47 print("REMOVE CURENT TAB")
48 tabs.removeTab(index)
49 }
50 }
51 ToolbarButton {
52 text: "remove first"
53 onTriggered: {
54 print("REMOVE TAB AT #0")
55 tabs.removeTab(0)
56 }
57 }
58 }
59 }
60 }
61 }
62
25 Tabs {63 Tabs {
26 id: tabs64 id: tabs
27 selectedTabIndex: 065 selectedTabIndex: 0
@@ -31,6 +69,7 @@
3169
32 Tab {70 Tab {
33 id: simpleTab71 id: simpleTab
72 objectName: title
34 title: i18n.tr("Simple page #" + index)73 title: i18n.tr("Simple page #" + index)
35 page: Page {74 page: Page {
36 Row {75 Row {
@@ -55,13 +94,60 @@
55 iconSource: "call_icon.png"94 iconSource: "call_icon.png"
56 onTriggered: print("action triggered")95 onTriggered: print("action triggered")
57 }96 }
97 ToolbarButton {
98 text: "append"
99 onTriggered: {
100 print("APPEND TAB")
101 tabs.addTab("Appended tab", dynamicTab)
102 }
103 }
104 ToolbarButton {
105 text: "insert@1"
106 onTriggered: {
107 print("INSERT TAB TO #1")
108 tabs.insertTab(1, "Inserted tab", dynamicTab)
109 }
110 }
111 ToolbarButton {
112 text: "insert@2"
113 onTriggered: {
114 print("INSERT BETWEEN REPEATERS #1")
115 tabs.insertTab(2, "Between repeaters", dynamicTab)
116 }
117 }
118 ToolbarButton {
119 text: "insert@here"
120 onTriggered: {
121 print("INSERT AFTER ME")
122 tabs.insertTab(simpleTab.index, "Inserted tab", dynamicTab)
123 }
124 }
125 ToolbarButton {
126 text: "incRep"
127 onTriggered: {
128 print("INCREASE REPEATER MODEL")
129 root.repeaterModel += 1
130 }
131 }
132 ToolbarButton {
133 text: "remove last"
134 onTriggered: {
135 print("REMOVE LAST TAB")
136 tabs.removeTab(tabs.count - 1)
137 }
138 }
139 ToolbarButton {
140 text: "append predec"
141 onTriggered: tabs.addTab("Re-added ListView", listViewTab)
142 }
58 }143 }
59 }144 }
60 }145 }
61 Repeater {146 Repeater {
62 model: 3147 model: root.repeaterModel
63 Tab {148 Tab {
64 id: tab149 id: tab
150 objectName: title
65 title: "Extra #" + tab.index151 title: "Extra #" + tab.index
66 page: Page {152 page: Page {
67 Column {153 Column {
@@ -88,6 +174,7 @@
88 }174 }
89 Tab {175 Tab {
90 id: externalTab176 id: externalTab
177 objectName: title
91 title: i18n.tr("External #" + index)178 title: i18n.tr("External #" + index)
92 page: Loader {179 page: Loader {
93 parent: externalTab180 parent: externalTab
@@ -96,6 +183,8 @@
96 }183 }
97 }184 }
98 Tab {185 Tab {
186 id: listViewTab
187 objectName: title
99 title: i18n.tr("List view #" + index)188 title: i18n.tr("List view #" + index)
100 page: Page {189 page: Page {
101 ListView {190 ListView {
102191
=== added file 'tests/unit_x11/tst_components/ExternalTab.qml'
--- tests/unit_x11/tst_components/ExternalTab.qml 1970-01-01 00:00:00 +0000
+++ tests/unit_x11/tst_components/ExternalTab.qml 2014-04-10 11:35:42 +0000
@@ -0,0 +1,21 @@
1/*
2 * Copyright 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 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
20Tab {
21}
022
=== modified file 'tests/unit_x11/tst_components/tst_tabs.qml'
--- tests/unit_x11/tst_components/tst_tabs.qml 2014-01-13 12:43:12 +0000
+++ tests/unit_x11/tst_components/tst_tabs.qml 2014-04-10 11:35:42 +0000
@@ -27,6 +27,13 @@
27 id: emptyTabs27 id: emptyTabs
28 }28 }
2929
30 Component {
31 id: dynamicTab
32 Tab{
33 title: "OriginalTitle"
34 }
35 }
36
30 MainView {37 MainView {
31 id: mainView38 id: mainView
32 anchors.fill: parent39 anchors.fill: parent
@@ -424,5 +431,149 @@
424 mouseRelease(tabs.tabBar, tabs.tabBar.width/2, tabs.tabBar.height/2);431 mouseRelease(tabs.tabBar, tabs.tabBar.width/2, tabs.tabBar.height/2);
425 compare(tabs.tabBar.pressed, false, "After releasing, pressed is false");432 compare(tabs.tabBar.pressed, false, "After releasing, pressed is false");
426 }433 }
434
435
436
437 // these tests should not be mixed with Repeaters
438 function test_z_addTab() {
439 var newTab = tabs.addTab("Dynamic Tab", dynamicTab);
440 compare((newTab !== null), true, "tab added");
441 compare(newTab.active, false, "the inserted tab is inactive");
442 compare(newTab.index, tabs.count - 1, "the tab is the last one");
443 }
444
445 function test_z_addExternalTab() {
446 var newTab = tabs.addTab("External Tab", Qt.resolvedUrl("ExternalTab.qml"));
447 compare((newTab !== null), true, "tab added");
448 compare(newTab.active, false, "the inserted tab is inactive");
449 compare(newTab.index, tabs.count - 1, "the tab is the last one");
450 }
451
452 function test_z_addTabWithDefaultTitle() {
453 var newTab = tabs.addTab("", dynamicTab);
454 compare((newTab !== null), true, "tab added");
455 compare(newTab.title, "OriginalTitle", "tab created with original title");
456 }
457
458 function test_z_insertTab() {
459 var tabIndex = Math.ceil(tabs.count / 2);
460 var newTab = tabs.insertTab(tabIndex, "Inserted tab", dynamicTab);
461 compare((newTab !== null), true, "tab inserted");
462 compare(newTab.index, tabIndex, "this is the first tab");
463 compare(tabs.selectedTab !== newTab, true, "the new tab is not the active one");
464 }
465
466 function test_z_insertExternalTab() {
467 var tabIndex = Math.ceil(tabs.count / 2);
468 var newTab = tabs.insertTab(tabIndex, "Inserted External tab", Qt.resolvedUrl("ExternalTab.qml"));
469 compare((newTab !== null), true, "tab inserted");
470 compare(newTab.index, tabIndex, "this is the first tab");
471 compare(tabs.selectedTab !== newTab, true, "the new tab is not the active one");
472 }
473
474 function test_z_insertTabAtSelectedIndex() {
475 tabs.selectedTabIndex = 1;
476 var tabIndex = tabs.selectedTabIndex - 1;
477 var newTab = tabs.insertTab(tabIndex, "InsertedAtSelected tab", dynamicTab);
478 compare((newTab !== null), true, "tab inserted");
479 compare(newTab.index, tabIndex, "inserted at selected tab");
480 compare(tabs.selectedTabIndex != (tabIndex + 1), true, "it is not the selected tab");
481 }
482
483 function test_z_insertTabFront() {
484 var newTab = tabs.insertTab(-1, "PreTab", dynamicTab);
485 compare(newTab !== null, true, "pre-tab inserted");
486 compare(newTab.index, 0, "this is the new first tab");
487 compare(tabs.selectedTab !== newTab, true, "the new tab is not the active one");
488 }
489
490 function test_z_insertTabEnd() {
491 var newTab = tabs.insertTab(tabs.count, "PostTab", dynamicTab);
492 compare(newTab !== null, true, "post-tab inserted");
493 compare(newTab.index, tabs.count - 1, "thsi is the new last tab");
494 compare(tabs.selectedTab !== newTab, true, "the new tab is not the active one");
495 }
496
497 function test_z_insertTabAndActivate() {
498 var newTab = tabs.addTab("Inserted tab", dynamicTab);
499 compare((newTab !== null), true, "tab inserted");
500 compare(newTab.index, tabs.count - 1, "the tab is the last one");
501 tabs.selectedTabIndex = newTab.index;
502 compare(tabs.selectedTab, newTab, "the inserted tab is selected");
503 compare(newTab.active, true, "the new tab is active");
504 }
505
506 function test_z_moveTab() {
507 var selectedIndex = tabs.count - 1;
508 tabs.selectedTabIndex = selectedIndex;
509 compare(tabs.moveTab(0, selectedIndex), true, "first tab moved to last");
510 compare(tabs.selectedTabIndex, selectedIndex - 1, "the selected index moved backwards");
511 tabs.selectedTabIndex = selectedIndex = 0;
512 compare(tabs.moveTab(selectedIndex, selectedIndex + 1), true, "selected tab moved as next");
513 compare(tabs.selectedTabIndex, selectedIndex + 1, "the selected index moved forewards");
514 }
515
516 function test_z_moveSelectedTab() {
517 tabs.selectedTabIndex = 0;
518 tabs.moveTab(0, 1);
519 compare(tabs.selectedTabIndex, 1, "selected tab moved");
520 }
521
522 function test_z_moveTabFail() {
523 compare(tabs.moveTab(-1, tabs.count - 1), false, "from-parameter out of range");
524 compare(tabs.moveTab(0, tabs.count), false, "to-parameter out of range");
525 }
526
527 function test_z_removeTab() {
528 compare(tabs.removeTab(tabs.count - 1), true, "last tab removed");
529 tabs.selectedTabIndex = 0;
530 compare(tabs.removeTab(0), true, "active tab removed");
531 compare(tabs.selectedTabIndex, 0, "the next tab is selected")
532 }
533
534 function test_z_removeActiveTab() {
535 tabs.selectedTabIndex = 1;
536 compare(tabs.removeTab(1), true, "selected tab removed");
537 compare(tabs.selectedTabIndex, 1, "selected tab is next");
538
539 tabs.selectedTabIndex = tabs.count - 1;
540 compare(tabs.removeTab(tabs.count - 1), true, "last tab removed");
541 compare(tabs.selectedTabIndex, tabs.count - 1, "selected tab moved to last item");
542 }
543
544 function test_z_removeTabAfterActiveTab() {
545 var activeTab = tabs.count - 2;
546 tabs.selectedTabIndex = activeTab;
547 compare(tabs.removeTab(tabs.count - 1), true, "last tab removed");
548 compare(tabs.selectedTabIndex, activeTab, "the selected tab wasn't moved");
549 }
550
551 function test_z_removeTabBeforeActiveTab() {
552 var activeTab = tabs.count - 1;
553 tabs.selectedTabIndex = activeTab;
554 compare(tabs.removeTab(0), true, "first tab removed");
555 compare(tabs.selectedTabIndex, activeTab - 1, "the selected tab index decreased");
556 }
557
558 function test_zz_addTabAfterCleaningUpTabs() {
559 while (tabs.count > 1) {
560 tabs.removeTab(tabs.count - 1);
561 }
562 compare(tabs.selectedTabIndex, 0, "the only tab is the selected one");
563 // add a new tab anc check the count (default added tas should not be added anymore
564 tabs.addTab("Second tab", dynamicTab);
565 compare(tabs.count, 2, "we have two tabs only");
566 }
567
568 function test_zz_addPredeclaredTab() {
569 tabs.removeTab(tab1.index);
570
571 // add a predeclared tab back with original title
572 compare(tabs.addTab("", tab1), tab1, "tab1 was not added back");
573 compare(tab1.title, "tab 1", "the original title differs");
574
575 // add a predeclared tab which was added already
576 compare(tabs.addTab("", tab1), null, "tab1 is already in tabs");
577 }
427 }578 }
428}579}

Subscribers

People subscribed via source and target branches