Merge lp:~zsombi/ubuntu-ui-toolkit/dynamic-tabs-reloaded into lp:ubuntu-ui-toolkit
- dynamic-tabs-reloaded
- Merge into trunk
Status: | Superseded |
---|---|
Proposed branch: | lp:~zsombi/ubuntu-ui-toolkit/dynamic-tabs-reloaded |
Merge into: | lp:ubuntu-ui-toolkit |
Diff against target: |
1137 lines (+767/-49) 14 files modified
CHANGES (+1/-0) components.api (+8/-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) modules/Ubuntu/Test/UbuntuTestCase.qml (+69/-1) modules/Ubuntu/Test/deployment.pri (+6/-1) tests/resources/navigation/Tabs.qml (+90/-1) tests/unit/runtest.sh (+1/-1) tests/unit_x11/tst_components/ExternalTab.qml (+21/-0) tests/unit_x11/tst_components/tst_tabs.qml (+151/-0) tests/unit_x11/tst_test/tst_ubuntutestcase.qml (+103/-15) |
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 | Approve | |
Review via email: mp+199620@code.launchpad.net |
This proposal has been superseded by a proposal from 2014-04-10.
Commit message
Extending Tabs with functions to dynamically add, move and remove tabs.
Description of the change
PS Jenkins bot (ps-jenkins) wrote : | # |
PS Jenkins bot (ps-jenkins) wrote : | # |
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 : | # |
637 + * Copyright 2012 Canonical Ltd.
2013
Cris Dywan (kalikiana) wrote : | # |
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 : | # |
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 : | # |
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 : | # |
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 : | # |
> 637 + * Copyright 2012 Canonical Ltd.
>
> 2013
It's 2014 now :)
Zsombor Egri (zsombi) wrote : | # |
> 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 : | # |
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 : | # |
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 : | # |
+/*!
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 : | # |
That one escaped me; I'll give green light with the above sentence fixed.
Cris Dywan (kalikiana) wrote : | # |
CI@HOME OK http://
Tim Peeters (tpeeters) wrote : | # |
note that you didn't fix the doc yet
Tim Peeters (tpeeters) wrote : | # |
I changed the status back to needs review, because we need to re-do the CI@home with qt52.
Zsombor Egri (zsombi) wrote : | # |
> note that you didn't fix the doc yet
Sorry, done now.
PS Jenkins bot (ps-jenkins) wrote : | # |
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 : | # |
> modules/
> defined
This is still broken. Please add the include in Tabs.qml.
Tim Peeters (tpeeters) wrote : | # |
> 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 : | # |
>
> > 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 : | # |
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 : | # |
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 : | # |
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://
PS Jenkins bot (ps-jenkins) wrote : | # |
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 : | # |
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 : | # |
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://
- 913. By Zsombor Egri
-
staging merge
Unmerged revisions
Preview Diff
1 | === modified file 'CHANGES' |
2 | --- CHANGES 2014-02-24 22:03:55 +0000 |
3 | +++ CHANGES 2014-04-10 11:34:46 +0000 |
4 | @@ -9,6 +9,7 @@ |
5 | |
6 | API Changes |
7 | *********** |
8 | +* DEPRECATED IN: Tabs: default property list<Item> tabChildren |
9 | * ADDED IN: PickerDelegate: readonly property Picker picker |
10 | * CHANGED IN: OptionSelector: readonly property bool currentlyExpanded TO property bool currentlyExpanded |
11 | * CHANGED IN: ItemSelector: readonly property bool currentlyExpanded TO property bool currentlyExpanded |
12 | |
13 | === modified file 'components.api' |
14 | --- components.api 2014-04-01 12:57:27 +0000 |
15 | +++ components.api 2014-04-10 11:34:46 +0000 |
16 | @@ -417,9 +417,14 @@ |
17 | readonly property Tab selectedTab |
18 | readonly property Item currentPage |
19 | property TabBar tabBar |
20 | - default property list<Item> tabChildren |
21 | + property list<Item> tabChildren |
22 | readonly property int count |
23 | signal modelChanged() |
24 | + function addTab(title, tab) |
25 | + function insertTab(index, title, tab) |
26 | + function getTab(index) |
27 | + function moveTab(from, to) |
28 | + function removeTab(index) |
29 | modules/Ubuntu/Components/TextArea.qml |
30 | StyledItem |
31 | property bool highlighted |
32 | @@ -593,6 +598,8 @@ |
33 | function findChild(obj,objectName) |
34 | function findInvisibleChild(obj,objectName) |
35 | function mouseMoveSlowly(item,x,y,dx,dy,steps,stepdelay) |
36 | + function flick(item, x, y, dx, dy, pressTimeout, steps, button, modifiers, delay) |
37 | + function mouseLongPress(item, x, y, button, modifiers, delay) |
38 | function tryCompareFunction(func, expectedResult, timeout) |
39 | plugins.qmltypes |
40 | name: "InverseMouseAreaType" |
41 | |
42 | === modified file 'modules/Ubuntu/Components/Tab.qml' |
43 | --- modules/Ubuntu/Components/Tab.qml 2013-11-07 07:26:01 +0000 |
44 | +++ modules/Ubuntu/Components/Tab.qml 2014-04-10 11:34:46 +0000 |
45 | @@ -105,12 +105,5 @@ |
46 | Tab is destroyed upon removal. |
47 | */ |
48 | property bool dynamic: false |
49 | - |
50 | - /* |
51 | - This flag is used by the Tabs to determine whether the pre-declared Tab was removed |
52 | - from the Tabs model or not. The flag guards adding back pre-declared tabs upon Tabs |
53 | - component stack (children) change. |
54 | - */ |
55 | - property bool removedFromTabs: false |
56 | } |
57 | } |
58 | |
59 | === modified file 'modules/Ubuntu/Components/Tabs.qml' |
60 | --- modules/Ubuntu/Components/Tabs.qml 2014-04-08 12:38:35 +0000 |
61 | +++ modules/Ubuntu/Components/Tabs.qml 2014-04-10 11:34:46 +0000 |
62 | @@ -15,6 +15,7 @@ |
63 | */ |
64 | |
65 | import QtQuick 2.0 |
66 | +import "mathUtils.js" as MathUtils |
67 | |
68 | /*! |
69 | \qmltype Tabs |
70 | @@ -149,6 +150,110 @@ |
71 | } |
72 | } |
73 | \endqml |
74 | + |
75 | + \section2 Dynamic tabs |
76 | + So far all Tab elements were pre-declared, but there can be situations when |
77 | + tabs need to be added dynamically. There are two ways to solve this, depending |
78 | + on the output needed. |
79 | + |
80 | + \section3 Using Repeaters |
81 | + A Repeater can be used to create the necessary tabs depending on a given model. |
82 | + In this way the number of tabs will be driven by the model itself. |
83 | + An example of such a dynamic tab: |
84 | + \qml |
85 | + // DynamicTab.qml |
86 | + import QtQuick 2.0 |
87 | + import Ubuntu.Components 0.1 |
88 | + |
89 | + Tabs { |
90 | + property alias model: tabRepeater.model |
91 | + Repeater { |
92 | + id: tabRepeater |
93 | + model: 5 |
94 | + Tab { |
95 | + title: "Tab #" + index |
96 | + page: Page { |
97 | + // [...] |
98 | + } |
99 | + } |
100 | + } |
101 | + } |
102 | + \endqml |
103 | + Note that in the example above the Tabs will be re-created each time the model |
104 | + changes. This will cause state losing of each Tab, which depending on the |
105 | + content type can be solved at some extent using StateSaver. Using a Loader |
106 | + or specifying the Tab instance/component in the model the state can be preserved, |
107 | + however may increase code complexity. |
108 | + |
109 | + \section3 Dynamic tabs |
110 | + Tabs provides functions to add Tab elements dynamically on runtime, without |
111 | + destroying the state of the existing tabs. You can add, move and remove any |
112 | + kind of Tab element, pre-declared or dynamically created ones. When removing |
113 | + pre-declared tabs, those will all be held back and hidden by Tabs, and can be |
114 | + added back any time either to the same or to a different position. |
115 | + |
116 | + \qml |
117 | + import QtQuick 2.0 |
118 | + import Ubuntu.Components 0.1 |
119 | + |
120 | + MainView { |
121 | + width: units.gu(40) |
122 | + height: units.gu(71) |
123 | + |
124 | + Component { |
125 | + id: dynamicTab |
126 | + Tab { |
127 | + page: Page { |
128 | + Label { |
129 | + text: title |
130 | + anchors.centerIn: parent |
131 | + } |
132 | + } |
133 | + } |
134 | + } |
135 | + Tabs { |
136 | + id: tabs |
137 | + Tab { |
138 | + title: "Main tab" |
139 | + page: Page { |
140 | + toolbar: ToolbarItems { |
141 | + ToolbarButton { |
142 | + text: "remove predeclared" |
143 | + onTriggered: tabs.removeTab(preDeclared.index) |
144 | + } |
145 | + ToolbarButton { |
146 | + text: "add new" |
147 | + onTriggered: tabs.addTab("New tab", dynamicTab) |
148 | + } |
149 | + ToolbarButton { |
150 | + text: "insert predeclared" |
151 | + onTriggered: tabs.insertTab("", 0) |
152 | + } |
153 | + } |
154 | + } |
155 | + } |
156 | + Tab { |
157 | + id: preDeclared |
158 | + title: "Pre-declared tab" |
159 | + page: Page { |
160 | + Label { |
161 | + text: "This is a predeclared tab at index #" + index |
162 | + anchors.centerIn: parent |
163 | + } |
164 | + } |
165 | + } |
166 | + } |
167 | + } |
168 | + \endqml |
169 | + |
170 | + \section3 Using Repeater and functions together |
171 | + Repeaters re-create their delegates as many times the model changes. Tabs added |
172 | + or moved in between the tabs maintained by the Repeater, as well as reordered |
173 | + through the Tabs functions will be re-arranged once the Repeater's model changes. |
174 | + This should be taken into account when designing the application, and the use |
175 | + of Repeater and functions toghether should be avoided if possible, or at least |
176 | + Repeater should always add tabs to te tail of the tab stack, and no tab insertion |
177 | + happens in that area. |
178 | */ |
179 | PageTreeNode { |
180 | id: tabs |
181 | @@ -186,10 +291,14 @@ |
182 | } |
183 | |
184 | /*! |
185 | - Children are placed in a separate item that has functionality to extract the Tab items. |
186 | + \deprecated |
187 | + Children are placed in a separate item that has functionality to extract |
188 | + the Tab items. |
189 | + Note: this property is deprecated. Tab components are directly parented |
190 | + to Tabs' data property. |
191 | \qmlproperty list<Item> tabChildren |
192 | */ |
193 | - default property alias tabChildren: tabStack.data |
194 | + property alias tabChildren: tabs.data |
195 | |
196 | /*! |
197 | \qmlproperty int count |
198 | @@ -206,36 +315,180 @@ |
199 | signal modelChanged() |
200 | |
201 | /*! |
202 | + Appends a Tab dynamically to the list of tabs. The \a title specifies the |
203 | + title of the Tab. The \a tab can be either a Component, a URL to a Tab |
204 | + component to be loaded or an instance of a pre-declared tab that has been |
205 | + previously removed. The Tab's title will be replaced with the given \a title, |
206 | + unless the value is an empty string or undefined. |
207 | + Returns the instance of the added Tab. |
208 | + */ |
209 | + function addTab(title, tab) { |
210 | + return insertTab(count, title, tab); |
211 | + } |
212 | + |
213 | + /*! |
214 | + Inserts a Tab at the given index. If the \a index is less or equal than 0, |
215 | + the Tab will be added to the front, and to the end of the tab stack in case |
216 | + the \a index is greater than \l count. \a title and \a tab are used in the |
217 | + same way as with \l addTab(). |
218 | + Returns the instance of the inserted Tab. |
219 | + */ |
220 | + function insertTab(index, title, tab) { |
221 | + // check if the given component is a Tab instance |
222 | + var tabObject = null; |
223 | + |
224 | + if (typeof tab === "string") { |
225 | + // we have a URL |
226 | + var tabComponent = Qt.createComponent(tab); |
227 | + if (tabComponent.status === Component.Error) { |
228 | + console.error(tabComponent.errorString()); |
229 | + return null; |
230 | + } |
231 | + tabObject = tabComponent.createObject(); |
232 | + tabObject.__protected.dynamic = true; |
233 | + } else if (tab.hasOwnProperty("createObject")) { |
234 | + // we have a Component |
235 | + tabObject = tab.createObject(); |
236 | + tabObject.__protected.dynamic = true; |
237 | + } else if (tab.hasOwnProperty("parent") && tab.parent === trashedTabs) { |
238 | + // we have a pre-declared tab that has been removed |
239 | + tabObject = tab; |
240 | + } else { |
241 | + console.error(i18n.tr("The object is not a URL, Component or a removed Tab: ") + tab); |
242 | + return null; |
243 | + } |
244 | + |
245 | + // fix title |
246 | + if (title !== undefined && title !== "") { |
247 | + tabObject.title = title; |
248 | + } |
249 | + |
250 | + // insert the created tab into the model |
251 | + index = MathUtils.clamp(index, 0, count); |
252 | + tabObject.__protected.inserted = true; |
253 | + tabObject.__protected.index = index; |
254 | + tabsModel.insertTab(tabObject, index); |
255 | + if (tabs.selectedTabIndex >= index) { |
256 | + // move the selected index to the next index |
257 | + tabs.selectedTabIndex += 1; |
258 | + } else { |
259 | + internal.sync(); |
260 | + } |
261 | + return tabObject; |
262 | + } |
263 | + |
264 | + /*! |
265 | + The function returns the Tab from the given \a index, or null if the \a index |
266 | + is invalid (less than \c 0 and greater than \l count). |
267 | + */ |
268 | + function getTab(index) { |
269 | + return (index >=0) && (index < count) ? tabsModel.get(index).tab : null; |
270 | + } |
271 | + |
272 | + /*! |
273 | + Moves the tab from the given \a from position to the position given in \a to. |
274 | + Returns true if the indexes were in 0..\l count - 1 boundary and if the operation |
275 | + succeeds, and false otherwise. The \l selectedTabIndex is updated if it is |
276 | + affected by the move (it is equal with \a from or falls between \a from and |
277 | + \a to indexes). |
278 | + */ |
279 | + function moveTab(from, to) { |
280 | + if (from < 0 || from >= count || to < 0 || to >= count || from === to) return false; |
281 | + var tabFrom = tabsModel.get(from).tab; |
282 | + var tabTo = tabsModel.get(to).tab; |
283 | + |
284 | + // move tab |
285 | + QuickUtils.moveItemBefore(tabFrom, tabTo); |
286 | + tabsModel.updateTabList(tabs.children); |
287 | + |
288 | + // fix selected tab |
289 | + if (selectedTabIndex === from) { |
290 | + selectedTabIndex = to; |
291 | + } else if (selectedTabIndex >= Math.min(from, to) && selectedTabIndex <= Math.max(from, to)) { |
292 | + selectedTabIndex--; |
293 | + } else { |
294 | + internal.sync(); |
295 | + } |
296 | + |
297 | + return true; |
298 | + } |
299 | + |
300 | + /*! |
301 | + Removes the Tab from the given \a index. Returns true if the \a index falls |
302 | + into 0..\l count - 1 boundary and the operation succeeds, and false on error. |
303 | + The function removes also the pre-declared tabs. These can be added back using |
304 | + \l addTab or \l insertTab by specifying the instance of the Tab to be added as |
305 | + component. The \l selectedTabIndex is updated if is affected by the removal |
306 | + (it is identical or greater than the tab index to be removed). |
307 | + */ |
308 | + function removeTab(index) { |
309 | + if (index < 0 || index >= count) return false; |
310 | + var tab = tabsModel.get(index).tab; |
311 | + var activeIndex = (selectedTabIndex >= index) ? MathUtils.clamp(selectedTabIndex, 0, count - 2) : -1; |
312 | + |
313 | + // remove from Tabs; Tabs children change will remove the tab from the model |
314 | + tab.parent = null; |
315 | + if (tab.__protected.dynamic) { |
316 | + tab.destroy(); |
317 | + } else { |
318 | + // pre-declared tab, mark it as removed, so we don't update it next time |
319 | + // the tabs stack children is updated |
320 | + tab.parent = trashedTabs; |
321 | + } |
322 | + |
323 | + // move active tab if needed |
324 | + if (activeIndex >= 0 && activeIndex !== selectedTabIndex) { |
325 | + selectedTabIndex = activeIndex; |
326 | + } else { |
327 | + internal.sync(); |
328 | + } |
329 | + |
330 | + return true; |
331 | + } |
332 | + |
333 | + /*! \internal */ |
334 | + onChildrenChanged: { |
335 | + internal.connectToRepeaters(tabs.children); |
336 | + tabsModel.updateTabList(tabs.children); |
337 | + } |
338 | + |
339 | + /*! |
340 | \internal |
341 | required by TabsStyle |
342 | */ |
343 | ListModel { |
344 | id: tabsModel |
345 | |
346 | + property bool updateDisabled: false |
347 | + |
348 | function listModel(tab) { |
349 | return {"title": tab.title, "tab": tab}; |
350 | } |
351 | |
352 | function updateTabList(tabsList) { |
353 | + if (updateDisabled) return; |
354 | var offset = 0; |
355 | - var tabIndex; |
356 | + var tabIndex = -1; |
357 | for (var i in tabsList) { |
358 | var tab = tabsList[i]; |
359 | if (internal.isTab(tab)) { |
360 | tabIndex = i - offset; |
361 | // make sure we have the right parent |
362 | - tab.parent = tabStack; |
363 | + tab.parent = tabs; |
364 | |
365 | if (!tab.__protected.inserted) { |
366 | tab.__protected.index = tabIndex; |
367 | tab.__protected.inserted = true; |
368 | insert(tabIndex, listModel(tab)); |
369 | - } else if (!tab.__protected.removedFromTabs && tabsModel.count > tab.index) { |
370 | + } else { |
371 | get(tab.index).title = tab.title; |
372 | } |
373 | |
374 | // always makes sure that tabsModel has the same order as tabsList |
375 | - move(tab.__protected.index, tabIndex, 1); |
376 | + // but move only if there is more than one item in the list |
377 | + if (count > 1) { |
378 | + move(tab.__protected.index, tabIndex, 1); |
379 | + } |
380 | reindex(); |
381 | } else { |
382 | // keep track of children that are not tabs so that we compute |
383 | @@ -243,6 +496,10 @@ |
384 | offset += 1; |
385 | } |
386 | } |
387 | + // remove deleted tabs, those should be at the end of the list by now |
388 | + if ((tabIndex >= 0) && (tabIndex + 1) < count) { |
389 | + remove(tabIndex + 1, count - tabIndex - 1); |
390 | + } |
391 | internal.sync(); |
392 | } |
393 | |
394 | @@ -257,18 +514,31 @@ |
395 | tab.__protected.index = i; |
396 | } |
397 | } |
398 | + |
399 | + function insertTab(tab, index) { |
400 | + // fix index |
401 | + if (index < 0) { |
402 | + index = 0; |
403 | + } |
404 | + // get the tab before which the item will be inserted |
405 | + var itemAtIndex = ((index >= 0) && (index < count)) ? get(index).tab : null; |
406 | + // disable update only if we insert, append can keep the logic rolling |
407 | + updateDisabled = (itemAtIndex !== null); |
408 | + insert(index, listModel(tab)); |
409 | + tab.parent = tabs; |
410 | + updateDisabled = false; |
411 | + if (itemAtIndex) { |
412 | + QuickUtils.moveItemBefore(tab, itemAtIndex); |
413 | + updateTabList(tabs.children); |
414 | + } |
415 | + } |
416 | } |
417 | |
418 | - // FIXME: this component is not really needed, as it doesn't really bring any |
419 | - // value; should be removed in a later MR |
420 | + // invisible component stacking removed pre-declared components |
421 | Item { |
422 | - anchors.fill: parent |
423 | - id: tabStack |
424 | - |
425 | - onChildrenChanged: { |
426 | - internal.connectToRepeaters(tabStack.children); |
427 | - tabsModel.updateTabList(tabStack.children); |
428 | - } |
429 | + id: trashedTabs |
430 | + visible: false |
431 | + opacity: 0.0 |
432 | } |
433 | |
434 | /* |
435 | @@ -283,7 +553,7 @@ |
436 | interval: 1 |
437 | running: false |
438 | onTriggered: { |
439 | - tabsModel.updateTabList(tabStack.children); |
440 | + tabsModel.updateTabList(tabs.children); |
441 | internal.sync(); |
442 | } |
443 | } |
444 | @@ -295,8 +565,8 @@ |
445 | Binding { |
446 | target: tabBar |
447 | property: "animate" |
448 | - when: internal.header && internal.header.hasOwnProperty("animate") |
449 | - value: internal.header.animate |
450 | + when: (internal.header !== null) && internal.header.hasOwnProperty("animate") |
451 | + value: internal.header ? internal.header.animate : "false" |
452 | } |
453 | |
454 | /* |
455 | @@ -332,7 +602,9 @@ |
456 | function connectToRepeaters(children) { |
457 | for (var i = 0; i < children.length; i++) { |
458 | var child = children[i]; |
459 | - if (internal.isRepeater(child) && (internal.repeaters.indexOf(child) < 0)) { |
460 | + if (internal.isRepeater(child) && |
461 | + (internal.repeaters !== undefined) && |
462 | + (internal.repeaters.indexOf(child) < 0)) { |
463 | internal.connectRepeater(child); |
464 | } |
465 | } |
466 | @@ -355,7 +627,7 @@ |
467 | https://bugreports.qt-project.org/browse/QTBUG-32438 |
468 | */ |
469 | function updateTabsModel() { |
470 | - tabsModel.updateTabList(tabStack.children); |
471 | + tabsModel.updateTabList(tabs.children); |
472 | } |
473 | |
474 | /* |
475 | |
476 | === modified file 'modules/Ubuntu/Components/Themes/Ambiance/TabBarStyle.qml' |
477 | --- modules/Ubuntu/Components/Themes/Ambiance/TabBarStyle.qml 2014-04-08 12:38:35 +0000 |
478 | +++ modules/Ubuntu/Components/Themes/Ambiance/TabBarStyle.qml 2014-04-10 11:34:46 +0000 |
479 | @@ -116,8 +116,8 @@ |
480 | AbstractButton { |
481 | id: button |
482 | anchors { |
483 | - top: parent.top |
484 | - bottom: parent.bottom |
485 | + top: parent ? parent.top : undefined |
486 | + bottom: parent ? parent.bottom: undefined |
487 | } |
488 | width: text.paintedWidth + text.anchors.leftMargin + text.anchors.rightMargin |
489 | |
490 | @@ -150,6 +150,13 @@ |
491 | return false; |
492 | } |
493 | |
494 | + // update the offset of the buttonRow |
495 | + onOffsetChanged: { |
496 | + if (selected) { |
497 | + buttonView.updateOffset(button.offset); |
498 | + } |
499 | + } |
500 | + |
501 | Behavior on opacity { |
502 | NumberAnimation { |
503 | duration: headerTextFadeDuration |
504 | |
505 | === modified file 'modules/Ubuntu/Components/plugin/quickutils.cpp' |
506 | --- modules/Ubuntu/Components/plugin/quickutils.cpp 2014-03-20 15:46:28 +0000 |
507 | +++ modules/Ubuntu/Components/plugin/quickutils.cpp 2014-04-10 11:34:46 +0000 |
508 | @@ -119,6 +119,21 @@ |
509 | return result.left(result.indexOf("_QML")); |
510 | } |
511 | |
512 | +/*! |
513 | + * \internal |
514 | + * Moves a given \a item before the \a other one in the object stack. Both \a item |
515 | + * and \a other must have the same parent item. |
516 | + */ |
517 | +void QuickUtils::moveItemBefore(QQuickItem *item, QQuickItem *other) |
518 | +{ |
519 | + Q_ASSERT(item); |
520 | + Q_ASSERT(item->parentItem()); |
521 | + if (other) { |
522 | + Q_ASSERT(other->parentItem() == item->parentItem()); |
523 | + item->stackBefore(other); |
524 | + } |
525 | +} |
526 | + |
527 | |
528 | /*! |
529 | * \internal |
530 | |
531 | === modified file 'modules/Ubuntu/Components/plugin/quickutils.h' |
532 | --- modules/Ubuntu/Components/plugin/quickutils.h 2014-03-20 15:46:28 +0000 |
533 | +++ modules/Ubuntu/Components/plugin/quickutils.h 2014-04-10 11:34:46 +0000 |
534 | @@ -42,6 +42,7 @@ |
535 | QString inputMethodProvider() const; |
536 | |
537 | Q_INVOKABLE static QString className(QObject *item); |
538 | + Q_INVOKABLE void moveItemBefore(QQuickItem *item, QQuickItem *before); |
539 | QObject* createQmlObject(const QUrl &url, QQmlEngine *engine); |
540 | |
541 | Q_SIGNALS: |
542 | |
543 | === modified file 'modules/Ubuntu/Test/UbuntuTestCase.qml' |
544 | --- modules/Ubuntu/Test/UbuntuTestCase.qml 2014-02-25 12:36:27 +0000 |
545 | +++ modules/Ubuntu/Test/UbuntuTestCase.qml 2014-04-10 11:34:46 +0000 |
546 | @@ -87,7 +87,75 @@ |
547 | } |
548 | } |
549 | |
550 | - /*! |
551 | + /*! |
552 | + \qmlmethod UbuntuTestCase::flick(item, x, y, dx, dy, pressTimeout = -1, steps = -1, button = Qt.LeftButton, modifiers = Qt.NoModifiers, delay = -1) |
553 | + |
554 | + The function produces a flick event when executed on Flickables. When used |
555 | + on other components it provides the same functionality as \l mouseDrag() |
556 | + function. The optional \a pressTimeout parameter can be used to introduce |
557 | + a small delay between the mouse press and the first mouse move. Setting a |
558 | + negative or zero value will disable the timeout. |
559 | + |
560 | + The default flick velocity is built up using 5 move points. This can be altered |
561 | + by setting a positive value to \a steps parameter. The bigger the number the |
562 | + longer the flick will be. When a negative or zero value is given, the default |
563 | + of 5 move points will be used. |
564 | + |
565 | + \note The function can be used to select a text in a TextField or TextArea by |
566 | + specifying at least 400 millisecods to \a pressTimeout. |
567 | + */ |
568 | + function flick(item, x, y, dx, dy, pressTimeout, steps, button, modifiers, delay) { |
569 | + if (item === undefined || item.x === undefined || item.y === undefined) |
570 | + return |
571 | + if (button === undefined) |
572 | + button = Qt.LeftButton |
573 | + if (modifiers === undefined) |
574 | + modifiers = Qt.NoModifier |
575 | + if (steps === undefined || steps <= 0) |
576 | + steps = 4; |
577 | + // make sure we have at least two move steps so the flick will be sensed |
578 | + steps += 1; |
579 | + if (delay === undefined) |
580 | + delay = -1; |
581 | + |
582 | + var ddx = dx / steps; |
583 | + var ddy = dy / steps; |
584 | + |
585 | + mousePress(item, x, y, button, modifiers, delay); |
586 | + if (pressTimeout !== undefined && pressTimeout > 0) { |
587 | + wait(pressTimeout); |
588 | + } |
589 | + for (var i = 1; i <= steps; i++) { |
590 | + // mouse moves are all processed immediately, without delay in between events |
591 | + mouseMove(item, x + i * ddx, y + i * ddy, -1, button); |
592 | + } |
593 | + mouseRelease(item, x + dx, y + dy, button, modifiers, delay); |
594 | + // empty event buffer |
595 | + wait(200); |
596 | + } |
597 | + |
598 | + /*! |
599 | + \qmlmethod UbuntuTestCase::mouseLongPress(item, x, y, button = Qt.LeftButton, modifiers = Qt.NoModifiers, delay = -1) |
600 | + |
601 | + Simulates a long press on a mouse \a button with an optional \a modifier |
602 | + on an \a item. The position is defined by \a x and \a y. If \a delay is |
603 | + specified, the test will wait the specified amount of milliseconds before |
604 | + the press. |
605 | + |
606 | + The position given by \a x and \a y is transformed from the co-ordinate |
607 | + system of \a item into window co-ordinates and then delivered. |
608 | + If \a item is obscured by another item, or a child of \a item occupies |
609 | + that position, then the event will be delivered to the other item instead. |
610 | + |
611 | + \sa mouseRelease(), mouseClick(), mouseDoubleClick(), mouseMove(), mouseDrag(), mouseWheel() |
612 | + */ |
613 | + function mouseLongPress(item, x, y, button, modifiers, delay) { |
614 | + mousePress(item, x, y, button, modifiers, delay); |
615 | + // the delay is taken from QQuickMouseArea |
616 | + wait(800); |
617 | + } |
618 | + |
619 | + /*! |
620 | Keeps executing a given parameter-less function until it returns the given |
621 | expected result or the timemout is reached (in which case a test failure |
622 | is generated) |
623 | |
624 | === modified file 'modules/Ubuntu/Test/deployment.pri' |
625 | --- modules/Ubuntu/Test/deployment.pri 2014-01-17 12:30:05 +0000 |
626 | +++ modules/Ubuntu/Test/deployment.pri 2014-04-10 11:34:46 +0000 |
627 | @@ -7,9 +7,14 @@ |
628 | # make found deployables visible in Qt Creator |
629 | OTHER_FILES += $$QMLDIR_FILE |
630 | |
631 | +QML_FILES = $$system(ls *.qml) |
632 | +JS_FILES = $$system(ls *.js) |
633 | + |
634 | # define deployment for found deployables |
635 | qmldir_file.path = $$installPath |
636 | qmldir_file.files = $$QMLDIR_FILE |
637 | +qml_files.path = $$installPath |
638 | +qml_files.files = $$QML_FILES |
639 | js_files.path = $$installPath |
640 | js_files.files = $$JS_FILES |
641 | |
642 | @@ -20,4 +25,4 @@ |
643 | # https://bugreports.qt-project.org/browse/QTBUG-36243 |
644 | plugins_qmltypes.extra = $$[QT_INSTALL_BINS]/qmlplugindump -notrelocatable Ubuntu.Test 0.1 ../../ 2>/dev/null > $(INSTALL_ROOT)/$$installPath/plugins.qmltypes |
645 | |
646 | -INSTALLS += qmldir_file plugins_qmltypes |
647 | +INSTALLS += qmldir_file plugins_qmltypes qml_files js_files |
648 | |
649 | === modified file 'tests/resources/navigation/Tabs.qml' |
650 | --- tests/resources/navigation/Tabs.qml 2014-04-07 10:03:39 +0000 |
651 | +++ tests/resources/navigation/Tabs.qml 2014-04-10 11:34:46 +0000 |
652 | @@ -19,9 +19,47 @@ |
653 | import Ubuntu.Components.ListItems 0.1 as ListItem |
654 | |
655 | MainView { |
656 | + id: root |
657 | width: 800 |
658 | height: 600 |
659 | |
660 | + property var repeaterModel: 3 |
661 | + |
662 | + Component { |
663 | + id: dynamicTab |
664 | + Tab { |
665 | + page: Page { |
666 | + Label { |
667 | + text: title + " at index " + index |
668 | + anchors.centerIn: parent |
669 | + } |
670 | + tools: ToolbarItems { |
671 | + ToolbarButton { |
672 | + text: "move @1" |
673 | + onTriggered: { |
674 | + print("MOVE TAB TO #1") |
675 | + tabs.moveTab(index, 1) |
676 | + } |
677 | + } |
678 | + ToolbarButton { |
679 | + text: "remove me" |
680 | + onTriggered: { |
681 | + print("REMOVE CURENT TAB") |
682 | + tabs.removeTab(index) |
683 | + } |
684 | + } |
685 | + ToolbarButton { |
686 | + text: "remove first" |
687 | + onTriggered: { |
688 | + print("REMOVE TAB AT #0") |
689 | + tabs.removeTab(0) |
690 | + } |
691 | + } |
692 | + } |
693 | + } |
694 | + } |
695 | + } |
696 | + |
697 | Tabs { |
698 | id: tabs |
699 | selectedTabIndex: 0 |
700 | @@ -31,6 +69,7 @@ |
701 | |
702 | Tab { |
703 | id: simpleTab |
704 | + objectName: title |
705 | title: i18n.tr("Simple page #" + index) |
706 | page: Page { |
707 | Row { |
708 | @@ -55,13 +94,60 @@ |
709 | iconSource: "call_icon.png" |
710 | onTriggered: print("action triggered") |
711 | } |
712 | + ToolbarButton { |
713 | + text: "append" |
714 | + onTriggered: { |
715 | + print("APPEND TAB") |
716 | + tabs.addTab("Appended tab", dynamicTab) |
717 | + } |
718 | + } |
719 | + ToolbarButton { |
720 | + text: "insert@1" |
721 | + onTriggered: { |
722 | + print("INSERT TAB TO #1") |
723 | + tabs.insertTab(1, "Inserted tab", dynamicTab) |
724 | + } |
725 | + } |
726 | + ToolbarButton { |
727 | + text: "insert@2" |
728 | + onTriggered: { |
729 | + print("INSERT BETWEEN REPEATERS #1") |
730 | + tabs.insertTab(2, "Between repeaters", dynamicTab) |
731 | + } |
732 | + } |
733 | + ToolbarButton { |
734 | + text: "insert@here" |
735 | + onTriggered: { |
736 | + print("INSERT AFTER ME") |
737 | + tabs.insertTab(simpleTab.index, "Inserted tab", dynamicTab) |
738 | + } |
739 | + } |
740 | + ToolbarButton { |
741 | + text: "incRep" |
742 | + onTriggered: { |
743 | + print("INCREASE REPEATER MODEL") |
744 | + root.repeaterModel += 1 |
745 | + } |
746 | + } |
747 | + ToolbarButton { |
748 | + text: "remove last" |
749 | + onTriggered: { |
750 | + print("REMOVE LAST TAB") |
751 | + tabs.removeTab(tabs.count - 1) |
752 | + } |
753 | + } |
754 | + ToolbarButton { |
755 | + text: "append predec" |
756 | + onTriggered: tabs.addTab("Re-added ListView", listViewTab) |
757 | + } |
758 | } |
759 | } |
760 | } |
761 | Repeater { |
762 | - model: 3 |
763 | + model: root.repeaterModel |
764 | Tab { |
765 | id: tab |
766 | + objectName: title |
767 | title: "Extra #" + tab.index |
768 | page: Page { |
769 | Column { |
770 | @@ -88,6 +174,7 @@ |
771 | } |
772 | Tab { |
773 | id: externalTab |
774 | + objectName: title |
775 | title: i18n.tr("External #" + index) |
776 | page: Loader { |
777 | parent: externalTab |
778 | @@ -96,6 +183,8 @@ |
779 | } |
780 | } |
781 | Tab { |
782 | + id: listViewTab |
783 | + objectName: title |
784 | title: i18n.tr("List view #" + index) |
785 | page: Page { |
786 | ListView { |
787 | |
788 | === modified file 'tests/unit/runtest.sh' |
789 | --- tests/unit/runtest.sh 2014-03-31 18:26:46 +0000 |
790 | +++ tests/unit/runtest.sh 2014-04-10 11:34:46 +0000 |
791 | @@ -33,7 +33,7 @@ |
792 | if [ $_TARGET != $_TESTFILE ]; then |
793 | _CMD="$_CMD -input $_TESTFILE" |
794 | fi |
795 | - _CMD="$_CMD -maxwarnings 4" |
796 | + _CMD="$_CMD -maxwarnings 40" |
797 | } |
798 | |
799 | function execute_test_cmd { |
800 | |
801 | === added file 'tests/unit_x11/tst_components/ExternalTab.qml' |
802 | --- tests/unit_x11/tst_components/ExternalTab.qml 1970-01-01 00:00:00 +0000 |
803 | +++ tests/unit_x11/tst_components/ExternalTab.qml 2014-04-10 11:34:46 +0000 |
804 | @@ -0,0 +1,21 @@ |
805 | +/* |
806 | + * Copyright 2014 Canonical Ltd. |
807 | + * |
808 | + * This program is free software; you can redistribute it and/or modify |
809 | + * it under the terms of the GNU Lesser General Public License as published by |
810 | + * the Free Software Foundation; version 3. |
811 | + * |
812 | + * This program is distributed in the hope that it will be useful, |
813 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
814 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
815 | + * GNU Lesser General Public License for more details. |
816 | + * |
817 | + * You should have received a copy of the GNU Lesser General Public License |
818 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
819 | + */ |
820 | + |
821 | +import QtQuick 2.0 |
822 | +import Ubuntu.Components 0.1 |
823 | + |
824 | +Tab { |
825 | +} |
826 | |
827 | === modified file 'tests/unit_x11/tst_components/tst_tabs.qml' |
828 | --- tests/unit_x11/tst_components/tst_tabs.qml 2014-01-13 12:43:12 +0000 |
829 | +++ tests/unit_x11/tst_components/tst_tabs.qml 2014-04-10 11:34:46 +0000 |
830 | @@ -27,6 +27,13 @@ |
831 | id: emptyTabs |
832 | } |
833 | |
834 | + Component { |
835 | + id: dynamicTab |
836 | + Tab{ |
837 | + title: "OriginalTitle" |
838 | + } |
839 | + } |
840 | + |
841 | MainView { |
842 | id: mainView |
843 | anchors.fill: parent |
844 | @@ -424,5 +431,149 @@ |
845 | mouseRelease(tabs.tabBar, tabs.tabBar.width/2, tabs.tabBar.height/2); |
846 | compare(tabs.tabBar.pressed, false, "After releasing, pressed is false"); |
847 | } |
848 | + |
849 | + |
850 | + |
851 | + // these tests should not be mixed with Repeaters |
852 | + function test_z_addTab() { |
853 | + var newTab = tabs.addTab("Dynamic Tab", dynamicTab); |
854 | + compare((newTab !== null), true, "tab added"); |
855 | + compare(newTab.active, false, "the inserted tab is inactive"); |
856 | + compare(newTab.index, tabs.count - 1, "the tab is the last one"); |
857 | + } |
858 | + |
859 | + function test_z_addExternalTab() { |
860 | + var newTab = tabs.addTab("External Tab", Qt.resolvedUrl("ExternalTab.qml")); |
861 | + compare((newTab !== null), true, "tab added"); |
862 | + compare(newTab.active, false, "the inserted tab is inactive"); |
863 | + compare(newTab.index, tabs.count - 1, "the tab is the last one"); |
864 | + } |
865 | + |
866 | + function test_z_addTabWithDefaultTitle() { |
867 | + var newTab = tabs.addTab("", dynamicTab); |
868 | + compare((newTab !== null), true, "tab added"); |
869 | + compare(newTab.title, "OriginalTitle", "tab created with original title"); |
870 | + } |
871 | + |
872 | + function test_z_insertTab() { |
873 | + var tabIndex = Math.ceil(tabs.count / 2); |
874 | + var newTab = tabs.insertTab(tabIndex, "Inserted tab", dynamicTab); |
875 | + compare((newTab !== null), true, "tab inserted"); |
876 | + compare(newTab.index, tabIndex, "this is the first tab"); |
877 | + compare(tabs.selectedTab !== newTab, true, "the new tab is not the active one"); |
878 | + } |
879 | + |
880 | + function test_z_insertExternalTab() { |
881 | + var tabIndex = Math.ceil(tabs.count / 2); |
882 | + var newTab = tabs.insertTab(tabIndex, "Inserted External tab", Qt.resolvedUrl("ExternalTab.qml")); |
883 | + compare((newTab !== null), true, "tab inserted"); |
884 | + compare(newTab.index, tabIndex, "this is the first tab"); |
885 | + compare(tabs.selectedTab !== newTab, true, "the new tab is not the active one"); |
886 | + } |
887 | + |
888 | + function test_z_insertTabAtSelectedIndex() { |
889 | + tabs.selectedTabIndex = 1; |
890 | + var tabIndex = tabs.selectedTabIndex - 1; |
891 | + var newTab = tabs.insertTab(tabIndex, "InsertedAtSelected tab", dynamicTab); |
892 | + compare((newTab !== null), true, "tab inserted"); |
893 | + compare(newTab.index, tabIndex, "inserted at selected tab"); |
894 | + compare(tabs.selectedTabIndex != (tabIndex + 1), true, "it is not the selected tab"); |
895 | + } |
896 | + |
897 | + function test_z_insertTabFront() { |
898 | + var newTab = tabs.insertTab(-1, "PreTab", dynamicTab); |
899 | + compare(newTab !== null, true, "pre-tab inserted"); |
900 | + compare(newTab.index, 0, "this is the new first tab"); |
901 | + compare(tabs.selectedTab !== newTab, true, "the new tab is not the active one"); |
902 | + } |
903 | + |
904 | + function test_z_insertTabEnd() { |
905 | + var newTab = tabs.insertTab(tabs.count, "PostTab", dynamicTab); |
906 | + compare(newTab !== null, true, "post-tab inserted"); |
907 | + compare(newTab.index, tabs.count - 1, "thsi is the new last tab"); |
908 | + compare(tabs.selectedTab !== newTab, true, "the new tab is not the active one"); |
909 | + } |
910 | + |
911 | + function test_z_insertTabAndActivate() { |
912 | + var newTab = tabs.addTab("Inserted tab", dynamicTab); |
913 | + compare((newTab !== null), true, "tab inserted"); |
914 | + compare(newTab.index, tabs.count - 1, "the tab is the last one"); |
915 | + tabs.selectedTabIndex = newTab.index; |
916 | + compare(tabs.selectedTab, newTab, "the inserted tab is selected"); |
917 | + compare(newTab.active, true, "the new tab is active"); |
918 | + } |
919 | + |
920 | + function test_z_moveTab() { |
921 | + var selectedIndex = tabs.count - 1; |
922 | + tabs.selectedTabIndex = selectedIndex; |
923 | + compare(tabs.moveTab(0, selectedIndex), true, "first tab moved to last"); |
924 | + compare(tabs.selectedTabIndex, selectedIndex - 1, "the selected index moved backwards"); |
925 | + tabs.selectedTabIndex = selectedIndex = 0; |
926 | + compare(tabs.moveTab(selectedIndex, selectedIndex + 1), true, "selected tab moved as next"); |
927 | + compare(tabs.selectedTabIndex, selectedIndex + 1, "the selected index moved forewards"); |
928 | + } |
929 | + |
930 | + function test_z_moveSelectedTab() { |
931 | + tabs.selectedTabIndex = 0; |
932 | + tabs.moveTab(0, 1); |
933 | + compare(tabs.selectedTabIndex, 1, "selected tab moved"); |
934 | + } |
935 | + |
936 | + function test_z_moveTabFail() { |
937 | + compare(tabs.moveTab(-1, tabs.count - 1), false, "from-parameter out of range"); |
938 | + compare(tabs.moveTab(0, tabs.count), false, "to-parameter out of range"); |
939 | + } |
940 | + |
941 | + function test_z_removeTab() { |
942 | + compare(tabs.removeTab(tabs.count - 1), true, "last tab removed"); |
943 | + tabs.selectedTabIndex = 0; |
944 | + compare(tabs.removeTab(0), true, "active tab removed"); |
945 | + compare(tabs.selectedTabIndex, 0, "the next tab is selected") |
946 | + } |
947 | + |
948 | + function test_z_removeActiveTab() { |
949 | + tabs.selectedTabIndex = 1; |
950 | + compare(tabs.removeTab(1), true, "selected tab removed"); |
951 | + compare(tabs.selectedTabIndex, 1, "selected tab is next"); |
952 | + |
953 | + tabs.selectedTabIndex = tabs.count - 1; |
954 | + compare(tabs.removeTab(tabs.count - 1), true, "last tab removed"); |
955 | + compare(tabs.selectedTabIndex, tabs.count - 1, "selected tab moved to last item"); |
956 | + } |
957 | + |
958 | + function test_z_removeTabAfterActiveTab() { |
959 | + var activeTab = tabs.count - 2; |
960 | + tabs.selectedTabIndex = activeTab; |
961 | + compare(tabs.removeTab(tabs.count - 1), true, "last tab removed"); |
962 | + compare(tabs.selectedTabIndex, activeTab, "the selected tab wasn't moved"); |
963 | + } |
964 | + |
965 | + function test_z_removeTabBeforeActiveTab() { |
966 | + var activeTab = tabs.count - 1; |
967 | + tabs.selectedTabIndex = activeTab; |
968 | + compare(tabs.removeTab(0), true, "first tab removed"); |
969 | + compare(tabs.selectedTabIndex, activeTab - 1, "the selected tab index decreased"); |
970 | + } |
971 | + |
972 | + function test_zz_addTabAfterCleaningUpTabs() { |
973 | + while (tabs.count > 1) { |
974 | + tabs.removeTab(tabs.count - 1); |
975 | + } |
976 | + compare(tabs.selectedTabIndex, 0, "the only tab is the selected one"); |
977 | + // add a new tab anc check the count (default added tas should not be added anymore |
978 | + tabs.addTab("Second tab", dynamicTab); |
979 | + compare(tabs.count, 2, "we have two tabs only"); |
980 | + } |
981 | + |
982 | + function test_zz_addPredeclaredTab() { |
983 | + tabs.removeTab(tab1.index); |
984 | + |
985 | + // add a predeclared tab back with original title |
986 | + compare(tabs.addTab("", tab1), tab1, "tab1 was not added back"); |
987 | + compare(tab1.title, "tab 1", "the original title differs"); |
988 | + |
989 | + // add a predeclared tab which was added already |
990 | + compare(tabs.addTab("", tab1), null, "tab1 is already in tabs"); |
991 | + } |
992 | } |
993 | } |
994 | |
995 | === modified file 'tests/unit_x11/tst_test/tst_ubuntutestcase.qml' |
996 | --- tests/unit_x11/tst_test/tst_ubuntutestcase.qml 2014-02-13 10:27:14 +0000 |
997 | +++ tests/unit_x11/tst_test/tst_ubuntutestcase.qml 2014-04-10 11:34:46 +0000 |
998 | @@ -23,30 +23,57 @@ |
999 | width: 800 |
1000 | height: 600 |
1001 | |
1002 | - MouseArea { |
1003 | - id: mouseArea |
1004 | - objectName: "myMouseArea" |
1005 | - anchors.fill: parent |
1006 | - hoverEnabled: true |
1007 | - property int testX : 0 |
1008 | - property int testY : 0 |
1009 | - property int steps : 0 |
1010 | + Column { |
1011 | + anchors.fill: parent |
1012 | + MouseArea { |
1013 | + id: mouseArea |
1014 | + objectName: "myMouseArea" |
1015 | + width: parent.width |
1016 | + height: 300 |
1017 | + hoverEnabled: true |
1018 | + acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton |
1019 | + property int testX : 0 |
1020 | + property int testY : 0 |
1021 | + property int steps : 0 |
1022 | |
1023 | - onPositionChanged: { |
1024 | - testX = mouseX; |
1025 | - testY = mouseY; |
1026 | - steps++; |
1027 | - } |
1028 | + onPositionChanged: { |
1029 | + testX = mouseX; |
1030 | + testY = mouseY; |
1031 | + steps++; |
1032 | + } |
1033 | + } |
1034 | + Flickable { |
1035 | + id: flicker |
1036 | + width: parent.width |
1037 | + height: 400 |
1038 | + contentWidth: rect.width |
1039 | + contentHeight: rect.height |
1040 | + clip: true |
1041 | + Rectangle { |
1042 | + id: rect |
1043 | + color: "blue" |
1044 | + width: 1000 |
1045 | + height: 1000 |
1046 | + } |
1047 | + } |
1048 | } |
1049 | |
1050 | UbuntuTestCase { |
1051 | name: "TestTheUbuntuTestCase" |
1052 | when: windowShown |
1053 | |
1054 | + function init() { |
1055 | + mouseArea.steps = 0; |
1056 | + } |
1057 | + function cleanup() { |
1058 | + movementSpy.clear(); |
1059 | + longPressSpy.clear(); |
1060 | + } |
1061 | + |
1062 | function test_mouseMoveSlowly() { |
1063 | - mouseMoveSlowly(root,0,0,800,600,10,100); |
1064 | + mouseMoveSlowly(root,0,0,800,300,10,100); |
1065 | compare(mouseArea.testX,800); |
1066 | - compare(mouseArea.testY,600); |
1067 | + compare(mouseArea.testY,300); |
1068 | compare(mouseArea.steps,10); |
1069 | } |
1070 | |
1071 | @@ -58,5 +85,66 @@ |
1072 | child = findChild(root,"NoSuchChildHere"); |
1073 | compare(child===null,true,"When there is no child, function should return null"); |
1074 | } |
1075 | + |
1076 | + SignalSpy { |
1077 | + id: longPressSpy |
1078 | + target: mouseArea |
1079 | + signalName: "onPressAndHold" |
1080 | + } |
1081 | + |
1082 | + function test_longPress_left() { |
1083 | + longPressSpy.clear(); |
1084 | + mouseLongPress(mouseArea, mouseArea.width / 2, mouseArea.height / 2); |
1085 | + longPressSpy.wait(); |
1086 | + // cleanup |
1087 | + mouseRelease(mouseArea, mouseArea.width / 2, mouseArea.height / 2); |
1088 | + } |
1089 | + |
1090 | + function test_longPress_right() { |
1091 | + longPressSpy.clear(); |
1092 | + mouseLongPress(mouseArea, mouseArea.width / 2, mouseArea.height / 2, Qt.RightButton); |
1093 | + longPressSpy.wait(); |
1094 | + // cleanup |
1095 | + mouseRelease(mouseArea, mouseArea.width / 2, mouseArea.height / 2, Qt.RightButton); |
1096 | + } |
1097 | + |
1098 | + function test_longPress_middle() { |
1099 | + longPressSpy.clear(); |
1100 | + mouseLongPress(mouseArea, mouseArea.width / 2, mouseArea.height / 2, Qt.MiddleButton); |
1101 | + longPressSpy.wait(); |
1102 | + // cleanup |
1103 | + mouseRelease(mouseArea, mouseArea.width / 2, mouseArea.height / 2, Qt.MiddleButton); |
1104 | + } |
1105 | + |
1106 | + SignalSpy { |
1107 | + id: movementSpy |
1108 | + target: flicker |
1109 | + signalName: "onMovementEnded" |
1110 | + } |
1111 | + |
1112 | + function test_flick_default() { |
1113 | + flick(flicker, 0, 0, flicker.width, flicker.height); |
1114 | + movementSpy.wait(); |
1115 | + } |
1116 | + function test_flick_long() { |
1117 | + flick(flicker, 0, 0, flicker.width, flicker.height, -1, 10); |
1118 | + movementSpy.wait(); |
1119 | + } |
1120 | + function test_flick_short() { |
1121 | + flick(flicker, 0, 0, flicker.width, flicker.height, -1, 1); |
1122 | + movementSpy.wait(); |
1123 | + } |
1124 | + function test_flick_pressTimeout() { |
1125 | + flick(flicker, 0, 0, flicker.width, flicker.height, 400); |
1126 | + movementSpy.wait(); |
1127 | + } |
1128 | + function test_flick_pressTimeout_short() { |
1129 | + flick(flicker, flicker.width, flicker.height, -flicker.width, -flicker.height, 400, 1); |
1130 | + movementSpy.wait(); |
1131 | + } |
1132 | + function test_flick_pressTimeout_long() { |
1133 | + flick(flicker, flicker.width, flicker.height, -flicker.width, -flicker.height, 400, 100); |
1134 | + movementSpy.wait(); |
1135 | + } |
1136 | } |
1137 | } |
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://