Merge lp:~zsombi/ubuntu-ui-toolkit/dynamic-tabs-reloaded into lp:ubuntu-ui-toolkit/staging
- dynamic-tabs-reloaded
- Merge into staging
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 |
Related bugs: |
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
Description of the change
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:901
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Tim Peeters (tpeeters) wrote : Posted in a previous version of this proposal | # |
637 + * Copyright 2012 Canonical Ltd.
2013
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/
coming from connectToRepeaters
modules/
coming from insertTab
modules/
coming from anchors.top on the Repeater, not sure what this is
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.
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:902
http://
Executed test runs:
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:902
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Zsombor Egri (zsombi) wrote : Posted in a previous version of this proposal | # |
> 637 + * Copyright 2012 Canonical Ltd.
>
> 2013
It's 2014 now :)
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
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:903
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:904
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
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:
description doesn't exactly match the function specs.
* Moves a given item before the specified item in their parent's child list
?
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.
Cris Dywan (kalikiana) wrote : Posted in a previous version of this proposal | # |
CI@HOME OK http://
Tim Peeters (tpeeters) wrote : Posted in a previous version of this proposal | # |
note that you didn't fix the doc yet
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.
Zsombor Egri (zsombi) wrote : Posted in a previous version of this proposal | # |
> note that you didn't fix the doc yet
Sorry, done now.
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:906
http://
Executed test runs:
FAILURE: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Tim Peeters (tpeeters) wrote : Posted in a previous version of this proposal | # |
> modules/
> defined
This is still broken. Please add the include in Tabs.qml.
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/
> 'indexOf' of undefined
> coming from connectToRepeaters
> modules/
> defined
> coming from insertTab
> modules/
> 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
Zsombor Egri (zsombi) wrote : Posted in a previous version of this proposal | # |
>
> > modules/
> > defined
>
> This is still broken. Please add the include in Tabs.qml.
I'm wondering why was it passing till now???
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:907
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:908
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:909
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Tim Peeters (tpeeters) wrote : Posted in a previous version of this proposal | # |
ok
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:911
http://
Executed test runs:
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
ABORTED: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:911
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:912
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:913
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) : | # |
Preview Diff
1 | === modified file 'CHANGES' | |||
2 | --- CHANGES 2014-02-24 22:03:55 +0000 | |||
3 | +++ CHANGES 2014-04-10 11:35:42 +0000 | |||
4 | @@ -9,6 +9,7 @@ | |||
5 | 9 | 9 | ||
6 | 10 | API Changes | 10 | API Changes |
7 | 11 | *********** | 11 | *********** |
8 | 12 | * DEPRECATED IN: Tabs: default property list<Item> tabChildren | ||
9 | 12 | * ADDED IN: PickerDelegate: readonly property Picker picker | 13 | * ADDED IN: PickerDelegate: readonly property Picker picker |
10 | 13 | * CHANGED IN: OptionSelector: readonly property bool currentlyExpanded TO property bool currentlyExpanded | 14 | * CHANGED IN: OptionSelector: readonly property bool currentlyExpanded TO property bool currentlyExpanded |
11 | 14 | * CHANGED IN: ItemSelector: readonly property bool currentlyExpanded TO property bool currentlyExpanded | 15 | * CHANGED IN: ItemSelector: readonly property bool currentlyExpanded TO property bool currentlyExpanded |
12 | 15 | 16 | ||
13 | === modified file 'components.api' | |||
14 | --- components.api 2014-04-10 05:14:42 +0000 | |||
15 | +++ components.api 2014-04-10 11:35:42 +0000 | |||
16 | @@ -417,9 +417,14 @@ | |||
17 | 417 | readonly property Tab selectedTab | 417 | readonly property Tab selectedTab |
18 | 418 | readonly property Item currentPage | 418 | readonly property Item currentPage |
19 | 419 | property TabBar tabBar | 419 | property TabBar tabBar |
21 | 420 | default property list<Item> tabChildren | 420 | property list<Item> tabChildren |
22 | 421 | readonly property int count | 421 | readonly property int count |
23 | 422 | signal modelChanged() | 422 | signal modelChanged() |
24 | 423 | function addTab(title, tab) | ||
25 | 424 | function insertTab(index, title, tab) | ||
26 | 425 | function getTab(index) | ||
27 | 426 | function moveTab(from, to) | ||
28 | 427 | function removeTab(index) | ||
29 | 423 | modules/Ubuntu/Components/TextArea.qml | 428 | modules/Ubuntu/Components/TextArea.qml |
30 | 424 | StyledItem | 429 | StyledItem |
31 | 425 | property bool highlighted | 430 | property bool highlighted |
32 | 426 | 431 | ||
33 | === modified file 'modules/Ubuntu/Components/Tab.qml' | |||
34 | --- modules/Ubuntu/Components/Tab.qml 2013-11-07 07:26:01 +0000 | |||
35 | +++ modules/Ubuntu/Components/Tab.qml 2014-04-10 11:35:42 +0000 | |||
36 | @@ -105,12 +105,5 @@ | |||
37 | 105 | Tab is destroyed upon removal. | 105 | Tab is destroyed upon removal. |
38 | 106 | */ | 106 | */ |
39 | 107 | property bool dynamic: false | 107 | property bool dynamic: false |
40 | 108 | |||
41 | 109 | /* | ||
42 | 110 | This flag is used by the Tabs to determine whether the pre-declared Tab was removed | ||
43 | 111 | from the Tabs model or not. The flag guards adding back pre-declared tabs upon Tabs | ||
44 | 112 | component stack (children) change. | ||
45 | 113 | */ | ||
46 | 114 | property bool removedFromTabs: false | ||
47 | 115 | } | 108 | } |
48 | 116 | } | 109 | } |
49 | 117 | 110 | ||
50 | === modified file 'modules/Ubuntu/Components/Tabs.qml' | |||
51 | --- modules/Ubuntu/Components/Tabs.qml 2014-04-08 12:38:35 +0000 | |||
52 | +++ modules/Ubuntu/Components/Tabs.qml 2014-04-10 11:35:42 +0000 | |||
53 | @@ -15,6 +15,7 @@ | |||
54 | 15 | */ | 15 | */ |
55 | 16 | 16 | ||
56 | 17 | import QtQuick 2.0 | 17 | import QtQuick 2.0 |
57 | 18 | import "mathUtils.js" as MathUtils | ||
58 | 18 | 19 | ||
59 | 19 | /*! | 20 | /*! |
60 | 20 | \qmltype Tabs | 21 | \qmltype Tabs |
61 | @@ -149,6 +150,110 @@ | |||
62 | 149 | } | 150 | } |
63 | 150 | } | 151 | } |
64 | 151 | \endqml | 152 | \endqml |
65 | 153 | |||
66 | 154 | \section2 Dynamic tabs | ||
67 | 155 | So far all Tab elements were pre-declared, but there can be situations when | ||
68 | 156 | tabs need to be added dynamically. There are two ways to solve this, depending | ||
69 | 157 | on the output needed. | ||
70 | 158 | |||
71 | 159 | \section3 Using Repeaters | ||
72 | 160 | A Repeater can be used to create the necessary tabs depending on a given model. | ||
73 | 161 | In this way the number of tabs will be driven by the model itself. | ||
74 | 162 | An example of such a dynamic tab: | ||
75 | 163 | \qml | ||
76 | 164 | // DynamicTab.qml | ||
77 | 165 | import QtQuick 2.0 | ||
78 | 166 | import Ubuntu.Components 0.1 | ||
79 | 167 | |||
80 | 168 | Tabs { | ||
81 | 169 | property alias model: tabRepeater.model | ||
82 | 170 | Repeater { | ||
83 | 171 | id: tabRepeater | ||
84 | 172 | model: 5 | ||
85 | 173 | Tab { | ||
86 | 174 | title: "Tab #" + index | ||
87 | 175 | page: Page { | ||
88 | 176 | // [...] | ||
89 | 177 | } | ||
90 | 178 | } | ||
91 | 179 | } | ||
92 | 180 | } | ||
93 | 181 | \endqml | ||
94 | 182 | Note that in the example above the Tabs will be re-created each time the model | ||
95 | 183 | changes. This will cause state losing of each Tab, which depending on the | ||
96 | 184 | content type can be solved at some extent using StateSaver. Using a Loader | ||
97 | 185 | or specifying the Tab instance/component in the model the state can be preserved, | ||
98 | 186 | however may increase code complexity. | ||
99 | 187 | |||
100 | 188 | \section3 Dynamic tabs | ||
101 | 189 | Tabs provides functions to add Tab elements dynamically on runtime, without | ||
102 | 190 | destroying the state of the existing tabs. You can add, move and remove any | ||
103 | 191 | kind of Tab element, pre-declared or dynamically created ones. When removing | ||
104 | 192 | pre-declared tabs, those will all be held back and hidden by Tabs, and can be | ||
105 | 193 | added back any time either to the same or to a different position. | ||
106 | 194 | |||
107 | 195 | \qml | ||
108 | 196 | import QtQuick 2.0 | ||
109 | 197 | import Ubuntu.Components 0.1 | ||
110 | 198 | |||
111 | 199 | MainView { | ||
112 | 200 | width: units.gu(40) | ||
113 | 201 | height: units.gu(71) | ||
114 | 202 | |||
115 | 203 | Component { | ||
116 | 204 | id: dynamicTab | ||
117 | 205 | Tab { | ||
118 | 206 | page: Page { | ||
119 | 207 | Label { | ||
120 | 208 | text: title | ||
121 | 209 | anchors.centerIn: parent | ||
122 | 210 | } | ||
123 | 211 | } | ||
124 | 212 | } | ||
125 | 213 | } | ||
126 | 214 | Tabs { | ||
127 | 215 | id: tabs | ||
128 | 216 | Tab { | ||
129 | 217 | title: "Main tab" | ||
130 | 218 | page: Page { | ||
131 | 219 | toolbar: ToolbarItems { | ||
132 | 220 | ToolbarButton { | ||
133 | 221 | text: "remove predeclared" | ||
134 | 222 | onTriggered: tabs.removeTab(preDeclared.index) | ||
135 | 223 | } | ||
136 | 224 | ToolbarButton { | ||
137 | 225 | text: "add new" | ||
138 | 226 | onTriggered: tabs.addTab("New tab", dynamicTab) | ||
139 | 227 | } | ||
140 | 228 | ToolbarButton { | ||
141 | 229 | text: "insert predeclared" | ||
142 | 230 | onTriggered: tabs.insertTab("", 0) | ||
143 | 231 | } | ||
144 | 232 | } | ||
145 | 233 | } | ||
146 | 234 | } | ||
147 | 235 | Tab { | ||
148 | 236 | id: preDeclared | ||
149 | 237 | title: "Pre-declared tab" | ||
150 | 238 | page: Page { | ||
151 | 239 | Label { | ||
152 | 240 | text: "This is a predeclared tab at index #" + index | ||
153 | 241 | anchors.centerIn: parent | ||
154 | 242 | } | ||
155 | 243 | } | ||
156 | 244 | } | ||
157 | 245 | } | ||
158 | 246 | } | ||
159 | 247 | \endqml | ||
160 | 248 | |||
161 | 249 | \section3 Using Repeater and functions together | ||
162 | 250 | Repeaters re-create their delegates as many times the model changes. Tabs added | ||
163 | 251 | or moved in between the tabs maintained by the Repeater, as well as reordered | ||
164 | 252 | through the Tabs functions will be re-arranged once the Repeater's model changes. | ||
165 | 253 | This should be taken into account when designing the application, and the use | ||
166 | 254 | of Repeater and functions toghether should be avoided if possible, or at least | ||
167 | 255 | Repeater should always add tabs to te tail of the tab stack, and no tab insertion | ||
168 | 256 | happens in that area. | ||
169 | 152 | */ | 257 | */ |
170 | 153 | PageTreeNode { | 258 | PageTreeNode { |
171 | 154 | id: tabs | 259 | id: tabs |
172 | @@ -186,10 +291,14 @@ | |||
173 | 186 | } | 291 | } |
174 | 187 | 292 | ||
175 | 188 | /*! | 293 | /*! |
177 | 189 | Children are placed in a separate item that has functionality to extract the Tab items. | 294 | \deprecated |
178 | 295 | Children are placed in a separate item that has functionality to extract | ||
179 | 296 | the Tab items. | ||
180 | 297 | Note: this property is deprecated. Tab components are directly parented | ||
181 | 298 | to Tabs' data property. | ||
182 | 190 | \qmlproperty list<Item> tabChildren | 299 | \qmlproperty list<Item> tabChildren |
183 | 191 | */ | 300 | */ |
185 | 192 | default property alias tabChildren: tabStack.data | 301 | property alias tabChildren: tabs.data |
186 | 193 | 302 | ||
187 | 194 | /*! | 303 | /*! |
188 | 195 | \qmlproperty int count | 304 | \qmlproperty int count |
189 | @@ -206,36 +315,180 @@ | |||
190 | 206 | signal modelChanged() | 315 | signal modelChanged() |
191 | 207 | 316 | ||
192 | 208 | /*! | 317 | /*! |
193 | 318 | Appends a Tab dynamically to the list of tabs. The \a title specifies the | ||
194 | 319 | title of the Tab. The \a tab can be either a Component, a URL to a Tab | ||
195 | 320 | component to be loaded or an instance of a pre-declared tab that has been | ||
196 | 321 | previously removed. The Tab's title will be replaced with the given \a title, | ||
197 | 322 | unless the value is an empty string or undefined. | ||
198 | 323 | Returns the instance of the added Tab. | ||
199 | 324 | */ | ||
200 | 325 | function addTab(title, tab) { | ||
201 | 326 | return insertTab(count, title, tab); | ||
202 | 327 | } | ||
203 | 328 | |||
204 | 329 | /*! | ||
205 | 330 | Inserts a Tab at the given index. If the \a index is less or equal than 0, | ||
206 | 331 | the Tab will be added to the front, and to the end of the tab stack in case | ||
207 | 332 | the \a index is greater than \l count. \a title and \a tab are used in the | ||
208 | 333 | same way as with \l addTab(). | ||
209 | 334 | Returns the instance of the inserted Tab. | ||
210 | 335 | */ | ||
211 | 336 | function insertTab(index, title, tab) { | ||
212 | 337 | // check if the given component is a Tab instance | ||
213 | 338 | var tabObject = null; | ||
214 | 339 | |||
215 | 340 | if (typeof tab === "string") { | ||
216 | 341 | // we have a URL | ||
217 | 342 | var tabComponent = Qt.createComponent(tab); | ||
218 | 343 | if (tabComponent.status === Component.Error) { | ||
219 | 344 | console.error(tabComponent.errorString()); | ||
220 | 345 | return null; | ||
221 | 346 | } | ||
222 | 347 | tabObject = tabComponent.createObject(); | ||
223 | 348 | tabObject.__protected.dynamic = true; | ||
224 | 349 | } else if (tab.hasOwnProperty("createObject")) { | ||
225 | 350 | // we have a Component | ||
226 | 351 | tabObject = tab.createObject(); | ||
227 | 352 | tabObject.__protected.dynamic = true; | ||
228 | 353 | } else if (tab.hasOwnProperty("parent") && tab.parent === trashedTabs) { | ||
229 | 354 | // we have a pre-declared tab that has been removed | ||
230 | 355 | tabObject = tab; | ||
231 | 356 | } else { | ||
232 | 357 | console.error(i18n.tr("The object is not a URL, Component or a removed Tab: ") + tab); | ||
233 | 358 | return null; | ||
234 | 359 | } | ||
235 | 360 | |||
236 | 361 | // fix title | ||
237 | 362 | if (title !== undefined && title !== "") { | ||
238 | 363 | tabObject.title = title; | ||
239 | 364 | } | ||
240 | 365 | |||
241 | 366 | // insert the created tab into the model | ||
242 | 367 | index = MathUtils.clamp(index, 0, count); | ||
243 | 368 | tabObject.__protected.inserted = true; | ||
244 | 369 | tabObject.__protected.index = index; | ||
245 | 370 | tabsModel.insertTab(tabObject, index); | ||
246 | 371 | if (tabs.selectedTabIndex >= index) { | ||
247 | 372 | // move the selected index to the next index | ||
248 | 373 | tabs.selectedTabIndex += 1; | ||
249 | 374 | } else { | ||
250 | 375 | internal.sync(); | ||
251 | 376 | } | ||
252 | 377 | return tabObject; | ||
253 | 378 | } | ||
254 | 379 | |||
255 | 380 | /*! | ||
256 | 381 | The function returns the Tab from the given \a index, or null if the \a index | ||
257 | 382 | is invalid (less than \c 0 and greater than \l count). | ||
258 | 383 | */ | ||
259 | 384 | function getTab(index) { | ||
260 | 385 | return (index >=0) && (index < count) ? tabsModel.get(index).tab : null; | ||
261 | 386 | } | ||
262 | 387 | |||
263 | 388 | /*! | ||
264 | 389 | Moves the tab from the given \a from position to the position given in \a to. | ||
265 | 390 | Returns true if the indexes were in 0..\l count - 1 boundary and if the operation | ||
266 | 391 | succeeds, and false otherwise. The \l selectedTabIndex is updated if it is | ||
267 | 392 | affected by the move (it is equal with \a from or falls between \a from and | ||
268 | 393 | \a to indexes). | ||
269 | 394 | */ | ||
270 | 395 | function moveTab(from, to) { | ||
271 | 396 | if (from < 0 || from >= count || to < 0 || to >= count || from === to) return false; | ||
272 | 397 | var tabFrom = tabsModel.get(from).tab; | ||
273 | 398 | var tabTo = tabsModel.get(to).tab; | ||
274 | 399 | |||
275 | 400 | // move tab | ||
276 | 401 | QuickUtils.moveItemBefore(tabFrom, tabTo); | ||
277 | 402 | tabsModel.updateTabList(tabs.children); | ||
278 | 403 | |||
279 | 404 | // fix selected tab | ||
280 | 405 | if (selectedTabIndex === from) { | ||
281 | 406 | selectedTabIndex = to; | ||
282 | 407 | } else if (selectedTabIndex >= Math.min(from, to) && selectedTabIndex <= Math.max(from, to)) { | ||
283 | 408 | selectedTabIndex--; | ||
284 | 409 | } else { | ||
285 | 410 | internal.sync(); | ||
286 | 411 | } | ||
287 | 412 | |||
288 | 413 | return true; | ||
289 | 414 | } | ||
290 | 415 | |||
291 | 416 | /*! | ||
292 | 417 | Removes the Tab from the given \a index. Returns true if the \a index falls | ||
293 | 418 | into 0..\l count - 1 boundary and the operation succeeds, and false on error. | ||
294 | 419 | The function removes also the pre-declared tabs. These can be added back using | ||
295 | 420 | \l addTab or \l insertTab by specifying the instance of the Tab to be added as | ||
296 | 421 | component. The \l selectedTabIndex is updated if is affected by the removal | ||
297 | 422 | (it is identical or greater than the tab index to be removed). | ||
298 | 423 | */ | ||
299 | 424 | function removeTab(index) { | ||
300 | 425 | if (index < 0 || index >= count) return false; | ||
301 | 426 | var tab = tabsModel.get(index).tab; | ||
302 | 427 | var activeIndex = (selectedTabIndex >= index) ? MathUtils.clamp(selectedTabIndex, 0, count - 2) : -1; | ||
303 | 428 | |||
304 | 429 | // remove from Tabs; Tabs children change will remove the tab from the model | ||
305 | 430 | tab.parent = null; | ||
306 | 431 | if (tab.__protected.dynamic) { | ||
307 | 432 | tab.destroy(); | ||
308 | 433 | } else { | ||
309 | 434 | // pre-declared tab, mark it as removed, so we don't update it next time | ||
310 | 435 | // the tabs stack children is updated | ||
311 | 436 | tab.parent = trashedTabs; | ||
312 | 437 | } | ||
313 | 438 | |||
314 | 439 | // move active tab if needed | ||
315 | 440 | if (activeIndex >= 0 && activeIndex !== selectedTabIndex) { | ||
316 | 441 | selectedTabIndex = activeIndex; | ||
317 | 442 | } else { | ||
318 | 443 | internal.sync(); | ||
319 | 444 | } | ||
320 | 445 | |||
321 | 446 | return true; | ||
322 | 447 | } | ||
323 | 448 | |||
324 | 449 | /*! \internal */ | ||
325 | 450 | onChildrenChanged: { | ||
326 | 451 | internal.connectToRepeaters(tabs.children); | ||
327 | 452 | tabsModel.updateTabList(tabs.children); | ||
328 | 453 | } | ||
329 | 454 | |||
330 | 455 | /*! | ||
331 | 209 | \internal | 456 | \internal |
332 | 210 | required by TabsStyle | 457 | required by TabsStyle |
333 | 211 | */ | 458 | */ |
334 | 212 | ListModel { | 459 | ListModel { |
335 | 213 | id: tabsModel | 460 | id: tabsModel |
336 | 214 | 461 | ||
337 | 462 | property bool updateDisabled: false | ||
338 | 463 | |||
339 | 215 | function listModel(tab) { | 464 | function listModel(tab) { |
340 | 216 | return {"title": tab.title, "tab": tab}; | 465 | return {"title": tab.title, "tab": tab}; |
341 | 217 | } | 466 | } |
342 | 218 | 467 | ||
343 | 219 | function updateTabList(tabsList) { | 468 | function updateTabList(tabsList) { |
344 | 469 | if (updateDisabled) return; | ||
345 | 220 | var offset = 0; | 470 | var offset = 0; |
347 | 221 | var tabIndex; | 471 | var tabIndex = -1; |
348 | 222 | for (var i in tabsList) { | 472 | for (var i in tabsList) { |
349 | 223 | var tab = tabsList[i]; | 473 | var tab = tabsList[i]; |
350 | 224 | if (internal.isTab(tab)) { | 474 | if (internal.isTab(tab)) { |
351 | 225 | tabIndex = i - offset; | 475 | tabIndex = i - offset; |
352 | 226 | // make sure we have the right parent | 476 | // make sure we have the right parent |
354 | 227 | tab.parent = tabStack; | 477 | tab.parent = tabs; |
355 | 228 | 478 | ||
356 | 229 | if (!tab.__protected.inserted) { | 479 | if (!tab.__protected.inserted) { |
357 | 230 | tab.__protected.index = tabIndex; | 480 | tab.__protected.index = tabIndex; |
358 | 231 | tab.__protected.inserted = true; | 481 | tab.__protected.inserted = true; |
359 | 232 | insert(tabIndex, listModel(tab)); | 482 | insert(tabIndex, listModel(tab)); |
361 | 233 | } else if (!tab.__protected.removedFromTabs && tabsModel.count > tab.index) { | 483 | } else { |
362 | 234 | get(tab.index).title = tab.title; | 484 | get(tab.index).title = tab.title; |
363 | 235 | } | 485 | } |
364 | 236 | 486 | ||
365 | 237 | // always makes sure that tabsModel has the same order as tabsList | 487 | // always makes sure that tabsModel has the same order as tabsList |
367 | 238 | move(tab.__protected.index, tabIndex, 1); | 488 | // but move only if there is more than one item in the list |
368 | 489 | if (count > 1) { | ||
369 | 490 | move(tab.__protected.index, tabIndex, 1); | ||
370 | 491 | } | ||
371 | 239 | reindex(); | 492 | reindex(); |
372 | 240 | } else { | 493 | } else { |
373 | 241 | // keep track of children that are not tabs so that we compute | 494 | // keep track of children that are not tabs so that we compute |
374 | @@ -243,6 +496,10 @@ | |||
375 | 243 | offset += 1; | 496 | offset += 1; |
376 | 244 | } | 497 | } |
377 | 245 | } | 498 | } |
378 | 499 | // remove deleted tabs, those should be at the end of the list by now | ||
379 | 500 | if ((tabIndex >= 0) && (tabIndex + 1) < count) { | ||
380 | 501 | remove(tabIndex + 1, count - tabIndex - 1); | ||
381 | 502 | } | ||
382 | 246 | internal.sync(); | 503 | internal.sync(); |
383 | 247 | } | 504 | } |
384 | 248 | 505 | ||
385 | @@ -257,18 +514,31 @@ | |||
386 | 257 | tab.__protected.index = i; | 514 | tab.__protected.index = i; |
387 | 258 | } | 515 | } |
388 | 259 | } | 516 | } |
389 | 517 | |||
390 | 518 | function insertTab(tab, index) { | ||
391 | 519 | // fix index | ||
392 | 520 | if (index < 0) { | ||
393 | 521 | index = 0; | ||
394 | 522 | } | ||
395 | 523 | // get the tab before which the item will be inserted | ||
396 | 524 | var itemAtIndex = ((index >= 0) && (index < count)) ? get(index).tab : null; | ||
397 | 525 | // disable update only if we insert, append can keep the logic rolling | ||
398 | 526 | updateDisabled = (itemAtIndex !== null); | ||
399 | 527 | insert(index, listModel(tab)); | ||
400 | 528 | tab.parent = tabs; | ||
401 | 529 | updateDisabled = false; | ||
402 | 530 | if (itemAtIndex) { | ||
403 | 531 | QuickUtils.moveItemBefore(tab, itemAtIndex); | ||
404 | 532 | updateTabList(tabs.children); | ||
405 | 533 | } | ||
406 | 534 | } | ||
407 | 260 | } | 535 | } |
408 | 261 | 536 | ||
411 | 262 | // FIXME: this component is not really needed, as it doesn't really bring any | 537 | // invisible component stacking removed pre-declared components |
410 | 263 | // value; should be removed in a later MR | ||
412 | 264 | Item { | 538 | Item { |
420 | 265 | anchors.fill: parent | 539 | id: trashedTabs |
421 | 266 | id: tabStack | 540 | visible: false |
422 | 267 | 541 | opacity: 0.0 | |
416 | 268 | onChildrenChanged: { | ||
417 | 269 | internal.connectToRepeaters(tabStack.children); | ||
418 | 270 | tabsModel.updateTabList(tabStack.children); | ||
419 | 271 | } | ||
423 | 272 | } | 542 | } |
424 | 273 | 543 | ||
425 | 274 | /* | 544 | /* |
426 | @@ -283,7 +553,7 @@ | |||
427 | 283 | interval: 1 | 553 | interval: 1 |
428 | 284 | running: false | 554 | running: false |
429 | 285 | onTriggered: { | 555 | onTriggered: { |
431 | 286 | tabsModel.updateTabList(tabStack.children); | 556 | tabsModel.updateTabList(tabs.children); |
432 | 287 | internal.sync(); | 557 | internal.sync(); |
433 | 288 | } | 558 | } |
434 | 289 | } | 559 | } |
435 | @@ -295,8 +565,8 @@ | |||
436 | 295 | Binding { | 565 | Binding { |
437 | 296 | target: tabBar | 566 | target: tabBar |
438 | 297 | property: "animate" | 567 | property: "animate" |
441 | 298 | when: internal.header && internal.header.hasOwnProperty("animate") | 568 | when: (internal.header !== null) && internal.header.hasOwnProperty("animate") |
442 | 299 | value: internal.header.animate | 569 | value: internal.header ? internal.header.animate : "false" |
443 | 300 | } | 570 | } |
444 | 301 | 571 | ||
445 | 302 | /* | 572 | /* |
446 | @@ -332,7 +602,9 @@ | |||
447 | 332 | function connectToRepeaters(children) { | 602 | function connectToRepeaters(children) { |
448 | 333 | for (var i = 0; i < children.length; i++) { | 603 | for (var i = 0; i < children.length; i++) { |
449 | 334 | var child = children[i]; | 604 | var child = children[i]; |
451 | 335 | if (internal.isRepeater(child) && (internal.repeaters.indexOf(child) < 0)) { | 605 | if (internal.isRepeater(child) && |
452 | 606 | (internal.repeaters !== undefined) && | ||
453 | 607 | (internal.repeaters.indexOf(child) < 0)) { | ||
454 | 336 | internal.connectRepeater(child); | 608 | internal.connectRepeater(child); |
455 | 337 | } | 609 | } |
456 | 338 | } | 610 | } |
457 | @@ -355,7 +627,7 @@ | |||
458 | 355 | https://bugreports.qt-project.org/browse/QTBUG-32438 | 627 | https://bugreports.qt-project.org/browse/QTBUG-32438 |
459 | 356 | */ | 628 | */ |
460 | 357 | function updateTabsModel() { | 629 | function updateTabsModel() { |
462 | 358 | tabsModel.updateTabList(tabStack.children); | 630 | tabsModel.updateTabList(tabs.children); |
463 | 359 | } | 631 | } |
464 | 360 | 632 | ||
465 | 361 | /* | 633 | /* |
466 | 362 | 634 | ||
467 | === modified file 'modules/Ubuntu/Components/Themes/Ambiance/TabBarStyle.qml' | |||
468 | --- modules/Ubuntu/Components/Themes/Ambiance/TabBarStyle.qml 2014-04-08 12:38:35 +0000 | |||
469 | +++ modules/Ubuntu/Components/Themes/Ambiance/TabBarStyle.qml 2014-04-10 11:35:42 +0000 | |||
470 | @@ -116,8 +116,8 @@ | |||
471 | 116 | AbstractButton { | 116 | AbstractButton { |
472 | 117 | id: button | 117 | id: button |
473 | 118 | anchors { | 118 | anchors { |
476 | 119 | top: parent.top | 119 | top: parent ? parent.top : undefined |
477 | 120 | bottom: parent.bottom | 120 | bottom: parent ? parent.bottom: undefined |
478 | 121 | } | 121 | } |
479 | 122 | width: text.paintedWidth + text.anchors.leftMargin + text.anchors.rightMargin | 122 | width: text.paintedWidth + text.anchors.leftMargin + text.anchors.rightMargin |
480 | 123 | 123 | ||
481 | @@ -150,6 +150,13 @@ | |||
482 | 150 | return false; | 150 | return false; |
483 | 151 | } | 151 | } |
484 | 152 | 152 | ||
485 | 153 | // update the offset of the buttonRow | ||
486 | 154 | onOffsetChanged: { | ||
487 | 155 | if (selected) { | ||
488 | 156 | buttonView.updateOffset(button.offset); | ||
489 | 157 | } | ||
490 | 158 | } | ||
491 | 159 | |||
492 | 153 | Behavior on opacity { | 160 | Behavior on opacity { |
493 | 154 | NumberAnimation { | 161 | NumberAnimation { |
494 | 155 | duration: headerTextFadeDuration | 162 | duration: headerTextFadeDuration |
495 | 156 | 163 | ||
496 | === modified file 'modules/Ubuntu/Components/plugin/quickutils.cpp' | |||
497 | --- modules/Ubuntu/Components/plugin/quickutils.cpp 2014-03-20 15:46:28 +0000 | |||
498 | +++ modules/Ubuntu/Components/plugin/quickutils.cpp 2014-04-10 11:35:42 +0000 | |||
499 | @@ -119,6 +119,21 @@ | |||
500 | 119 | return result.left(result.indexOf("_QML")); | 119 | return result.left(result.indexOf("_QML")); |
501 | 120 | } | 120 | } |
502 | 121 | 121 | ||
503 | 122 | /*! | ||
504 | 123 | * \internal | ||
505 | 124 | * Moves a given \a item before the \a other one in the object stack. Both \a item | ||
506 | 125 | * and \a other must have the same parent item. | ||
507 | 126 | */ | ||
508 | 127 | void QuickUtils::moveItemBefore(QQuickItem *item, QQuickItem *other) | ||
509 | 128 | { | ||
510 | 129 | Q_ASSERT(item); | ||
511 | 130 | Q_ASSERT(item->parentItem()); | ||
512 | 131 | if (other) { | ||
513 | 132 | Q_ASSERT(other->parentItem() == item->parentItem()); | ||
514 | 133 | item->stackBefore(other); | ||
515 | 134 | } | ||
516 | 135 | } | ||
517 | 136 | |||
518 | 122 | 137 | ||
519 | 123 | /*! | 138 | /*! |
520 | 124 | * \internal | 139 | * \internal |
521 | 125 | 140 | ||
522 | === modified file 'modules/Ubuntu/Components/plugin/quickutils.h' | |||
523 | --- modules/Ubuntu/Components/plugin/quickutils.h 2014-03-20 15:46:28 +0000 | |||
524 | +++ modules/Ubuntu/Components/plugin/quickutils.h 2014-04-10 11:35:42 +0000 | |||
525 | @@ -42,6 +42,7 @@ | |||
526 | 42 | QString inputMethodProvider() const; | 42 | QString inputMethodProvider() const; |
527 | 43 | 43 | ||
528 | 44 | Q_INVOKABLE static QString className(QObject *item); | 44 | Q_INVOKABLE static QString className(QObject *item); |
529 | 45 | Q_INVOKABLE void moveItemBefore(QQuickItem *item, QQuickItem *before); | ||
530 | 45 | QObject* createQmlObject(const QUrl &url, QQmlEngine *engine); | 46 | QObject* createQmlObject(const QUrl &url, QQmlEngine *engine); |
531 | 46 | 47 | ||
532 | 47 | Q_SIGNALS: | 48 | Q_SIGNALS: |
533 | 48 | 49 | ||
534 | === modified file 'tests/resources/navigation/Tabs.qml' | |||
535 | --- tests/resources/navigation/Tabs.qml 2014-04-07 10:03:39 +0000 | |||
536 | +++ tests/resources/navigation/Tabs.qml 2014-04-10 11:35:42 +0000 | |||
537 | @@ -19,9 +19,47 @@ | |||
538 | 19 | import Ubuntu.Components.ListItems 0.1 as ListItem | 19 | import Ubuntu.Components.ListItems 0.1 as ListItem |
539 | 20 | 20 | ||
540 | 21 | MainView { | 21 | MainView { |
541 | 22 | id: root | ||
542 | 22 | width: 800 | 23 | width: 800 |
543 | 23 | height: 600 | 24 | height: 600 |
544 | 24 | 25 | ||
545 | 26 | property var repeaterModel: 3 | ||
546 | 27 | |||
547 | 28 | Component { | ||
548 | 29 | id: dynamicTab | ||
549 | 30 | Tab { | ||
550 | 31 | page: Page { | ||
551 | 32 | Label { | ||
552 | 33 | text: title + " at index " + index | ||
553 | 34 | anchors.centerIn: parent | ||
554 | 35 | } | ||
555 | 36 | tools: ToolbarItems { | ||
556 | 37 | ToolbarButton { | ||
557 | 38 | text: "move @1" | ||
558 | 39 | onTriggered: { | ||
559 | 40 | print("MOVE TAB TO #1") | ||
560 | 41 | tabs.moveTab(index, 1) | ||
561 | 42 | } | ||
562 | 43 | } | ||
563 | 44 | ToolbarButton { | ||
564 | 45 | text: "remove me" | ||
565 | 46 | onTriggered: { | ||
566 | 47 | print("REMOVE CURENT TAB") | ||
567 | 48 | tabs.removeTab(index) | ||
568 | 49 | } | ||
569 | 50 | } | ||
570 | 51 | ToolbarButton { | ||
571 | 52 | text: "remove first" | ||
572 | 53 | onTriggered: { | ||
573 | 54 | print("REMOVE TAB AT #0") | ||
574 | 55 | tabs.removeTab(0) | ||
575 | 56 | } | ||
576 | 57 | } | ||
577 | 58 | } | ||
578 | 59 | } | ||
579 | 60 | } | ||
580 | 61 | } | ||
581 | 62 | |||
582 | 25 | Tabs { | 63 | Tabs { |
583 | 26 | id: tabs | 64 | id: tabs |
584 | 27 | selectedTabIndex: 0 | 65 | selectedTabIndex: 0 |
585 | @@ -31,6 +69,7 @@ | |||
586 | 31 | 69 | ||
587 | 32 | Tab { | 70 | Tab { |
588 | 33 | id: simpleTab | 71 | id: simpleTab |
589 | 72 | objectName: title | ||
590 | 34 | title: i18n.tr("Simple page #" + index) | 73 | title: i18n.tr("Simple page #" + index) |
591 | 35 | page: Page { | 74 | page: Page { |
592 | 36 | Row { | 75 | Row { |
593 | @@ -55,13 +94,60 @@ | |||
594 | 55 | iconSource: "call_icon.png" | 94 | iconSource: "call_icon.png" |
595 | 56 | onTriggered: print("action triggered") | 95 | onTriggered: print("action triggered") |
596 | 57 | } | 96 | } |
597 | 97 | ToolbarButton { | ||
598 | 98 | text: "append" | ||
599 | 99 | onTriggered: { | ||
600 | 100 | print("APPEND TAB") | ||
601 | 101 | tabs.addTab("Appended tab", dynamicTab) | ||
602 | 102 | } | ||
603 | 103 | } | ||
604 | 104 | ToolbarButton { | ||
605 | 105 | text: "insert@1" | ||
606 | 106 | onTriggered: { | ||
607 | 107 | print("INSERT TAB TO #1") | ||
608 | 108 | tabs.insertTab(1, "Inserted tab", dynamicTab) | ||
609 | 109 | } | ||
610 | 110 | } | ||
611 | 111 | ToolbarButton { | ||
612 | 112 | text: "insert@2" | ||
613 | 113 | onTriggered: { | ||
614 | 114 | print("INSERT BETWEEN REPEATERS #1") | ||
615 | 115 | tabs.insertTab(2, "Between repeaters", dynamicTab) | ||
616 | 116 | } | ||
617 | 117 | } | ||
618 | 118 | ToolbarButton { | ||
619 | 119 | text: "insert@here" | ||
620 | 120 | onTriggered: { | ||
621 | 121 | print("INSERT AFTER ME") | ||
622 | 122 | tabs.insertTab(simpleTab.index, "Inserted tab", dynamicTab) | ||
623 | 123 | } | ||
624 | 124 | } | ||
625 | 125 | ToolbarButton { | ||
626 | 126 | text: "incRep" | ||
627 | 127 | onTriggered: { | ||
628 | 128 | print("INCREASE REPEATER MODEL") | ||
629 | 129 | root.repeaterModel += 1 | ||
630 | 130 | } | ||
631 | 131 | } | ||
632 | 132 | ToolbarButton { | ||
633 | 133 | text: "remove last" | ||
634 | 134 | onTriggered: { | ||
635 | 135 | print("REMOVE LAST TAB") | ||
636 | 136 | tabs.removeTab(tabs.count - 1) | ||
637 | 137 | } | ||
638 | 138 | } | ||
639 | 139 | ToolbarButton { | ||
640 | 140 | text: "append predec" | ||
641 | 141 | onTriggered: tabs.addTab("Re-added ListView", listViewTab) | ||
642 | 142 | } | ||
643 | 58 | } | 143 | } |
644 | 59 | } | 144 | } |
645 | 60 | } | 145 | } |
646 | 61 | Repeater { | 146 | Repeater { |
648 | 62 | model: 3 | 147 | model: root.repeaterModel |
649 | 63 | Tab { | 148 | Tab { |
650 | 64 | id: tab | 149 | id: tab |
651 | 150 | objectName: title | ||
652 | 65 | title: "Extra #" + tab.index | 151 | title: "Extra #" + tab.index |
653 | 66 | page: Page { | 152 | page: Page { |
654 | 67 | Column { | 153 | Column { |
655 | @@ -88,6 +174,7 @@ | |||
656 | 88 | } | 174 | } |
657 | 89 | Tab { | 175 | Tab { |
658 | 90 | id: externalTab | 176 | id: externalTab |
659 | 177 | objectName: title | ||
660 | 91 | title: i18n.tr("External #" + index) | 178 | title: i18n.tr("External #" + index) |
661 | 92 | page: Loader { | 179 | page: Loader { |
662 | 93 | parent: externalTab | 180 | parent: externalTab |
663 | @@ -96,6 +183,8 @@ | |||
664 | 96 | } | 183 | } |
665 | 97 | } | 184 | } |
666 | 98 | Tab { | 185 | Tab { |
667 | 186 | id: listViewTab | ||
668 | 187 | objectName: title | ||
669 | 99 | title: i18n.tr("List view #" + index) | 188 | title: i18n.tr("List view #" + index) |
670 | 100 | page: Page { | 189 | page: Page { |
671 | 101 | ListView { | 190 | ListView { |
672 | 102 | 191 | ||
673 | === added file 'tests/unit_x11/tst_components/ExternalTab.qml' | |||
674 | --- tests/unit_x11/tst_components/ExternalTab.qml 1970-01-01 00:00:00 +0000 | |||
675 | +++ tests/unit_x11/tst_components/ExternalTab.qml 2014-04-10 11:35:42 +0000 | |||
676 | @@ -0,0 +1,21 @@ | |||
677 | 1 | /* | ||
678 | 2 | * Copyright 2014 Canonical Ltd. | ||
679 | 3 | * | ||
680 | 4 | * This program is free software; you can redistribute it and/or modify | ||
681 | 5 | * it under the terms of the GNU Lesser General Public License as published by | ||
682 | 6 | * the Free Software Foundation; version 3. | ||
683 | 7 | * | ||
684 | 8 | * This program is distributed in the hope that it will be useful, | ||
685 | 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
686 | 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
687 | 11 | * GNU Lesser General Public License for more details. | ||
688 | 12 | * | ||
689 | 13 | * You should have received a copy of the GNU Lesser General Public License | ||
690 | 14 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
691 | 15 | */ | ||
692 | 16 | |||
693 | 17 | import QtQuick 2.0 | ||
694 | 18 | import Ubuntu.Components 0.1 | ||
695 | 19 | |||
696 | 20 | Tab { | ||
697 | 21 | } | ||
698 | 0 | 22 | ||
699 | === modified file 'tests/unit_x11/tst_components/tst_tabs.qml' | |||
700 | --- tests/unit_x11/tst_components/tst_tabs.qml 2014-01-13 12:43:12 +0000 | |||
701 | +++ tests/unit_x11/tst_components/tst_tabs.qml 2014-04-10 11:35:42 +0000 | |||
702 | @@ -27,6 +27,13 @@ | |||
703 | 27 | id: emptyTabs | 27 | id: emptyTabs |
704 | 28 | } | 28 | } |
705 | 29 | 29 | ||
706 | 30 | Component { | ||
707 | 31 | id: dynamicTab | ||
708 | 32 | Tab{ | ||
709 | 33 | title: "OriginalTitle" | ||
710 | 34 | } | ||
711 | 35 | } | ||
712 | 36 | |||
713 | 30 | MainView { | 37 | MainView { |
714 | 31 | id: mainView | 38 | id: mainView |
715 | 32 | anchors.fill: parent | 39 | anchors.fill: parent |
716 | @@ -424,5 +431,149 @@ | |||
717 | 424 | mouseRelease(tabs.tabBar, tabs.tabBar.width/2, tabs.tabBar.height/2); | 431 | mouseRelease(tabs.tabBar, tabs.tabBar.width/2, tabs.tabBar.height/2); |
718 | 425 | compare(tabs.tabBar.pressed, false, "After releasing, pressed is false"); | 432 | compare(tabs.tabBar.pressed, false, "After releasing, pressed is false"); |
719 | 426 | } | 433 | } |
720 | 434 | |||
721 | 435 | |||
722 | 436 | |||
723 | 437 | // these tests should not be mixed with Repeaters | ||
724 | 438 | function test_z_addTab() { | ||
725 | 439 | var newTab = tabs.addTab("Dynamic Tab", dynamicTab); | ||
726 | 440 | compare((newTab !== null), true, "tab added"); | ||
727 | 441 | compare(newTab.active, false, "the inserted tab is inactive"); | ||
728 | 442 | compare(newTab.index, tabs.count - 1, "the tab is the last one"); | ||
729 | 443 | } | ||
730 | 444 | |||
731 | 445 | function test_z_addExternalTab() { | ||
732 | 446 | var newTab = tabs.addTab("External Tab", Qt.resolvedUrl("ExternalTab.qml")); | ||
733 | 447 | compare((newTab !== null), true, "tab added"); | ||
734 | 448 | compare(newTab.active, false, "the inserted tab is inactive"); | ||
735 | 449 | compare(newTab.index, tabs.count - 1, "the tab is the last one"); | ||
736 | 450 | } | ||
737 | 451 | |||
738 | 452 | function test_z_addTabWithDefaultTitle() { | ||
739 | 453 | var newTab = tabs.addTab("", dynamicTab); | ||
740 | 454 | compare((newTab !== null), true, "tab added"); | ||
741 | 455 | compare(newTab.title, "OriginalTitle", "tab created with original title"); | ||
742 | 456 | } | ||
743 | 457 | |||
744 | 458 | function test_z_insertTab() { | ||
745 | 459 | var tabIndex = Math.ceil(tabs.count / 2); | ||
746 | 460 | var newTab = tabs.insertTab(tabIndex, "Inserted tab", dynamicTab); | ||
747 | 461 | compare((newTab !== null), true, "tab inserted"); | ||
748 | 462 | compare(newTab.index, tabIndex, "this is the first tab"); | ||
749 | 463 | compare(tabs.selectedTab !== newTab, true, "the new tab is not the active one"); | ||
750 | 464 | } | ||
751 | 465 | |||
752 | 466 | function test_z_insertExternalTab() { | ||
753 | 467 | var tabIndex = Math.ceil(tabs.count / 2); | ||
754 | 468 | var newTab = tabs.insertTab(tabIndex, "Inserted External tab", Qt.resolvedUrl("ExternalTab.qml")); | ||
755 | 469 | compare((newTab !== null), true, "tab inserted"); | ||
756 | 470 | compare(newTab.index, tabIndex, "this is the first tab"); | ||
757 | 471 | compare(tabs.selectedTab !== newTab, true, "the new tab is not the active one"); | ||
758 | 472 | } | ||
759 | 473 | |||
760 | 474 | function test_z_insertTabAtSelectedIndex() { | ||
761 | 475 | tabs.selectedTabIndex = 1; | ||
762 | 476 | var tabIndex = tabs.selectedTabIndex - 1; | ||
763 | 477 | var newTab = tabs.insertTab(tabIndex, "InsertedAtSelected tab", dynamicTab); | ||
764 | 478 | compare((newTab !== null), true, "tab inserted"); | ||
765 | 479 | compare(newTab.index, tabIndex, "inserted at selected tab"); | ||
766 | 480 | compare(tabs.selectedTabIndex != (tabIndex + 1), true, "it is not the selected tab"); | ||
767 | 481 | } | ||
768 | 482 | |||
769 | 483 | function test_z_insertTabFront() { | ||
770 | 484 | var newTab = tabs.insertTab(-1, "PreTab", dynamicTab); | ||
771 | 485 | compare(newTab !== null, true, "pre-tab inserted"); | ||
772 | 486 | compare(newTab.index, 0, "this is the new first tab"); | ||
773 | 487 | compare(tabs.selectedTab !== newTab, true, "the new tab is not the active one"); | ||
774 | 488 | } | ||
775 | 489 | |||
776 | 490 | function test_z_insertTabEnd() { | ||
777 | 491 | var newTab = tabs.insertTab(tabs.count, "PostTab", dynamicTab); | ||
778 | 492 | compare(newTab !== null, true, "post-tab inserted"); | ||
779 | 493 | compare(newTab.index, tabs.count - 1, "thsi is the new last tab"); | ||
780 | 494 | compare(tabs.selectedTab !== newTab, true, "the new tab is not the active one"); | ||
781 | 495 | } | ||
782 | 496 | |||
783 | 497 | function test_z_insertTabAndActivate() { | ||
784 | 498 | var newTab = tabs.addTab("Inserted tab", dynamicTab); | ||
785 | 499 | compare((newTab !== null), true, "tab inserted"); | ||
786 | 500 | compare(newTab.index, tabs.count - 1, "the tab is the last one"); | ||
787 | 501 | tabs.selectedTabIndex = newTab.index; | ||
788 | 502 | compare(tabs.selectedTab, newTab, "the inserted tab is selected"); | ||
789 | 503 | compare(newTab.active, true, "the new tab is active"); | ||
790 | 504 | } | ||
791 | 505 | |||
792 | 506 | function test_z_moveTab() { | ||
793 | 507 | var selectedIndex = tabs.count - 1; | ||
794 | 508 | tabs.selectedTabIndex = selectedIndex; | ||
795 | 509 | compare(tabs.moveTab(0, selectedIndex), true, "first tab moved to last"); | ||
796 | 510 | compare(tabs.selectedTabIndex, selectedIndex - 1, "the selected index moved backwards"); | ||
797 | 511 | tabs.selectedTabIndex = selectedIndex = 0; | ||
798 | 512 | compare(tabs.moveTab(selectedIndex, selectedIndex + 1), true, "selected tab moved as next"); | ||
799 | 513 | compare(tabs.selectedTabIndex, selectedIndex + 1, "the selected index moved forewards"); | ||
800 | 514 | } | ||
801 | 515 | |||
802 | 516 | function test_z_moveSelectedTab() { | ||
803 | 517 | tabs.selectedTabIndex = 0; | ||
804 | 518 | tabs.moveTab(0, 1); | ||
805 | 519 | compare(tabs.selectedTabIndex, 1, "selected tab moved"); | ||
806 | 520 | } | ||
807 | 521 | |||
808 | 522 | function test_z_moveTabFail() { | ||
809 | 523 | compare(tabs.moveTab(-1, tabs.count - 1), false, "from-parameter out of range"); | ||
810 | 524 | compare(tabs.moveTab(0, tabs.count), false, "to-parameter out of range"); | ||
811 | 525 | } | ||
812 | 526 | |||
813 | 527 | function test_z_removeTab() { | ||
814 | 528 | compare(tabs.removeTab(tabs.count - 1), true, "last tab removed"); | ||
815 | 529 | tabs.selectedTabIndex = 0; | ||
816 | 530 | compare(tabs.removeTab(0), true, "active tab removed"); | ||
817 | 531 | compare(tabs.selectedTabIndex, 0, "the next tab is selected") | ||
818 | 532 | } | ||
819 | 533 | |||
820 | 534 | function test_z_removeActiveTab() { | ||
821 | 535 | tabs.selectedTabIndex = 1; | ||
822 | 536 | compare(tabs.removeTab(1), true, "selected tab removed"); | ||
823 | 537 | compare(tabs.selectedTabIndex, 1, "selected tab is next"); | ||
824 | 538 | |||
825 | 539 | tabs.selectedTabIndex = tabs.count - 1; | ||
826 | 540 | compare(tabs.removeTab(tabs.count - 1), true, "last tab removed"); | ||
827 | 541 | compare(tabs.selectedTabIndex, tabs.count - 1, "selected tab moved to last item"); | ||
828 | 542 | } | ||
829 | 543 | |||
830 | 544 | function test_z_removeTabAfterActiveTab() { | ||
831 | 545 | var activeTab = tabs.count - 2; | ||
832 | 546 | tabs.selectedTabIndex = activeTab; | ||
833 | 547 | compare(tabs.removeTab(tabs.count - 1), true, "last tab removed"); | ||
834 | 548 | compare(tabs.selectedTabIndex, activeTab, "the selected tab wasn't moved"); | ||
835 | 549 | } | ||
836 | 550 | |||
837 | 551 | function test_z_removeTabBeforeActiveTab() { | ||
838 | 552 | var activeTab = tabs.count - 1; | ||
839 | 553 | tabs.selectedTabIndex = activeTab; | ||
840 | 554 | compare(tabs.removeTab(0), true, "first tab removed"); | ||
841 | 555 | compare(tabs.selectedTabIndex, activeTab - 1, "the selected tab index decreased"); | ||
842 | 556 | } | ||
843 | 557 | |||
844 | 558 | function test_zz_addTabAfterCleaningUpTabs() { | ||
845 | 559 | while (tabs.count > 1) { | ||
846 | 560 | tabs.removeTab(tabs.count - 1); | ||
847 | 561 | } | ||
848 | 562 | compare(tabs.selectedTabIndex, 0, "the only tab is the selected one"); | ||
849 | 563 | // add a new tab anc check the count (default added tas should not be added anymore | ||
850 | 564 | tabs.addTab("Second tab", dynamicTab); | ||
851 | 565 | compare(tabs.count, 2, "we have two tabs only"); | ||
852 | 566 | } | ||
853 | 567 | |||
854 | 568 | function test_zz_addPredeclaredTab() { | ||
855 | 569 | tabs.removeTab(tab1.index); | ||
856 | 570 | |||
857 | 571 | // add a predeclared tab back with original title | ||
858 | 572 | compare(tabs.addTab("", tab1), tab1, "tab1 was not added back"); | ||
859 | 573 | compare(tab1.title, "tab 1", "the original title differs"); | ||
860 | 574 | |||
861 | 575 | // add a predeclared tab which was added already | ||
862 | 576 | compare(tabs.addTab("", tab1), null, "tab1 is already in tabs"); | ||
863 | 577 | } | ||
864 | 427 | } | 578 | } |
865 | 428 | } | 579 | } |
FAILED: Continuous integration, rev:900 jenkins. qa.ubuntu. com/job/ ubuntu- ui-toolkit- ci/1483/ jenkins. qa.ubuntu. com/job/ generic- mediumtests- trusty/ 1856/console jenkins. qa.ubuntu. com/job/ generic- mediumtests- trusty- touch/1773/ console jenkins. qa.ubuntu. com/job/ ubuntu- ui-toolkit- trusty- amd64-ci/ 431/console jenkins. qa.ubuntu. com/job/ ubuntu- ui-toolkit- trusty- armhf-ci/ 431/console jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- trusty- amd64/1856/ console jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- trusty- armhf/1773/ console
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/ubuntu- ui-toolkit- ci/1483/ rebuild
http://