Merge lp:~kalikiana/ubuntu-ui-toolkit/appsettings into lp:ubuntu-ui-toolkit

Proposed by Cris Dywan
Status: Superseded
Proposed branch: lp:~kalikiana/ubuntu-ui-toolkit/appsettings
Merge into: lp:ubuntu-ui-toolkit
Diff against target: 3194 lines (+1869/-290)
43 files modified
CHANGES (+1/-0)
components.api (+20/-1)
debian/control (+2/-0)
documentation/overview.qdoc (+2/-0)
documentation/settings.qdoc (+172/-0)
examples/ubuntu-ui-toolkit-gallery/AppSettings.qml (+112/-0)
examples/ubuntu-ui-toolkit-gallery/WidgetsModel.qml (+4/-0)
modules/Ubuntu/Components/CheckBox.qml (+11/-2)
modules/Ubuntu/Components/Option.qml (+139/-0)
modules/Ubuntu/Components/OptionSelector.qml (+1/-1)
modules/Ubuntu/Components/Settings.qml (+81/-0)
modules/Ubuntu/Components/Tab.qml (+0/-7)
modules/Ubuntu/Components/Tabs.qml (+292/-20)
modules/Ubuntu/Components/TextField.qml (+2/-0)
modules/Ubuntu/Components/Themes/Ambiance/TabBarStyle.qml (+9/-2)
modules/Ubuntu/Components/plugin/adapters/alarmsadapter_organizer.cpp (+56/-23)
modules/Ubuntu/Components/plugin/adapters/alarmsadapter_p.h (+2/-0)
modules/Ubuntu/Components/plugin/alarmmanager_p.h (+2/-0)
modules/Ubuntu/Components/plugin/plugin.cpp (+0/-5)
modules/Ubuntu/Components/plugin/quickutils.cpp (+15/-0)
modules/Ubuntu/Components/plugin/quickutils.h (+1/-0)
modules/Ubuntu/Components/plugin/ucalarm.cpp (+33/-27)
modules/Ubuntu/Components/plugin/ucalarm_p.h (+1/-1)
modules/Ubuntu/Components/qmldir (+2/-0)
modules/Ubuntu/Test/UbuntuTestCase.qml (+69/-1)
modules/Ubuntu/Test/deployment.pri (+6/-1)
modules/Ubuntu/Test/plugin/uctestcase.cpp (+12/-0)
modules/Ubuntu/Test/plugin/uctestcase.h (+1/-1)
tests/autopilot/ubuntuuitoolkit/emulators.py (+23/-0)
tests/autopilot/ubuntuuitoolkit/tests/test_emulators.py (+197/-0)
tests/resources/alarm/Alarms.qml (+6/-2)
tests/resources/navigation/Tabs.qml (+90/-1)
tests/unit/runtest.sh (+1/-2)
tests/unit/tst_alarms/tst_alarms.cpp (+171/-20)
tests/unit/tst_mainview/tst_mainview.cpp (+3/-6)
tests/unit/tst_page/tst_page.cpp (+3/-39)
tests/unit_x11/tst_components/ExternalTab.qml (+21/-0)
tests/unit_x11/tst_components/tst_tabs.qml (+151/-0)
tests/unit_x11/tst_mousefilters/tst_mousefilterstest.cpp (+11/-29)
tests/unit_x11/tst_orientation/tst_orientation.cpp (+9/-47)
tests/unit_x11/tst_statesaver/tst_statesaver.cpp (+29/-37)
tests/unit_x11/tst_test/tst_ubuntutestcase.qml (+103/-15)
tests/unit_x11/tst_theme_engine/tst_theme_enginetest.cpp (+3/-0)
To merge this branch: bzr merge lp:~kalikiana/ubuntu-ui-toolkit/appsettings
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Needs Fixing
Zsombor Egri Needs Fixing
Review via email: mp+181304@code.launchpad.net

This proposal has been superseded by a proposal from 2014-04-10.

Commit message

Implement Settings and Option API with examples

Split action support in components into separate branches:
lp:~kalikiana/ubuntu-ui-toolkit/checktrigger
lp:~kalikiana/ubuntu-ui-toolkit/optiontrigger
lp:~kalikiana/ubuntu-ui-toolkit/texttrigger
Excluding built-in types from qmlapicheck which includes U1db types:
✓ lp:~kalikiana/ubuntu-ui-toolkit/skipnuildinapi
Unrelated bugs affecting test case:
✓ lp:~ubuntu-sdk-team/ubuntu-ui-toolkit/fix1244660 (MainView.qml:257: TypeError: Cannot call method 'hasOwnProperty')
✓ lp:~kalikiana/ubuntu-ui-toolkit/unit_x11_fixes (TextField.qml:839: TypeError: Cannot read property 'Integer')
UbuntuTestCase C++ API to make tests more consistent:
  lp:~kalikiana/ubuntu-ui-toolkit/ubuntuTestCaseCxx

To post a comment you must log in.
Revision history for this message
Cris Dywan (kalikiana) wrote :

TODO: Add document on how to use Settings API

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

I think this MR shoul dbe split in two ones at least... One is which fixes trigge() and TextField stuff, then a separate one which introduces the App Settings.

What is the U1DB API in the components.api?

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

Meh…

+++ components.api.new 2013-11-07 14:57:07.914501464 +0000
@@ -36,6 +36,7 @@ modules/Ubuntu/Components/Button.qml
 modules/Ubuntu/Components/CheckBox.qml
 AbstractButton
     property bool checked
+ function trigger()
 modules/Ubuntu/Components/CrossFadeImage.qml
 Item
     property url source

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

Can we have more setting groups defined in an application? how do we select between those? Will the Settings.gropup property change cause settings reload?

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

735 +Item {
736 + id: settings

Do we need an Item for this? In that way we can put visual items onto it, but that does not make any sense... Shouldn't we use Object instead? (not QtObject, we have the Object declared which can have children too).

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

753 + Component.onCompleted: {

What if children get changed after this point? Do we exclude them from state saving?

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

I'd rather like to see something like
Object {
   ...
    default property list<Option> options
   ---
}

So we make sure we cannot add anything else into the Settings blob... what do you think?

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

Please close each JS line with semicolon.

review: Needs Fixing
Revision history for this message
Cris Dywan (kalikiana) wrote :

> Can we have more setting groups defined in an application? how do we select between those?

Yes, by using the "group" property which is a string.

> Will the Settings.gropup property change cause settings reload?

No and I don't see a use case for changing groups at runtime, so I added a warning that's shown if you try.

> 735 +Item {
> 736 + id: settings
> Do we need an Item for this? In that way we can put visual items onto it, but that does not make any sense... Shouldn't we use Object instead?

Yes and no. It must be an Item so that you can put it into another Item. It doesn't say anything about what you can put inside.

> 753 + Component.onCompleted: {
> What if children get changed after this point? Do we exclude them from state saving?

I changed it, this didn't work before I ported to a custom default property.

> default property list<Option> options
> So we make sure we cannot add anything else into the Settings blob

I failed to make this work before, I gave it another try. I managed to get it to work by using an alias on a second property which is the real list. If I declare it as one I get syntax errors. And indeed with this I could drop the hasOwnProperty checks. Though the error message generated by QML is not really great, it gets us an early failure.

> Please close each JS line with semicolon.

Done.

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

> > Will the Settings.gropup property change cause settings reload?
>
> No and I don't see a use case for changing groups at runtime, so I added a
> warning that's shown if you try.

A use case is when you define different profiles for instance. Assume you have a host/port/login/password quadruple for "default", "home", "other device" groups. Then in your app you select the setting to be loaded based on the group itself. I'm talking about the settings use, not about its declaration.

>
> > 735 +Item {
> > 736 + id: settings
> > Do we need an Item for this? In that way we can put visual items onto it,
> but that does not make any sense... Shouldn't we use Object instead?
>
> Yes and no. It must be an Item so that you can put it into another Item. It
> doesn't say anything about what you can put inside.

You can put QtObjects inside Items, not only Items. The danger in deriving from Item is that you can declare visual components inside the Settings upon use, which may cause problems (as Settings' default w/h is 0).

I'd rather go for QtObject using the same approach for children definition as we have in Object.

>
> > 753 + Component.onCompleted: {
> > What if children get changed after this point? Do we exclude them from state
> saving?
>
> I changed it, this didn't work before I ported to a custom default property.
>
> > default property list<Option> options
> > So we make sure we cannot add anything else into the Settings blob
>
> I failed to make this work before, I gave it another try. I managed to get it
> to work by using an alias on a second property which is the real list. If I
> declare it as one I get syntax errors. And indeed with this I could drop the
> hasOwnProperty checks. Though the error message generated by QML is not really
> great, it gets us an early failure.

Yep, that's the way to do it, just like we have in Object. Btw, you could make that aliased property internal, as the name is not really nice :D

>
> > Please close each JS line with semicolon.
>
> Done.

Cool.

review: Needs Fixing
Revision history for this message
Cris Dywan (kalikiana) wrote :

> A use case is when you define different profiles for instance. Assume you have a host/port/login/password
> quadruple for "default", "home", "other device" groups. Then in your app you select the setting to be
> loaded based on the group itself. I'm talking about the settings use, not about its declaration.

That's something I didn't consider in this way… I need to think about that a bit more. I'll get back to this.

> You can put QtObjects inside Items, not only Items. The danger in deriving from Item is that you can declare visual components inside the Settings upon use

I tried it before and it doesn't work. If I declare Settings as Object it errors when declaring it in the gallery Settings example complaining that it can't be added to the list. This is with a TemplateRow, a normal component used in all the gallery examples.

As for as children goes, using list<Option> defines exactly what you can put inside, and you cannot add components in there anymore, so I think we are good on that front.

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

> > A use case is when you define different profiles for instance. Assume you
> have a host/port/login/password
> > quadruple for "default", "home", "other device" groups. Then in your app you
> select the setting to be
> > loaded based on the group itself. I'm talking about the settings use, not
> about its declaration.
>
> That's something I didn't consider in this way… I need to think about that a
> bit more. I'll get back to this.
>
> > You can put QtObjects inside Items, not only Items. The danger in deriving
> from Item is that you can declare visual components inside the Settings upon
> use
>
> I tried it before and it doesn't work. If I declare Settings as Object it
> errors when declaring it in the gallery Settings example complaining that it
> can't be added to the list. This is with a TemplateRow, a normal component
> used in all the gallery examples.

Weird... let's keep it Item derived one, having the default property redirection would exclude the non-Option based children anyway.

Let's get the group change sorted then.

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

FAIL! : tst_MainView::testLocalStorage() 'QFile::exists(databaseFolder)' returned FALSE. ()
   Loc: [tst_mainview.cpp(140)]

731. By Cris Dywan

Merge lp:ubuntu-ui-toolkit

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
732. By Cris Dywan

Merge lp:ubuntu-ui-toolkit

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
733. By Cris Dywan

Merge lp:ubuntu-ui-toolkit

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

I have few more question (perhaps we discussed about that but wasn't logged here):
1. can a Setting element be declared inside other elements? Maybe to understand it better, let's take the following example:

Page1.qml
Page {
    Settings {
        group: "page1group"
        Option {
        }
        // [...]
    }
}

Then main.qml
MainView {
    Settings {
        group: "whatever"
        // [...]
    }
}

2. Can one declare the same group in two different document? Taken the previous example, have the group "page1group" also in main.qml. If yes, do I have to have the same amount of Options in both places?

Asking this as in this way it would be easier to use the settings in individual documents rather than using the id of the setting from the topmost QML document of the app.

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

915 +//#include <QtQuick/private/qquickanchors_p.h>
916 +//#include <QtQuick/private/qquickanchors_p_p.h>

Please remove these two lines.

review: Needs Fixing
734. By Cris Dywan

Drop uncommented private headers from settings test

735. By Cris Dywan

Merge lp:ubuntu-ui-toolkit

736. By Cris Dywan

Delete settings.db at the start of the settings test

737. By Cris Dywan

Drop sync and persistent settings for now

738. By Cris Dywan

Strengthen defaults test to rule out missing settings

739. By Cris Dywan

Add more explicit checks of the internal document

Revision history for this message
Cris Dywan (kalikiana) wrote :

> 1. can a Setting element be declared inside other elements? Maybe to
> understand it better, let's take the following example:
>
> Page1.qml
> Page {
> Settings {

Anywhere is fine as these are not visual components.

> 2. Can one declare the same group in two different document? Taken the
> previous example, have the group "page1group" also in main.qml. If yes, do I
> have to have the same amount of Options in both places?
>
> Asking this as in this way it would be easier to use the settings in
> individual documents rather than using the id of the setting from the topmost
> QML document of the app.

You can declare the same group in one place only. Anything else would mean you never know if the group is complete. Having part of a group in a separate file or Loader would lead to undefined behavior and data loss.

It's still very flexible I think. You can declare Settings in a separate file as long as it's imported where it's needed. Separate groups can be used - they can be put in a Loader without any risk. If you bind components to existing options they also don't need to be in the same file.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
740. By Cris Dywan

More refinements and more sanity checks in unit test

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
741. By Cris Dywan

Merge lp:ubuntu-ui-toolkit

742. By Cris Dywan

Merge lp:ubuntu-ui-toolkit

743. By Cris Dywan

Revamp test cases using test columns and pure C++

744. By Cris Dywan

Add double option and component-based value changes

745. By Cris Dywan

First stab at Settings emulator

Revision history for this message
Tim Peeters (tpeeters) wrote :

Is is it really necessary to add a trigger() function to CheckBox? Can onCheckedChanged be used instead?

If trigger() is \internal, it should be __trigger()

746. By Cris Dywan

Work-around inability to select non-visual components

747. By Cris Dywan

Verify flipped switches and clear the settings via a method

748. By Cris Dywan

Put switch in a property and use Eventually(Equals(

749. By Cris Dywan

No properties, restart within test, clear as class method

750. By Cris Dywan

Set __doc.docId when create is set to true

751. By Cris Dywan

Duoble-check that the database file exists

752. By Cris Dywan

Add string option and TextField component

753. By Cris Dywan

Add OptionSelector to Settings Autopilot test

754. By Cris Dywan

Use scenarios to test both default group and custom groups

755. By Cris Dywan

More concise code in autopilot code to conform to pep8

756. By Cris Dywan

Use multiple documents instead of one storage

Also use states to handle initialization

757. By Cris Dywan

Drop broken settings unit tests

758. By Cris Dywan

Merge lp:~ubuntu-sdk-team/ubuntu-ui-toolkit/staging

759. By Cris Dywan

Reword reasoning of CheckBox.trigger

760. By Cris Dywan

Maybe we should be pressing Enter

761. By Cris Dywan

Losing focus should suffice to save the value

762. By Cris Dywan

Use different syntax to avoid warning from property binding

763. By Cris Dywan

Move the real list property into an internal object

764. By Cris Dywan

Add qmlproperty annotation for Option

765. By Cris Dywan

Extend the settings tutorial with custom component example

766. By Cris Dywan

Merge lp:~ubuntu-sdk-team/ubuntu-ui-toolkit/staging

767. By Cris Dywan

Port settings test cases to new OptionSelector emulator

768. By Cris Dywan

Drop redundant [ ] in the State declared in Option

769. By Cris Dywan

Add parantheses around for and if blocks

770. By Cris Dywan

Merge lp:~ubuntu-sdk-team/ubuntu-ui-toolkit/staging

771. By Cris Dywan

Move Settings and Option to 1.1

772. By Cris Dywan

Merge lp:~ubuntu-sdk-team/ubuntu-ui-toolkit/staging

773. By Cris Dywan

Port Settings emulator to custom proxy objects

774. By Cris Dywan

Make flake8 happy

775. By Cris Dywan

Import Settings from custom proxy objects

Revision history for this message
Florian Boucault (fboucault) wrote :

Unless there is a solid reason against it, being API compatible with http://qt-project.org/doc/qt-5/qml-qt-labs-settings-settings.html would make us more future proof and would be easier for developers at large.

Revision history for this message
Cris Dywan (kalikiana) wrote :

There's been a lot of discussion around it. The Labs Settings API is limited for the purposes of defining defaults, binding values to components and abstracting when changes are preserved. Though if you have a specific idea around it feel free to file a bug with a proposal.

Revision history for this message
Florian Boucault (fboucault) wrote :

The specific use cases you are referring to have not been described in a
written form so it is hard for me to understand them. Can we add code
examples of things that cannot be done with the labs API?
On Jun 16, 2014 10:10 AM, "Christian Dywan" <email address hidden> wrote:

> There's been a lot of discussion around it. The Labs Settings API is
> limited for the purposes of defining defaults, binding values to components
> and abstracting when changes are preserved. Though if you have a specific
> idea around it feel free to file a bug with a proposal.
> --
>
> https://code.launchpad.net/~kalikiana/ubuntu-ui-toolkit/appsettings/+merge/181304
> Your team Ubuntu SDK team is subscribed to branch lp:ubuntu-ui-toolkit.
>

776. By Cris Dywan

Merge lp:~ubuntu-sdk-team/ubuntu-ui-toolkit/staging

777. By Cris Dywan

Merge lp:~ubuntu-sdk-team/ubuntu-ui-toolkit/staging

Unmerged revisions

777. By Cris Dywan

Merge lp:~ubuntu-sdk-team/ubuntu-ui-toolkit/staging

776. By Cris Dywan

Merge lp:~ubuntu-sdk-team/ubuntu-ui-toolkit/staging

775. By Cris Dywan

Import Settings from custom proxy objects

774. By Cris Dywan

Make flake8 happy

773. By Cris Dywan

Port Settings emulator to custom proxy objects

772. By Cris Dywan

Merge lp:~ubuntu-sdk-team/ubuntu-ui-toolkit/staging

771. By Cris Dywan

Move Settings and Option to 1.1

770. By Cris Dywan

Merge lp:~ubuntu-sdk-team/ubuntu-ui-toolkit/staging

769. By Cris Dywan

Add parantheses around for and if blocks

768. By Cris Dywan

Drop redundant [ ] in the State declared in Option

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CHANGES'
2--- CHANGES 2014-02-24 22:03:55 +0000
3+++ CHANGES 2014-04-10 15:55:52 +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 15:55:52 +0000
16@@ -35,6 +35,7 @@
17 modules/Ubuntu/Components/CheckBox.qml
18 AbstractButton
19 property bool checked
20+ function trigger()
21 modules/Ubuntu/Components/Colors/UbuntuColors.qml
22 QtObject
23 readonly property color orange
24@@ -198,6 +199,12 @@
25 modules/Ubuntu/Components/Object.qml
26 QtObject
27 default property internal children
28+modules/Ubuntu/Components/Option.qml
29+Action
30+ property var name
31+ property var defaultValue
32+ property var value
33+ property var __internal
34 modules/Ubuntu/Components/OptionSelector.qml
35 ListItem.Empty
36 property var model
37@@ -379,6 +386,11 @@
38 property int align
39 property bool __interactive
40 property internal __private
41+modules/Ubuntu/Components/Settings.qml
42+Item
43+ property string group
44+ property list<Option> couldntGetListToWorkWithoutAlias
45+ default property internal options
46 modules/Ubuntu/Components/Slider.qml
47 StyledItem
48 property real minimumValue
49@@ -417,9 +429,14 @@
50 readonly property Tab selectedTab
51 readonly property Item currentPage
52 property TabBar tabBar
53- default property list<Item> tabChildren
54+ property list<Item> tabChildren
55 readonly property int count
56 signal modelChanged()
57+ function addTab(title, tab)
58+ function insertTab(index, title, tab)
59+ function getTab(index)
60+ function moveTab(from, to)
61+ function removeTab(index)
62 modules/Ubuntu/Components/TextArea.qml
63 StyledItem
64 property bool highlighted
65@@ -593,6 +610,8 @@
66 function findChild(obj,objectName)
67 function findInvisibleChild(obj,objectName)
68 function mouseMoveSlowly(item,x,y,dx,dy,steps,stepdelay)
69+ function flick(item, x, y, dx, dy, pressTimeout, steps, button, modifiers, delay)
70+ function mouseLongPress(item, x, y, button, modifiers, delay)
71 function tryCompareFunction(func, expectedResult, timeout)
72 plugins.qmltypes
73 name: "InverseMouseAreaType"
74
75=== modified file 'debian/control'
76--- debian/control 2014-03-17 09:02:58 +0000
77+++ debian/control 2014-04-10 15:55:52 +0000
78@@ -20,6 +20,7 @@
79 qtdeclarative5-qtquick2-plugin,
80 qtdeclarative5-test-plugin,
81 qtdeclarative5-window-plugin,
82+ qtdeclarative5-u1db1.0,
83 qtdeclarative5-qtfeedback-plugin,
84 qtdeclarative5-unity-action-plugin (>= 1.1.0),
85 qtdeclarative5-localstorage-plugin,
86@@ -54,6 +55,7 @@
87 libqt5svg5,
88 qtdeclarative5-qtquick2-plugin,
89 qtdeclarative5-window-plugin,
90+ qtdeclarative5-u1db1.0,
91 qtdeclarative5-qtfeedback-plugin,
92 qtdeclarative5-unity-action-plugin (>= 1.1.0),
93 ttf-ubuntu-font-family,
94
95=== modified file 'documentation/overview.qdoc'
96--- documentation/overview.qdoc 2014-01-23 17:13:24 +0000
97+++ documentation/overview.qdoc 2014-04-10 15:55:52 +0000
98@@ -26,6 +26,8 @@
99 used when setting the size of UI elements (widgets, fonts, etc.) in order
100 for them to behave well on a variety of devices.
101 \li \l{Automatic State Saving} provides automatic property saving for components.
102+ \li Applications can define \l{Application Settings} which can be bound to components,
103+ handle default values and store changes.
104 \endlist
105
106 \part Basic QML Types
107
108=== added file 'documentation/settings.qdoc'
109--- documentation/settings.qdoc 1970-01-01 00:00:00 +0000
110+++ documentation/settings.qdoc 2014-04-10 15:55:52 +0000
111@@ -0,0 +1,172 @@
112+/*
113+ * Copyright 2013 Canonical Ltd.
114+ *
115+ * This program is free software; you can redistribute it and/or modify
116+ * it under the terms of the GNU Lesser General Public License as published by
117+ * the Free Software Foundation; version 3.
118+ *
119+ * This program is distributed in the hope that it will be useful,
120+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
121+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
122+ * GNU Lesser General Public License for more details.
123+ *
124+ * You should have received a copy of the GNU Lesser General Public License
125+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
126+ */
127+
128+/*!
129+ \page ubuntu-settings.html
130+ \title Application Settings
131+
132+ Typically applications have a few user-facing options that customize the UI or
133+ change behavior for different use cases. Let's start with a simple example:
134+
135+ \qml
136+ import QtQuick 2.0
137+ import Ubuntu.Components 0.1
138+
139+ MainView {
140+ applicationName: "example"
141+
142+ Settings {
143+ Option {
144+ id: optionVibrate
145+ name: "vibrate"
146+ defaultValue: false
147+ }
148+ }
149+
150+ Switch {
151+ action: optionVibrate
152+ }
153+ }
154+ \endqml
155+
156+ In a new application, we may initially just have one option which allows the
157+ user to enable vibrations, and is off by default. That's all that is needed
158+ for it to work, the API knows enough to load and save the value behind the scenes.
159+
160+ In the next iteration we may need a user name and age to be entered.
161+
162+ \qml
163+ import QtQuick 2.0
164+ import Ubuntu.Components 0.1
165+
166+ MainView {
167+ applicationName: "example"
168+
169+ Settings {
170+ Option {
171+ id: optionName
172+ name: "name"
173+ defaultValue: "Jane Doe"
174+ }
175+ Option {
176+ id: optionAge
177+ name: "age"
178+ defaultValue: 99
179+ }
180+ }
181+
182+ TextField {
183+ action: optionName
184+ }
185+ TextField {
186+ action: optionAge
187+ }
188+ }
189+ \endqml
190+
191+ Again all we need to define is a name and a default value. The id is used to
192+ be able to bind the options to UI components.
193+
194+ How about using those values in the app code?
195+
196+ \qml
197+ import QtQuick 2.0
198+ import Ubuntu.Components 0.1
199+
200+ Column {
201+ Label {
202+ text: i18n.tr("Name: %1").arg(optionName.value)
203+ }
204+ Label {
205+ text: i18n.tr("Age: %1").arg(optionAge.value)
206+ }
207+ Button {
208+ text: i18n.tr("Start playing")
209+ onClicked: {
210+ if (optionAge.value < 18)
211+ console.log("Sorry but this game is for adults.")
212+ else
213+ console.log("Game started by the looks of it")
214+ }
215+ }
216+ }
217+ \endqml
218+
219+ Note that there's no need to worry about the default values in most cases - the
220+ value reflects the saved or default automatically. If you need to ensure that
221+ a value was given by the user you can however check it easily.
222+
223+ Let's try choosing a value from several choices where the user must choose one.
224+
225+ \qml
226+ import QtQuick 2.0
227+ import Ubuntu.Components 0.1
228+
229+ Column {
230+ Settings {
231+ Option {
232+ name: "plan"
233+ defaultValue: 0
234+ }
235+ OptionSelector {
236+ action: optionPlan
237+ model: [ "Undecided", "Phone only", "500G", "1G is enough" ]
238+ }
239+ }
240+ Button {
241+ text: i18n.tr("Start playing")
242+ onClicked: {
243+ if (optionPlan.value == 0)
244+ console.log("You must pick a data plan to continue.")
245+ else
246+ console.log("Good choice, sir")
247+ }
248+ }
249+ }
250+ \endqml
251+
252+ As our app is starting to get really big let's use some grouping.
253+
254+ \qml
255+ import QtQuick 2.0
256+ import Ubuntu.Components 0.1
257+
258+ MainView {
259+ applicationName: "example"
260+
261+ Settings {
262+ group: "browser"
263+ persistent: false
264+ Option {
265+ id: optionUrl
266+ name: "homepage"
267+ defaultValue: "http://www.duckduckgo.com"
268+ }
269+ }
270+
271+ TextField {
272+ action: optionUrl
273+ }
274+ }
275+ \endqml
276+
277+ Grouping of settings is entirely optional. Without a group name all options
278+ are considered part of the "default" group.
279+
280+ We also just unset the persistant property, which means nothing will be saved
281+ until the property is set to true.
282+*/
283+
284
285=== added file 'examples/ubuntu-ui-toolkit-gallery/AppSettings.qml'
286--- examples/ubuntu-ui-toolkit-gallery/AppSettings.qml 1970-01-01 00:00:00 +0000
287+++ examples/ubuntu-ui-toolkit-gallery/AppSettings.qml 2014-04-10 15:55:52 +0000
288@@ -0,0 +1,112 @@
289+/*
290+ * Copyright 2013 Canonical Ltd.
291+ *
292+ * This program is free software; you can redistribute it and/or modify
293+ * it under the terms of the GNU Lesser General Public License as published by
294+ * the Free Software Foundation; version 3.
295+ *
296+ * This program is distributed in the hope that it will be useful,
297+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
298+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
299+ * GNU Lesser General Public License for more details.
300+ *
301+ * You should have received a copy of the GNU Lesser General Public License
302+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
303+ */
304+
305+import QtQuick 2.0
306+import Ubuntu.Components 0.1
307+
308+Template {
309+ TemplateSection {
310+ className: "Settings"
311+
312+ TemplateRow {
313+ title: i18n.tr("Boolean")
314+
315+ Settings {
316+ group: "setting_boolean"
317+ Option {
318+ id: optionVibrate
319+ name: "vibrate"
320+ defaultValue: false
321+ text: i18n.tr("Vibrate")
322+ }
323+ }
324+
325+ Label {
326+ text: optionVibrate.value
327+ }
328+
329+ Switch {
330+ action: optionVibrate
331+ }
332+ }
333+
334+ TemplateRow {
335+ title: i18n.tr("String")
336+ Settings {
337+ group: "settings_string"
338+ Option {
339+ id: optionHomepage
340+ name: "homepage"
341+ defaultValue: "http://www.duckduckgo.com"
342+ text: i18n.tr("Homepage")
343+ }
344+ }
345+
346+ Label {
347+ text: optionHomepage.value
348+ }
349+
350+ TextField {
351+ action: optionHomepage
352+ }
353+ }
354+
355+ TemplateRow {
356+ title: i18n.tr("Integer")
357+ Settings {
358+ group: "settings_int"
359+ Option {
360+ id: optionYearOfBirth
361+ name: "yeahOfBirth"
362+ defaultValue: 1975.2
363+ text: i18n.tr("Year of Birth")
364+ }
365+ }
366+
367+ Label {
368+ text: optionYearOfBirth.value
369+ }
370+
371+ TextField {
372+ action: optionYearOfBirth
373+ }
374+ }
375+
376+ TemplateRow {
377+ title: i18n.tr("Choices")
378+ Settings {
379+ group: "settings_choices"
380+ Option {
381+ id: optionHairColor
382+ name: "hairColor"
383+ defaultValue: 0
384+ text: i18n.tr("Hair Color")
385+ }
386+ }
387+
388+ Label {
389+ text: optionHairColor.value
390+ }
391+
392+ OptionSelector {
393+ action: optionHairColor
394+ expanded: true
395+ height: units.gu(20)
396+ model: [ "Black", "Ginger", "Peroxided", "White" ]
397+ }
398+ }
399+ }
400+}
401
402=== modified file 'examples/ubuntu-ui-toolkit-gallery/WidgetsModel.qml'
403--- examples/ubuntu-ui-toolkit-gallery/WidgetsModel.qml 2014-01-13 15:31:14 +0000
404+++ examples/ubuntu-ui-toolkit-gallery/WidgetsModel.qml 2014-04-10 15:55:52 +0000
405@@ -35,6 +35,10 @@
406 source: "Buttons.qml"
407 }
408 ListElement {
409+ label: "Settings"
410+ source: "AppSettings.qml"
411+ }
412+ ListElement {
413 objectName: "slidersElement"
414 label: "Slider"
415 source: "Sliders.qml"
416
417=== modified file 'modules/Ubuntu/Components/CheckBox.qml'
418--- modules/Ubuntu/Components/CheckBox.qml 2013-10-01 14:46:11 +0000
419+++ modules/Ubuntu/Components/CheckBox.qml 2014-04-10 15:55:52 +0000
420@@ -45,12 +45,21 @@
421 Specifies whether the checkbox is checked or not. By default the property
422 is set to false.
423 */
424- property bool checked: false
425+ property bool checked: action && action.hasOwnProperty("value") && typeof action.value == "boolean" ? action.value : false
426+
427+ /*!
428+ \internal
429+ Pass the real value, ActionItem.trigger otherwise causes a type mismatch error.
430+ */
431+ function trigger() {
432+ if (enabled)
433+ triggered(!checked)
434+ }
435
436 /*!
437 \internal
438 */
439- onTriggered: checked = !checked
440+ onTriggered: checked = value
441
442 style: Theme.createStyleComponent("CheckBoxStyle.qml", checkBox)
443 }
444
445=== added file 'modules/Ubuntu/Components/Option.qml'
446--- modules/Ubuntu/Components/Option.qml 1970-01-01 00:00:00 +0000
447+++ modules/Ubuntu/Components/Option.qml 2014-04-10 15:55:52 +0000
448@@ -0,0 +1,139 @@
449+/*
450+ * Copyright 2013 Canonical Ltd.
451+ *
452+ * This program is free software; you can redistribute it and/or modify
453+ * it under the terms of the GNU Lesser General Public License as published by
454+ * the Free Software Foundation; version 3.
455+ *
456+ * This program is distributed in the hope that it will be useful,
457+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
458+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
459+ * GNU Lesser General Public License for more details.
460+ *
461+ * You should have received a copy of the GNU Lesser General Public License
462+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
463+ */
464+
465+import QtQuick 2.0
466+import U1db 1.0 as U1db
467+import Ubuntu.Unity.Action 1.0 as UnityActions
468+
469+/*!
470+ \qmltype Option
471+ \inqmlmodule Ubuntu.Components 0.1
472+ \ingroup ubuntu
473+ \brief An option within application-specific settings.
474+
475+ Example:
476+ \qml
477+ import QtQuick 2.0
478+ import Ubuntu.Components 0.1
479+
480+ MainView {
481+ applicationName: "example"
482+ Settings {
483+ group: "sound"
484+ Option {
485+ name: "vibrate"
486+ defaultValue: false
487+ }
488+ Option {
489+ id: optionNickname
490+ name: "nomDePlum"
491+ defaultValue: "A. Nonymous"
492+ text: i18n.tr("Pseudonym")
493+ visible: proFeaturesUnlocked // variable defined in the application
494+ }
495+ }
496+
497+ Column {
498+ Row {
499+ Label {
500+ text: optionVibrate.text
501+ }
502+ Switch {
503+ action: optionVibrate
504+ }
505+ }
506+ Row {
507+ Label {
508+ text: optionNickname.text
509+ }
510+ TextField {
511+ action: optionNickname
512+ }
513+ }
514+ }
515+ }
516+ \endqml
517+
518+ See Settings for more examples.
519+*/
520+Action {
521+ id: option
522+ /*!
523+ A name for the setting that is unique in the group.
524+ */
525+ property var name: null
526+ /*!
527+ The default value. Any basic type is allowed. After the first time an
528+ option is used on one particular device, the default is ignored unless
529+ the group is reset.
530+ */
531+ property var defaultValue: null
532+ /*!
533+ The current value, either a default, locally saved state or synchronized.
534+ */
535+ property var value: defaultValue
536+
537+ /*!
538+ \internal
539+ The default value changed. From this we assume the value type.
540+ */
541+ onDefaultValueChanged: {
542+ if (typeof defaultValue == "string")
543+ parameterType = UnityActions.Action.String;
544+ else if (typeof defaultValue == "number")
545+ parameterType = UnityActions.Action.Real; // Javascript doesn't distinguish int and real
546+ else if (typeof defaultValue == "boolean")
547+ parameterType = UnityActions.Action.Bool;
548+ else
549+ parameterType = UnityActions.Action.None;
550+ }
551+
552+ /*!
553+ \internal
554+ Store the new value when the action is triggered.
555+ */
556+ onTriggered: option.value = value
557+
558+ /* Can't declare anonymous Object because parent class is no Item */
559+ property var __internal: Item {
560+ property string group: ""
561+ onGroupChanged: {
562+ if (name == null || defaultValue == null)
563+ console.log("Incomplete Option declaration %1 in %2".arg(objectName).arg(group));
564+ else
565+ state = "valid";
566+ }
567+ states: [ State {
568+ name: "valid"
569+ PropertyChanges {
570+ target: doc
571+ docId: "%1-%2".arg(__internal.group).arg(name)
572+ }
573+ PropertyChanges {
574+ target: option
575+ value: doc.contents.value
576+ onValueChanged: doc.contents = { "value": value }
577+ }
578+ } ]
579+ U1db.Document {
580+ id: doc
581+ database: U1db.Database {
582+ path: "settings.db"
583+ }
584+ }
585+ }
586+}
587+
588
589=== modified file 'modules/Ubuntu/Components/OptionSelector.qml'
590--- modules/Ubuntu/Components/OptionSelector.qml 2013-12-13 15:50:35 +0000
591+++ modules/Ubuntu/Components/OptionSelector.qml 2014-04-10 15:55:52 +0000
592@@ -282,7 +282,7 @@
593 onDelegateClicked: optionSelector.delegateClicked(index);
594 interactive: listContainer.height !== list.contentHeight && listContainer.currentlyExpanded ? true : false
595 clip: true
596- currentIndex: 0
597+ currentIndex: action && action.hasOwnProperty("value") && typeof action.value == "number" ? action.value : 0
598 model: optionSelector.model
599 anchors.fill: parent
600
601
602=== added file 'modules/Ubuntu/Components/Settings.qml'
603--- modules/Ubuntu/Components/Settings.qml 1970-01-01 00:00:00 +0000
604+++ modules/Ubuntu/Components/Settings.qml 2014-04-10 15:55:52 +0000
605@@ -0,0 +1,81 @@
606+/*
607+ * Copyright 2013 Canonical Ltd.
608+ *
609+ * This program is free software; you can redistribute it and/or modify
610+ * it under the terms of the GNU Lesser General Public License as published by
611+ * the Free Software Foundation; version 3.
612+ *
613+ * This program is distributed in the hope that it will be useful,
614+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
615+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
616+ * GNU Lesser General Public License for more details.
617+ *
618+ * You should have received a copy of the GNU Lesser General Public License
619+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
620+ */
621+
622+import QtQuick 2.0
623+import U1db 1.0 as U1db
624+import Ubuntu.Components 0.1 as Toolkit
625+
626+/*!
627+ \qmltype Settings
628+ \inqmlmodule Ubuntu.Components 0.1
629+ \ingroup ubuntu
630+ \brief Application-specific settings.
631+
632+ Settings are an easy way for an application to keep values on one or more
633+ devices, with optional grouping. If desired, actions can be associated with
634+ individual settings to provide UI.
635+
636+ Example:
637+ \qml
638+ import QtQuick 2.0
639+ import Ubuntu.Components 0.1
640+
641+ MainView {
642+ applicationName: "example"
643+ Settings {
644+ group: "sound"
645+ Option {
646+ name: "vibrate"
647+ defaultValue: false
648+ }
649+ }
650+ Settings {
651+ group: "tablet"
652+ sync: false
653+ Option {
654+ name: "bigLayout"
655+ defaultValue: false
656+ }
657+ }
658+ }
659+ \endqml
660+*/
661+Item {
662+ id: settings
663+ /*!
664+ An optional group for these settings.
665+ */
666+ property string group: "default"
667+
668+ /*!
669+ \internal
670+ */
671+ property list<Option> couldntGetListToWorkWithoutAlias
672+
673+ /*!
674+ The list of options. Most of the time it's enough to simply declare
675+ each Option within the Settings.
676+ */
677+ default property alias options: settings.couldntGetListToWorkWithoutAlias
678+
679+ /*!
680+ \internal
681+ */
682+ Component.onCompleted: {
683+ for(var item in options)
684+ options[item].__internal.group = group;
685+ }
686+}
687
688=== modified file 'modules/Ubuntu/Components/Tab.qml'
689--- modules/Ubuntu/Components/Tab.qml 2013-11-07 07:26:01 +0000
690+++ modules/Ubuntu/Components/Tab.qml 2014-04-10 15:55:52 +0000
691@@ -105,12 +105,5 @@
692 Tab is destroyed upon removal.
693 */
694 property bool dynamic: false
695-
696- /*
697- This flag is used by the Tabs to determine whether the pre-declared Tab was removed
698- from the Tabs model or not. The flag guards adding back pre-declared tabs upon Tabs
699- component stack (children) change.
700- */
701- property bool removedFromTabs: false
702 }
703 }
704
705=== modified file 'modules/Ubuntu/Components/Tabs.qml'
706--- modules/Ubuntu/Components/Tabs.qml 2014-04-08 12:38:35 +0000
707+++ modules/Ubuntu/Components/Tabs.qml 2014-04-10 15:55:52 +0000
708@@ -15,6 +15,7 @@
709 */
710
711 import QtQuick 2.0
712+import "mathUtils.js" as MathUtils
713
714 /*!
715 \qmltype Tabs
716@@ -149,6 +150,110 @@
717 }
718 }
719 \endqml
720+
721+ \section2 Dynamic tabs
722+ So far all Tab elements were pre-declared, but there can be situations when
723+ tabs need to be added dynamically. There are two ways to solve this, depending
724+ on the output needed.
725+
726+ \section3 Using Repeaters
727+ A Repeater can be used to create the necessary tabs depending on a given model.
728+ In this way the number of tabs will be driven by the model itself.
729+ An example of such a dynamic tab:
730+ \qml
731+ // DynamicTab.qml
732+ import QtQuick 2.0
733+ import Ubuntu.Components 0.1
734+
735+ Tabs {
736+ property alias model: tabRepeater.model
737+ Repeater {
738+ id: tabRepeater
739+ model: 5
740+ Tab {
741+ title: "Tab #" + index
742+ page: Page {
743+ // [...]
744+ }
745+ }
746+ }
747+ }
748+ \endqml
749+ Note that in the example above the Tabs will be re-created each time the model
750+ changes. This will cause state losing of each Tab, which depending on the
751+ content type can be solved at some extent using StateSaver. Using a Loader
752+ or specifying the Tab instance/component in the model the state can be preserved,
753+ however may increase code complexity.
754+
755+ \section3 Dynamic tabs
756+ Tabs provides functions to add Tab elements dynamically on runtime, without
757+ destroying the state of the existing tabs. You can add, move and remove any
758+ kind of Tab element, pre-declared or dynamically created ones. When removing
759+ pre-declared tabs, those will all be held back and hidden by Tabs, and can be
760+ added back any time either to the same or to a different position.
761+
762+ \qml
763+ import QtQuick 2.0
764+ import Ubuntu.Components 0.1
765+
766+ MainView {
767+ width: units.gu(40)
768+ height: units.gu(71)
769+
770+ Component {
771+ id: dynamicTab
772+ Tab {
773+ page: Page {
774+ Label {
775+ text: title
776+ anchors.centerIn: parent
777+ }
778+ }
779+ }
780+ }
781+ Tabs {
782+ id: tabs
783+ Tab {
784+ title: "Main tab"
785+ page: Page {
786+ toolbar: ToolbarItems {
787+ ToolbarButton {
788+ text: "remove predeclared"
789+ onTriggered: tabs.removeTab(preDeclared.index)
790+ }
791+ ToolbarButton {
792+ text: "add new"
793+ onTriggered: tabs.addTab("New tab", dynamicTab)
794+ }
795+ ToolbarButton {
796+ text: "insert predeclared"
797+ onTriggered: tabs.insertTab("", 0)
798+ }
799+ }
800+ }
801+ }
802+ Tab {
803+ id: preDeclared
804+ title: "Pre-declared tab"
805+ page: Page {
806+ Label {
807+ text: "This is a predeclared tab at index #" + index
808+ anchors.centerIn: parent
809+ }
810+ }
811+ }
812+ }
813+ }
814+ \endqml
815+
816+ \section3 Using Repeater and functions together
817+ Repeaters re-create their delegates as many times the model changes. Tabs added
818+ or moved in between the tabs maintained by the Repeater, as well as reordered
819+ through the Tabs functions will be re-arranged once the Repeater's model changes.
820+ This should be taken into account when designing the application, and the use
821+ of Repeater and functions toghether should be avoided if possible, or at least
822+ Repeater should always add tabs to te tail of the tab stack, and no tab insertion
823+ happens in that area.
824 */
825 PageTreeNode {
826 id: tabs
827@@ -186,10 +291,14 @@
828 }
829
830 /*!
831- Children are placed in a separate item that has functionality to extract the Tab items.
832+ \deprecated
833+ Children are placed in a separate item that has functionality to extract
834+ the Tab items.
835+ Note: this property is deprecated. Tab components are directly parented
836+ to Tabs' data property.
837 \qmlproperty list<Item> tabChildren
838 */
839- default property alias tabChildren: tabStack.data
840+ property alias tabChildren: tabs.data
841
842 /*!
843 \qmlproperty int count
844@@ -206,36 +315,180 @@
845 signal modelChanged()
846
847 /*!
848+ Appends a Tab dynamically to the list of tabs. The \a title specifies the
849+ title of the Tab. The \a tab can be either a Component, a URL to a Tab
850+ component to be loaded or an instance of a pre-declared tab that has been
851+ previously removed. The Tab's title will be replaced with the given \a title,
852+ unless the value is an empty string or undefined.
853+ Returns the instance of the added Tab.
854+ */
855+ function addTab(title, tab) {
856+ return insertTab(count, title, tab);
857+ }
858+
859+ /*!
860+ Inserts a Tab at the given index. If the \a index is less or equal than 0,
861+ the Tab will be added to the front, and to the end of the tab stack in case
862+ the \a index is greater than \l count. \a title and \a tab are used in the
863+ same way as with \l addTab().
864+ Returns the instance of the inserted Tab.
865+ */
866+ function insertTab(index, title, tab) {
867+ // check if the given component is a Tab instance
868+ var tabObject = null;
869+
870+ if (typeof tab === "string") {
871+ // we have a URL
872+ var tabComponent = Qt.createComponent(tab);
873+ if (tabComponent.status === Component.Error) {
874+ console.error(tabComponent.errorString());
875+ return null;
876+ }
877+ tabObject = tabComponent.createObject();
878+ tabObject.__protected.dynamic = true;
879+ } else if (tab.hasOwnProperty("createObject")) {
880+ // we have a Component
881+ tabObject = tab.createObject();
882+ tabObject.__protected.dynamic = true;
883+ } else if (tab.hasOwnProperty("parent") && tab.parent === trashedTabs) {
884+ // we have a pre-declared tab that has been removed
885+ tabObject = tab;
886+ } else {
887+ console.error(i18n.tr("The object is not a URL, Component or a removed Tab: ") + tab);
888+ return null;
889+ }
890+
891+ // fix title
892+ if (title !== undefined && title !== "") {
893+ tabObject.title = title;
894+ }
895+
896+ // insert the created tab into the model
897+ index = MathUtils.clamp(index, 0, count);
898+ tabObject.__protected.inserted = true;
899+ tabObject.__protected.index = index;
900+ tabsModel.insertTab(tabObject, index);
901+ if (tabs.selectedTabIndex >= index) {
902+ // move the selected index to the next index
903+ tabs.selectedTabIndex += 1;
904+ } else {
905+ internal.sync();
906+ }
907+ return tabObject;
908+ }
909+
910+ /*!
911+ The function returns the Tab from the given \a index, or null if the \a index
912+ is invalid (less than \c 0 and greater than \l count).
913+ */
914+ function getTab(index) {
915+ return (index >=0) && (index < count) ? tabsModel.get(index).tab : null;
916+ }
917+
918+ /*!
919+ Moves the tab from the given \a from position to the position given in \a to.
920+ Returns true if the indexes were in 0..\l count - 1 boundary and if the operation
921+ succeeds, and false otherwise. The \l selectedTabIndex is updated if it is
922+ affected by the move (it is equal with \a from or falls between \a from and
923+ \a to indexes).
924+ */
925+ function moveTab(from, to) {
926+ if (from < 0 || from >= count || to < 0 || to >= count || from === to) return false;
927+ var tabFrom = tabsModel.get(from).tab;
928+ var tabTo = tabsModel.get(to).tab;
929+
930+ // move tab
931+ QuickUtils.moveItemBefore(tabFrom, tabTo);
932+ tabsModel.updateTabList(tabs.children);
933+
934+ // fix selected tab
935+ if (selectedTabIndex === from) {
936+ selectedTabIndex = to;
937+ } else if (selectedTabIndex >= Math.min(from, to) && selectedTabIndex <= Math.max(from, to)) {
938+ selectedTabIndex--;
939+ } else {
940+ internal.sync();
941+ }
942+
943+ return true;
944+ }
945+
946+ /*!
947+ Removes the Tab from the given \a index. Returns true if the \a index falls
948+ into 0..\l count - 1 boundary and the operation succeeds, and false on error.
949+ The function removes also the pre-declared tabs. These can be added back using
950+ \l addTab or \l insertTab by specifying the instance of the Tab to be added as
951+ component. The \l selectedTabIndex is updated if is affected by the removal
952+ (it is identical or greater than the tab index to be removed).
953+ */
954+ function removeTab(index) {
955+ if (index < 0 || index >= count) return false;
956+ var tab = tabsModel.get(index).tab;
957+ var activeIndex = (selectedTabIndex >= index) ? MathUtils.clamp(selectedTabIndex, 0, count - 2) : -1;
958+
959+ // remove from Tabs; Tabs children change will remove the tab from the model
960+ tab.parent = null;
961+ if (tab.__protected.dynamic) {
962+ tab.destroy();
963+ } else {
964+ // pre-declared tab, mark it as removed, so we don't update it next time
965+ // the tabs stack children is updated
966+ tab.parent = trashedTabs;
967+ }
968+
969+ // move active tab if needed
970+ if (activeIndex >= 0 && activeIndex !== selectedTabIndex) {
971+ selectedTabIndex = activeIndex;
972+ } else {
973+ internal.sync();
974+ }
975+
976+ return true;
977+ }
978+
979+ /*! \internal */
980+ onChildrenChanged: {
981+ internal.connectToRepeaters(tabs.children);
982+ tabsModel.updateTabList(tabs.children);
983+ }
984+
985+ /*!
986 \internal
987 required by TabsStyle
988 */
989 ListModel {
990 id: tabsModel
991
992+ property bool updateDisabled: false
993+
994 function listModel(tab) {
995 return {"title": tab.title, "tab": tab};
996 }
997
998 function updateTabList(tabsList) {
999+ if (updateDisabled) return;
1000 var offset = 0;
1001- var tabIndex;
1002+ var tabIndex = -1;
1003 for (var i in tabsList) {
1004 var tab = tabsList[i];
1005 if (internal.isTab(tab)) {
1006 tabIndex = i - offset;
1007 // make sure we have the right parent
1008- tab.parent = tabStack;
1009+ tab.parent = tabs;
1010
1011 if (!tab.__protected.inserted) {
1012 tab.__protected.index = tabIndex;
1013 tab.__protected.inserted = true;
1014 insert(tabIndex, listModel(tab));
1015- } else if (!tab.__protected.removedFromTabs && tabsModel.count > tab.index) {
1016+ } else {
1017 get(tab.index).title = tab.title;
1018 }
1019
1020 // always makes sure that tabsModel has the same order as tabsList
1021- move(tab.__protected.index, tabIndex, 1);
1022+ // but move only if there is more than one item in the list
1023+ if (count > 1) {
1024+ move(tab.__protected.index, tabIndex, 1);
1025+ }
1026 reindex();
1027 } else {
1028 // keep track of children that are not tabs so that we compute
1029@@ -243,6 +496,10 @@
1030 offset += 1;
1031 }
1032 }
1033+ // remove deleted tabs, those should be at the end of the list by now
1034+ if ((tabIndex >= 0) && (tabIndex + 1) < count) {
1035+ remove(tabIndex + 1, count - tabIndex - 1);
1036+ }
1037 internal.sync();
1038 }
1039
1040@@ -257,18 +514,31 @@
1041 tab.__protected.index = i;
1042 }
1043 }
1044+
1045+ function insertTab(tab, index) {
1046+ // fix index
1047+ if (index < 0) {
1048+ index = 0;
1049+ }
1050+ // get the tab before which the item will be inserted
1051+ var itemAtIndex = ((index >= 0) && (index < count)) ? get(index).tab : null;
1052+ // disable update only if we insert, append can keep the logic rolling
1053+ updateDisabled = (itemAtIndex !== null);
1054+ insert(index, listModel(tab));
1055+ tab.parent = tabs;
1056+ updateDisabled = false;
1057+ if (itemAtIndex) {
1058+ QuickUtils.moveItemBefore(tab, itemAtIndex);
1059+ updateTabList(tabs.children);
1060+ }
1061+ }
1062 }
1063
1064- // FIXME: this component is not really needed, as it doesn't really bring any
1065- // value; should be removed in a later MR
1066+ // invisible component stacking removed pre-declared components
1067 Item {
1068- anchors.fill: parent
1069- id: tabStack
1070-
1071- onChildrenChanged: {
1072- internal.connectToRepeaters(tabStack.children);
1073- tabsModel.updateTabList(tabStack.children);
1074- }
1075+ id: trashedTabs
1076+ visible: false
1077+ opacity: 0.0
1078 }
1079
1080 /*
1081@@ -283,7 +553,7 @@
1082 interval: 1
1083 running: false
1084 onTriggered: {
1085- tabsModel.updateTabList(tabStack.children);
1086+ tabsModel.updateTabList(tabs.children);
1087 internal.sync();
1088 }
1089 }
1090@@ -295,8 +565,8 @@
1091 Binding {
1092 target: tabBar
1093 property: "animate"
1094- when: internal.header && internal.header.hasOwnProperty("animate")
1095- value: internal.header.animate
1096+ when: (internal.header !== null) && internal.header.hasOwnProperty("animate")
1097+ value: internal.header ? internal.header.animate : "false"
1098 }
1099
1100 /*
1101@@ -332,7 +602,9 @@
1102 function connectToRepeaters(children) {
1103 for (var i = 0; i < children.length; i++) {
1104 var child = children[i];
1105- if (internal.isRepeater(child) && (internal.repeaters.indexOf(child) < 0)) {
1106+ if (internal.isRepeater(child) &&
1107+ (internal.repeaters !== undefined) &&
1108+ (internal.repeaters.indexOf(child) < 0)) {
1109 internal.connectRepeater(child);
1110 }
1111 }
1112@@ -355,7 +627,7 @@
1113 https://bugreports.qt-project.org/browse/QTBUG-32438
1114 */
1115 function updateTabsModel() {
1116- tabsModel.updateTabList(tabStack.children);
1117+ tabsModel.updateTabList(tabs.children);
1118 }
1119
1120 /*
1121
1122=== modified file 'modules/Ubuntu/Components/TextField.qml'
1123--- modules/Ubuntu/Components/TextField.qml 2014-03-17 17:58:47 +0000
1124+++ modules/Ubuntu/Components/TextField.qml 2014-04-10 15:55:52 +0000
1125@@ -1011,6 +1011,8 @@
1126 }
1127 // get the control's style
1128 clip: true
1129+ // If action is an Option we want to prefill the text from its value
1130+ text: action && action.hasOwnProperty("value") && typeof action.value != "undefined" ? action.value : ""
1131 onTextChanged: internal.textChanged = true
1132 cursorDelegate: cursor
1133 color: control.__styleInstance.color
1134
1135=== modified file 'modules/Ubuntu/Components/Themes/Ambiance/TabBarStyle.qml'
1136--- modules/Ubuntu/Components/Themes/Ambiance/TabBarStyle.qml 2014-04-08 12:38:35 +0000
1137+++ modules/Ubuntu/Components/Themes/Ambiance/TabBarStyle.qml 2014-04-10 15:55:52 +0000
1138@@ -116,8 +116,8 @@
1139 AbstractButton {
1140 id: button
1141 anchors {
1142- top: parent.top
1143- bottom: parent.bottom
1144+ top: parent ? parent.top : undefined
1145+ bottom: parent ? parent.bottom: undefined
1146 }
1147 width: text.paintedWidth + text.anchors.leftMargin + text.anchors.rightMargin
1148
1149@@ -150,6 +150,13 @@
1150 return false;
1151 }
1152
1153+ // update the offset of the buttonRow
1154+ onOffsetChanged: {
1155+ if (selected) {
1156+ buttonView.updateOffset(button.offset);
1157+ }
1158+ }
1159+
1160 Behavior on opacity {
1161 NumberAnimation {
1162 duration: headerTextFadeDuration
1163
1164=== modified file 'modules/Ubuntu/Components/plugin/adapters/alarmsadapter_organizer.cpp'
1165--- modules/Ubuntu/Components/plugin/adapters/alarmsadapter_organizer.cpp 2014-02-21 20:02:29 +0000
1166+++ modules/Ubuntu/Components/plugin/adapters/alarmsadapter_organizer.cpp 2014-04-10 15:55:52 +0000
1167@@ -21,6 +21,7 @@
1168 #include "alarmmanager_p.h"
1169 #include "alarmrequest_p.h"
1170 #include "alarmsadapter_p.h"
1171+#include <qorganizertodooccurrence.h>
1172
1173 #include <QtCore/QFile>
1174 #include <QtCore/QDir>
1175@@ -55,11 +56,15 @@
1176 , manager(0)
1177 , fetchRequest(0)
1178 {
1179- QOrganizerManager local;
1180- bool usingDefaultManager = local.availableManagers().contains(ALARM_MANAGER);
1181- manager = (usingDefaultManager) ? new QOrganizerManager(ALARM_MANAGER) : new QOrganizerManager(ALARM_MANAGER_FALLBACK);
1182+ QString envManager(qgetenv("ALARM_BACKEND"));
1183+ if (!envManager.isEmpty() && QOrganizerManager::availableManagers().contains(envManager)) {
1184+ manager = new QOrganizerManager(envManager);
1185+ } else {
1186+ manager = QOrganizerManager::availableManagers().contains(ALARM_MANAGER) ?
1187+ new QOrganizerManager(ALARM_MANAGER) : new QOrganizerManager(ALARM_MANAGER_FALLBACK);
1188+ }
1189 manager->setParent(q_ptr);
1190- if (!usingDefaultManager) {
1191+ if (manager->managerName() != ALARM_MANAGER) {
1192 qWarning() << "WARNING: default alarm manager not installed, using" << manager->managerName() << "manager.";
1193 qWarning() << "This manager may not provide all the needed features.";
1194 }
1195@@ -114,6 +119,7 @@
1196
1197 int type, days;
1198 in >> alarm.message >> alarm.date >> alarm.sound >> type >> days >> alarm.enabled;
1199+ alarm.originalDate = alarm.date;
1200 alarm.type = static_cast<UCAlarm::AlarmType>(type);
1201 alarm.days = static_cast<UCAlarm::DaysOfWeek>(days);
1202
1203@@ -142,7 +148,7 @@
1204
1205 Q_FOREACH(const AlarmData &alarm, alarmList) {
1206 out << alarm.message
1207- << alarm.date
1208+ << alarm.originalDate
1209 << alarm.sound
1210 << alarm.type
1211 << alarm.days
1212@@ -157,7 +163,6 @@
1213 event.setCollectionId(collection.id());
1214 event.setAllDay(false);
1215 event.setStartDateTime(alarm.date);
1216- event.setDueDateTime(alarm.date);
1217 event.setDisplayLabel(alarm.message);
1218
1219 if (alarm.enabled) {
1220@@ -224,8 +229,9 @@
1221
1222 alarm.cookie = QVariant::fromValue<QOrganizerItemId>(event.id());
1223 alarm.message = event.displayLabel();
1224- alarm.date = AlarmData::normalizeDate(event.dueDateTime());
1225+ alarm.date = AlarmData::normalizeDate(event.startDateTime());
1226 alarm.sound = QUrl(event.description());
1227+ alarm.originalDate = alarm.date;
1228
1229 // check if the alarm is enabled or not
1230 QOrganizerItemVisualReminder visual = event.detail(QOrganizerItemDetail::TypeVisualReminder);
1231@@ -303,8 +309,8 @@
1232 Q_FOREACH(const QOrganizerItem &item, alarms) {
1233 // repeating alarms may be fetched as occurences, therefore check their parent event
1234 if (item.type() == QOrganizerItemType::TypeTodoOccurrence) {
1235- QOrganizerTodoOccurrence occurence = static_cast<QOrganizerTodoOccurrence>(item);
1236- QOrganizerItemId eventId = occurence.parentId();
1237+ QOrganizerTodoOccurrence occurrence = static_cast<QOrganizerTodoOccurrence>(item);
1238+ QOrganizerItemId eventId = occurrence.parentId();
1239 if (parentId.contains(eventId)) {
1240 continue;
1241 }
1242@@ -317,6 +323,7 @@
1243 }
1244 AlarmData alarm;
1245 if (alarmDataFromOrganizerEvent(event, alarm) == UCAlarm::NoError) {
1246+ adjustAlarmOccurrence(event, alarm);
1247 alarmList << alarm;
1248 }
1249 }
1250@@ -324,9 +331,48 @@
1251 saveAlarms();
1252 Q_EMIT q_ptr->alarmsChanged();
1253 completed = true;
1254+ fetchRequest->deleteLater();
1255 fetchRequest = 0;
1256 }
1257
1258+void AlarmsAdapter::adjustAlarmOccurrence(const QOrganizerTodo &event, AlarmData &alarm)
1259+{
1260+ if (alarm.type == UCAlarm::OneTime) {
1261+ return;
1262+ }
1263+ // with EDS we need to query the occurrences separately as the fetch reports only the main events
1264+ // with fallback manager this does not reduce the performance and does work the same way.
1265+ QDateTime currentDate = AlarmData::normalizeDate(QDateTime::currentDateTime());
1266+ if (alarm.date > currentDate) {
1267+ // no need to adjust date, the event occurs in the future
1268+ return;
1269+ }
1270+ QDateTime startDate;
1271+ QDateTime endDate;
1272+ if (alarm.type == UCAlarm::Repeating) {
1273+ // 8 days is enough from the starting date (or current date depending on the start date)
1274+ startDate = (alarm.date > currentDate) ? alarm.date : currentDate;
1275+ endDate = startDate.addDays(8);
1276+ }
1277+
1278+ QList<QOrganizerItem> occurrences = manager->itemOccurrences(event, startDate, endDate, 10);
1279+ // get the first occurrence and use the date from it
1280+ if ((occurrences.length() > 0) && (occurrences[0].type() == QOrganizerItemType::TypeTodoOccurrence)) {
1281+ // loop till we get a proper future due date
1282+ for (int i = 0; i < occurrences.count(); i++) {
1283+ QOrganizerTodoOccurrence occurrence = static_cast<QOrganizerTodoOccurrence>(occurrences[i]);
1284+ // check if the date is after the current datetime
1285+ // the first occurrence is the one closest to the currentDate, therefore we can safely
1286+ // set that startDate to the alarm
1287+ alarm.date = occurrence.startDateTime();
1288+ if (alarm.date > currentDate) {
1289+ // we have the proper date set, leave
1290+ break;
1291+ }
1292+ }
1293+ }
1294+}
1295+
1296 /*-----------------------------------------------------------------------------
1297 * AlarmRequestAdapter implementation
1298 */
1299@@ -410,23 +456,10 @@
1300 QOrganizerItemFetchRequest *operation = new QOrganizerItemFetchRequest(q_ptr);
1301 operation->setManager(owner->manager);
1302
1303- // FIXME: Since returning all events without a limit of date is not a good solution we need to find
1304- // a better solution for that.
1305- // The current solution filters only the next 7 days (one week).
1306- // This will be enough for now, since the current alarms occur weekly, but for the future
1307- // we want to allow create alarms with monthly or yearly recurrence
1308- QDate currentDate = QDate::currentDate();
1309- QDateTime startDate(currentDate,
1310- QTime(0,0,0));
1311- QDateTime endDate(currentDate.addDays(7),
1312- QTime(23,59,59));
1313- operation->setStartDate(startDate);
1314- operation->setEndDate(endDate);
1315-
1316 // set sort order
1317 QOrganizerItemSortOrder sortOrder;
1318 sortOrder.setDirection(Qt::AscendingOrder);
1319- sortOrder.setDetail(QOrganizerItemDetail::TypeTodoTime, QOrganizerTodoTime::FieldDueDateTime);
1320+ sortOrder.setDetail(QOrganizerItemDetail::TypeTodoTime, QOrganizerTodoTime::FieldStartDateTime);
1321 operation->setSorting(QList<QOrganizerItemSortOrder>() << sortOrder);
1322
1323 // set filter
1324
1325=== modified file 'modules/Ubuntu/Components/plugin/adapters/alarmsadapter_p.h'
1326--- modules/Ubuntu/Components/plugin/adapters/alarmsadapter_p.h 2013-09-18 10:13:04 +0000
1327+++ modules/Ubuntu/Components/plugin/adapters/alarmsadapter_p.h 2014-04-10 15:55:52 +0000
1328@@ -24,6 +24,7 @@
1329
1330 #include <qorganizer.h>
1331 #include <qorganizermanager.h>
1332+#include <qorganizertodo.h>
1333
1334 QTORGANIZER_USE_NAMESPACE
1335
1336@@ -78,6 +79,7 @@
1337 QOrganizerCollection collection;
1338
1339 void completeFetchAlarms(const QList<QOrganizerItem> &alarmList);
1340+ void adjustAlarmOccurrence(const QOrganizerTodo &event, AlarmData &alarm);
1341
1342 void loadAlarms();
1343 void saveAlarms();
1344
1345=== modified file 'modules/Ubuntu/Components/plugin/alarmmanager_p.h'
1346--- modules/Ubuntu/Components/plugin/alarmmanager_p.h 2013-09-12 05:27:40 +0000
1347+++ modules/Ubuntu/Components/plugin/alarmmanager_p.h 2014-04-10 15:55:52 +0000
1348@@ -48,6 +48,7 @@
1349 AlarmData(const AlarmData &other)
1350 : changes(0)
1351 , cookie(other.cookie)
1352+ , originalDate(other.originalDate)
1353 , date(other.date)
1354 , message(other.message)
1355 , type(other.type)
1356@@ -104,6 +105,7 @@
1357 QVariant cookie;
1358
1359 // data members
1360+ QDateTime originalDate;
1361 QDateTime date;
1362 QString message;
1363 QUrl sound;
1364
1365=== modified file 'modules/Ubuntu/Components/plugin/plugin.cpp'
1366--- modules/Ubuntu/Components/plugin/plugin.cpp 2014-03-05 12:29:58 +0000
1367+++ modules/Ubuntu/Components/plugin/plugin.cpp 2014-04-10 15:55:52 +0000
1368@@ -53,9 +53,6 @@
1369 #include <unistd.h>
1370 #include <stdexcept>
1371
1372-// Needed for unit tests
1373-Q_DECLARE_METATYPE(QList<QQmlError>)
1374-
1375 /*
1376 * Type registration functions.
1377 */
1378@@ -177,8 +174,6 @@
1379 qmlRegisterSingletonType<UCUriHandler>(uri, 0, 1, "UriHandler", registerUriHandler);
1380 qmlRegisterType<UCMouse>(uri, 0, 1, "Mouse");
1381 qmlRegisterType<UCInverseMouse>(uri, 0, 1, "InverseMouse");
1382- // Needed for unit tests
1383- qRegisterMetaType<QList <QQmlError> >();
1384 // register QML singletons
1385 qmlRegisterSingletonType<QObject>(uri, 0, 1, "PickerPanel", registerPickerPanel);
1386 }
1387
1388=== modified file 'modules/Ubuntu/Components/plugin/quickutils.cpp'
1389--- modules/Ubuntu/Components/plugin/quickutils.cpp 2014-03-20 15:46:28 +0000
1390+++ modules/Ubuntu/Components/plugin/quickutils.cpp 2014-04-10 15:55:52 +0000
1391@@ -119,6 +119,21 @@
1392 return result.left(result.indexOf("_QML"));
1393 }
1394
1395+/*!
1396+ * \internal
1397+ * Moves a given \a item before the \a other one in the object stack. Both \a item
1398+ * and \a other must have the same parent item.
1399+ */
1400+void QuickUtils::moveItemBefore(QQuickItem *item, QQuickItem *other)
1401+{
1402+ Q_ASSERT(item);
1403+ Q_ASSERT(item->parentItem());
1404+ if (other) {
1405+ Q_ASSERT(other->parentItem() == item->parentItem());
1406+ item->stackBefore(other);
1407+ }
1408+}
1409+
1410
1411 /*!
1412 * \internal
1413
1414=== modified file 'modules/Ubuntu/Components/plugin/quickutils.h'
1415--- modules/Ubuntu/Components/plugin/quickutils.h 2014-03-20 15:46:28 +0000
1416+++ modules/Ubuntu/Components/plugin/quickutils.h 2014-04-10 15:55:52 +0000
1417@@ -42,6 +42,7 @@
1418 QString inputMethodProvider() const;
1419
1420 Q_INVOKABLE static QString className(QObject *item);
1421+ Q_INVOKABLE void moveItemBefore(QQuickItem *item, QQuickItem *before);
1422 QObject* createQmlObject(const QUrl &url, QQmlEngine *engine);
1423
1424 Q_SIGNALS:
1425
1426=== modified file 'modules/Ubuntu/Components/plugin/ucalarm.cpp'
1427--- modules/Ubuntu/Components/plugin/ucalarm.cpp 2013-09-18 10:25:18 +0000
1428+++ modules/Ubuntu/Components/plugin/ucalarm.cpp 2014-04-10 15:55:52 +0000
1429@@ -102,15 +102,21 @@
1430
1431 int UCAlarmPrivate::nextDayOfWeek(UCAlarm::DaysOfWeek days, int fromDay)
1432 {
1433- if (fromDay <= 0) {
1434+ if (fromDay <= 0 || fromDay >= Qt::Sunday) {
1435+ // start from the beginning of the week
1436 fromDay = Qt::Monday;
1437+ } else {
1438+ // start checking from the next day onwards
1439+ fromDay++;
1440 }
1441 for (int d = fromDay; d <= Qt::Sunday; d++) {
1442 if ((1 << (d - 1)) & days) {
1443 return d;
1444 }
1445 }
1446- return 0;
1447+
1448+ // none found for the rest of the week, return the fist day set
1449+ return firstDayOfWeek(days);
1450 }
1451
1452 // checks whether the given num has more than one bit set
1453@@ -147,23 +153,14 @@
1454 return UCAlarm::NoError;
1455 }
1456
1457-UCAlarm::Error UCAlarmPrivate::checkDow()
1458+// adjust dayOfWeek
1459+UCAlarm::Error UCAlarmPrivate::adjustDow()
1460 {
1461 if (!rawData.days) {
1462 return UCAlarm::NoDaysOfWeek;
1463 } else if (rawData.days == UCAlarm::AutoDetect) {
1464 rawData.days = dayOfWeek(rawData.date);
1465 rawData.changes |= AlarmData::Days;
1466- } else if (rawData.days != UCAlarm::Daily) {
1467- int alarmDay = firstDayOfWeek(rawData.days);
1468- int dayOfWeek = rawData.date.date().dayOfWeek();
1469- if (alarmDay < dayOfWeek) {
1470- rawData.date = rawData.date.addDays(7 - dayOfWeek + alarmDay);
1471- rawData.changes |= AlarmData::Date;
1472- } else if (alarmDay > dayOfWeek) {
1473- rawData.date = rawData.date.addDays(alarmDay - dayOfWeek);
1474- rawData.changes |= AlarmData::Date;
1475- }
1476 }
1477 return UCAlarm::NoError;
1478 }
1479@@ -175,8 +172,7 @@
1480 return UCAlarm::OneTimeOnMoreDays;
1481 }
1482
1483- // adjust start date and/or dayOfWeek according to their values
1484- UCAlarm::Error result = checkDow();
1485+ UCAlarm::Error result = adjustDow();
1486 if (result != UCAlarm::NoError) {
1487 return result;
1488 }
1489@@ -193,23 +189,25 @@
1490 // start date is adjusted depending on the days value;
1491 // start date can be set to be the current time, as scheduling will move
1492 // it to the first occurence.
1493- UCAlarm::Error result = checkDow();
1494+ UCAlarm::Error result = adjustDow();
1495 if (result != UCAlarm::NoError) {
1496 return result;
1497 }
1498
1499- // move start time to the first occurence if needed
1500+ // move start time of the first occurence if needed
1501 int dayOfWeek = rawData.date.date().dayOfWeek();
1502 if (!isDaySet(dayOfWeek, rawData.days) || (rawData.date <= QDateTime::currentDateTime())) {
1503 // check the next occurence of the alarm
1504- int nextOccurence = nextDayOfWeek(rawData.days, dayOfWeek);
1505- if (nextOccurence <= 0) {
1506- // the starting date should be moved to the next week
1507- nextOccurence = firstDayOfWeek(rawData.days);
1508- rawData.date.addDays(7 - dayOfWeek + nextOccurence);
1509+ int nextOccurrence = nextDayOfWeek(rawData.days, dayOfWeek);
1510+ if (nextOccurrence == dayOfWeek) {
1511+ // move the date to the same day next week
1512+ rawData.date = rawData.date.addDays(7);
1513+ } else if (nextOccurrence < dayOfWeek) {
1514+ // the starting date should be moved to the next week's occurrence
1515+ rawData.date = rawData.date.addDays(7 - dayOfWeek + nextOccurrence);
1516 } else {
1517 // the starting date is still this week
1518- rawData.date.addDays(nextOccurence - dayOfWeek);
1519+ rawData.date = rawData.date.addDays(nextOccurrence - dayOfWeek);
1520 }
1521 rawData.changes |= AlarmData::Date;
1522 }
1523@@ -304,7 +302,7 @@
1524 : QObject(parent)
1525 , d_ptr(new UCAlarmPrivate(this))
1526 {
1527- d_ptr->rawData.date = dt;
1528+ d_ptr->rawData.date = AlarmData::normalizeDate(dt);
1529 if (!message.isEmpty()) {
1530 d_ptr->rawData.message = message;
1531 }
1532@@ -315,7 +313,7 @@
1533 : QObject(parent)
1534 , d_ptr(new UCAlarmPrivate(this))
1535 {
1536- d_ptr->rawData.date = dt;
1537+ d_ptr->rawData.date = AlarmData::normalizeDate(dt);
1538 d_ptr->rawData.type = Repeating;
1539 d_ptr->rawData.days = days;
1540 if (!message.isEmpty()) {
1541@@ -366,12 +364,16 @@
1542 void UCAlarm::setDate(const QDateTime &date)
1543 {
1544 Q_D(UCAlarm);
1545- if (d->rawData.date == date) {
1546+ if (d->rawData.date == AlarmData::normalizeDate(date)) {
1547 return;
1548 }
1549- d->rawData.date = date;
1550+ d->rawData.date = AlarmData::normalizeDate(date);
1551 d->rawData.changes |= AlarmData::Date;
1552 Q_EMIT dateChanged();
1553+ if (d->rawData.type == UCAlarm::OneTime) {
1554+ // adjust dayOfWeek as well
1555+ setDaysOfWeek(UCAlarm::AutoDetect);
1556+ }
1557 }
1558
1559 /*!
1560@@ -520,6 +522,8 @@
1561 Q_EMIT soundChanged();
1562 }
1563
1564+
1565+
1566 /*!
1567 * \qmlproperty Error Alarm::error
1568 * The property holds the error code occurred during the last performed operation.
1569@@ -670,6 +674,8 @@
1570 if (result != UCAlarm::NoError) {
1571 d->_q_syncStatus(Saving, Fail, result);
1572 } else {
1573+ // the alarm has been modified, therefore update the original date as well
1574+ d->rawData.originalDate = d->rawData.date;
1575 if (d->createRequest()) {
1576 d->request->save(d->rawData);
1577 }
1578
1579=== modified file 'modules/Ubuntu/Components/plugin/ucalarm_p.h'
1580--- modules/Ubuntu/Components/plugin/ucalarm_p.h 2013-09-18 10:25:18 +0000
1581+++ modules/Ubuntu/Components/plugin/ucalarm_p.h 2014-04-10 15:55:52 +0000
1582@@ -50,7 +50,7 @@
1583 static int nextDayOfWeek(UCAlarm::DaysOfWeek days, int fromDay);
1584 static bool multipleDaysSet(UCAlarm::DaysOfWeek days);
1585 UCAlarm::Error checkAlarm();
1586- UCAlarm::Error checkDow();
1587+ UCAlarm::Error adjustDow();
1588 UCAlarm::Error checkOneTime();
1589 UCAlarm::Error checkRepeatingWeekly();
1590
1591
1592=== modified file 'modules/Ubuntu/Components/qmldir'
1593--- modules/Ubuntu/Components/qmldir 2014-02-13 11:48:38 +0000
1594+++ modules/Ubuntu/Components/qmldir 2014-04-10 15:55:52 +0000
1595@@ -7,6 +7,8 @@
1596 ToolbarButton 0.1 ToolbarButton.qml
1597 MainView 0.1 MainView.qml
1598 Button 0.1 Button.qml
1599+Settings 0.1 Settings.qml
1600+Option 0.1 Option.qml
1601 Panel 0.1 Panel.qml
1602 internal DraggingArea DraggingArea.qml
1603 Tab 0.1 Tab.qml
1604
1605=== modified file 'modules/Ubuntu/Test/UbuntuTestCase.qml'
1606--- modules/Ubuntu/Test/UbuntuTestCase.qml 2014-02-25 12:36:27 +0000
1607+++ modules/Ubuntu/Test/UbuntuTestCase.qml 2014-04-10 15:55:52 +0000
1608@@ -87,7 +87,75 @@
1609 }
1610 }
1611
1612- /*!
1613+ /*!
1614+ \qmlmethod UbuntuTestCase::flick(item, x, y, dx, dy, pressTimeout = -1, steps = -1, button = Qt.LeftButton, modifiers = Qt.NoModifiers, delay = -1)
1615+
1616+ The function produces a flick event when executed on Flickables. When used
1617+ on other components it provides the same functionality as \l mouseDrag()
1618+ function. The optional \a pressTimeout parameter can be used to introduce
1619+ a small delay between the mouse press and the first mouse move. Setting a
1620+ negative or zero value will disable the timeout.
1621+
1622+ The default flick velocity is built up using 5 move points. This can be altered
1623+ by setting a positive value to \a steps parameter. The bigger the number the
1624+ longer the flick will be. When a negative or zero value is given, the default
1625+ of 5 move points will be used.
1626+
1627+ \note The function can be used to select a text in a TextField or TextArea by
1628+ specifying at least 400 millisecods to \a pressTimeout.
1629+ */
1630+ function flick(item, x, y, dx, dy, pressTimeout, steps, button, modifiers, delay) {
1631+ if (item === undefined || item.x === undefined || item.y === undefined)
1632+ return
1633+ if (button === undefined)
1634+ button = Qt.LeftButton
1635+ if (modifiers === undefined)
1636+ modifiers = Qt.NoModifier
1637+ if (steps === undefined || steps <= 0)
1638+ steps = 4;
1639+ // make sure we have at least two move steps so the flick will be sensed
1640+ steps += 1;
1641+ if (delay === undefined)
1642+ delay = -1;
1643+
1644+ var ddx = dx / steps;
1645+ var ddy = dy / steps;
1646+
1647+ mousePress(item, x, y, button, modifiers, delay);
1648+ if (pressTimeout !== undefined && pressTimeout > 0) {
1649+ wait(pressTimeout);
1650+ }
1651+ for (var i = 1; i <= steps; i++) {
1652+ // mouse moves are all processed immediately, without delay in between events
1653+ mouseMove(item, x + i * ddx, y + i * ddy, -1, button);
1654+ }
1655+ mouseRelease(item, x + dx, y + dy, button, modifiers, delay);
1656+ // empty event buffer
1657+ wait(200);
1658+ }
1659+
1660+ /*!
1661+ \qmlmethod UbuntuTestCase::mouseLongPress(item, x, y, button = Qt.LeftButton, modifiers = Qt.NoModifiers, delay = -1)
1662+
1663+ Simulates a long press on a mouse \a button with an optional \a modifier
1664+ on an \a item. The position is defined by \a x and \a y. If \a delay is
1665+ specified, the test will wait the specified amount of milliseconds before
1666+ the press.
1667+
1668+ The position given by \a x and \a y is transformed from the co-ordinate
1669+ system of \a item into window co-ordinates and then delivered.
1670+ If \a item is obscured by another item, or a child of \a item occupies
1671+ that position, then the event will be delivered to the other item instead.
1672+
1673+ \sa mouseRelease(), mouseClick(), mouseDoubleClick(), mouseMove(), mouseDrag(), mouseWheel()
1674+ */
1675+ function mouseLongPress(item, x, y, button, modifiers, delay) {
1676+ mousePress(item, x, y, button, modifiers, delay);
1677+ // the delay is taken from QQuickMouseArea
1678+ wait(800);
1679+ }
1680+
1681+ /*!
1682 Keeps executing a given parameter-less function until it returns the given
1683 expected result or the timemout is reached (in which case a test failure
1684 is generated)
1685
1686=== modified file 'modules/Ubuntu/Test/deployment.pri'
1687--- modules/Ubuntu/Test/deployment.pri 2014-01-17 12:30:05 +0000
1688+++ modules/Ubuntu/Test/deployment.pri 2014-04-10 15:55:52 +0000
1689@@ -7,9 +7,14 @@
1690 # make found deployables visible in Qt Creator
1691 OTHER_FILES += $$QMLDIR_FILE
1692
1693+QML_FILES = $$system(ls *.qml)
1694+JS_FILES = $$system(ls *.js)
1695+
1696 # define deployment for found deployables
1697 qmldir_file.path = $$installPath
1698 qmldir_file.files = $$QMLDIR_FILE
1699+qml_files.path = $$installPath
1700+qml_files.files = $$QML_FILES
1701 js_files.path = $$installPath
1702 js_files.files = $$JS_FILES
1703
1704@@ -20,4 +25,4 @@
1705 # https://bugreports.qt-project.org/browse/QTBUG-36243
1706 plugins_qmltypes.extra = $$[QT_INSTALL_BINS]/qmlplugindump -notrelocatable Ubuntu.Test 0.1 ../../ 2>/dev/null > $(INSTALL_ROOT)/$$installPath/plugins.qmltypes
1707
1708-INSTALLS += qmldir_file plugins_qmltypes
1709+INSTALLS += qmldir_file plugins_qmltypes qml_files js_files
1710
1711=== modified file 'modules/Ubuntu/Test/plugin/uctestcase.cpp'
1712--- modules/Ubuntu/Test/plugin/uctestcase.cpp 2014-03-19 12:48:33 +0000
1713+++ modules/Ubuntu/Test/plugin/uctestcase.cpp 2014-04-10 15:55:52 +0000
1714@@ -26,6 +26,8 @@
1715 #include <QtTest/QtTest>
1716 #include <QtQuick/QQuickItem>
1717
1718+Q_DECLARE_METATYPE(QList<QQmlError>)
1719+
1720 /*!
1721 * \ingroup ubuntu
1722 * \brief UbuntuTestCase is the C++ pendant to the QML UbuntuTestCase.
1723@@ -37,6 +39,7 @@
1724 QString modulePath(QDir(modules).absolutePath());
1725 engine()->addImportPath(modulePath);
1726
1727+ qRegisterMetaType<QList <QQmlError> >();
1728 m_spy = new QSignalSpy(engine(), SIGNAL(warnings(QList<QQmlError>)));
1729 m_spy->setParent(this);
1730
1731@@ -48,3 +51,12 @@
1732 QTest::qWaitForWindowExposed(this);
1733 }
1734
1735+/*!
1736+ * The number of all warnings from the point of loading the first line of QML code.
1737+ */
1738+int
1739+UbuntuTestCase::warnings() const
1740+{
1741+ return m_spy->count();
1742+}
1743+
1744
1745=== modified file 'modules/Ubuntu/Test/plugin/uctestcase.h'
1746--- modules/Ubuntu/Test/plugin/uctestcase.h 2014-03-19 12:48:33 +0000
1747+++ modules/Ubuntu/Test/plugin/uctestcase.h 2014-04-10 15:55:52 +0000
1748@@ -29,6 +29,7 @@
1749
1750 public:
1751 UbuntuTestCase(const QString& file, QWindow* parent = 0);
1752+ int warnings() const;
1753 // getter
1754 template<class T>
1755 inline T findItem(const QString& objectName) const {
1756@@ -39,7 +40,6 @@
1757 qFatal("Item '%s' found with unexpected type", qPrintable(objectName));
1758 qFatal("No item '%s' found", qPrintable(objectName));
1759 }
1760-
1761 private:
1762 QSignalSpy* m_spy;
1763 };
1764
1765=== modified file 'tests/autopilot/ubuntuuitoolkit/emulators.py'
1766--- tests/autopilot/ubuntuuitoolkit/emulators.py 2014-04-08 14:46:25 +0000
1767+++ tests/autopilot/ubuntuuitoolkit/emulators.py 2014-04-10 15:55:52 +0000
1768@@ -14,6 +14,8 @@
1769 # You should have received a copy of the GNU Lesser General Public License
1770 # along with this program. If not, see <http://www.gnu.org/licenses/>.
1771
1772+import os
1773+
1774 import logging
1775 from distutils import version
1776
1777@@ -349,6 +351,27 @@
1778 return len(self._get_tabs())
1779
1780
1781+class Settings(UbuntuUIToolkitEmulatorBase):
1782+ """Settings Autopilot emulator."""
1783+
1784+ @classmethod
1785+ @autopilot_logging.log_action(logger.info)
1786+ def clear(cls, application_name):
1787+ database_file = cls._get_database_filename(application_name)
1788+ if os.path.exists(database_file):
1789+ os.remove(database_file)
1790+
1791+ @classmethod
1792+ def _get_database_filename(self, application_name):
1793+ data_home = os.environ.get(
1794+ 'XDG_DATA_HOME',
1795+ os.path.join(os.environ['HOME'], '.local', 'share'))
1796+ return os.path.join(data_home, application_name, 'settings.db')
1797+
1798+ def get_option(self, optionName):
1799+ return self.select_single('Option', name=optionName)
1800+
1801+
1802 class TabBar(UbuntuUIToolkitEmulatorBase):
1803 """TabBar Autopilot emulator."""
1804
1805
1806=== modified file 'tests/autopilot/ubuntuuitoolkit/tests/test_emulators.py'
1807--- tests/autopilot/ubuntuuitoolkit/tests/test_emulators.py 2014-03-22 06:53:24 +0000
1808+++ tests/autopilot/ubuntuuitoolkit/tests/test_emulators.py 2014-04-10 15:55:52 +0000
1809@@ -14,6 +14,8 @@
1810 # You should have received a copy of the GNU Lesser General Public License
1811 # along with this program. If not, see <http://www.gnu.org/licenses/>.
1812
1813+import os
1814+import signal
1815 import time
1816 import unittest
1817
1818@@ -21,6 +23,8 @@
1819 from autopilot import input, platform
1820 from autopilot.introspection import dbus
1821 from testtools.matchers import GreaterThan, LessThan
1822+from autopilot.matchers import Eventually
1823+from testtools.matchers import Equals
1824 try:
1825 from unittest import mock
1826 except ImportError:
1827@@ -120,6 +124,199 @@
1828 self.assertEqual(
1829 str(error), 'The MainView has no Tabs.')
1830
1831+defaultGroup = ("""
1832+import QtQuick 2.0
1833+import Ubuntu.Components 0.1
1834+
1835+MainView {
1836+ width: units.gu(48)
1837+ height: units.gu(60)
1838+ applicationName: 'once.upon.a.time'
1839+
1840+ Settings {
1841+ objectName: 'settings'
1842+ Option {
1843+ id: optionVibrate
1844+ name: "vibrate"
1845+ defaultValue: false
1846+ }
1847+ Option {
1848+ id: optionHomepage
1849+ name: "homepage"
1850+ defaultValue: "http://www.canonical.com"
1851+ }
1852+ Option {
1853+ id: optionHair
1854+ name: "hairColor"
1855+ defaultValue: 0
1856+ }
1857+ }
1858+
1859+ Column {
1860+ Switch {
1861+ action: optionVibrate
1862+ objectName: "vibrateSwitch"
1863+ property bool val: action.value
1864+ }
1865+ TextField {
1866+ action: optionHomepage
1867+ objectName: "homepageEntry"
1868+ property string val: action.value
1869+ }
1870+ OptionSelector {
1871+ action: optionHair
1872+ objectName: "hairColorSelector"
1873+ model: [ "Black", "Ginger", "Peroxided", "White" ]
1874+ property int val: action.value
1875+ expanded: true
1876+ }
1877+ }
1878+}
1879+""")
1880+
1881+multipleGroups = ("""
1882+import QtQuick 2.0
1883+import Ubuntu.Components 0.1
1884+
1885+MainView {
1886+ width: units.gu(48)
1887+ height: units.gu(60)
1888+ applicationName: 'once.upon.a.time'
1889+
1890+ Settings {
1891+ objectName: 'settings'
1892+ group: "general"
1893+ Option {
1894+ id: optionVibrate
1895+ name: "vibrate"
1896+ defaultValue: false
1897+ }
1898+ }
1899+ Settings {
1900+ group: "MiscellaneousOptions"
1901+ Option {
1902+ id: optionHomepage
1903+ name: "homepage"
1904+ defaultValue: "http://www.canonical.com"
1905+ }
1906+ }
1907+ Settings {
1908+ group: "userProfile"
1909+ Option {
1910+ id: optionHair
1911+ name: "hairColor"
1912+ defaultValue: 0
1913+ }
1914+ }
1915+
1916+ Column {
1917+ Switch {
1918+ action: optionVibrate
1919+ objectName: "vibrateSwitch"
1920+ property bool val: action.value
1921+ }
1922+ TextField {
1923+ action: optionHomepage
1924+ objectName: "homepageEntry"
1925+ property string val: action.value
1926+ }
1927+ OptionSelector {
1928+ action: optionHair
1929+ objectName: "hairColorSelector"
1930+ model: [ "Black", "Ginger", "Peroxided", "White" ]
1931+ property int val: action.value
1932+ expanded: true
1933+ }
1934+ }
1935+}
1936+""")
1937+
1938+
1939+class SettingsTestCase(tests.QMLStringAppTestCase):
1940+
1941+ scenarios = [
1942+ ('defaultGroup', dict(test_qml=defaultGroup)),
1943+ ('multipleGroups', dict(test_qml=multipleGroups)),
1944+ ]
1945+
1946+ def setUp(self):
1947+ # Make sure we start with the default settings.
1948+ emulators.Settings.clear('once.upon.a.time')
1949+ super(SettingsTestCase, self).setUp()
1950+
1951+ def get_settings(self):
1952+ return self.main_view.select_single(
1953+ emulators.Settings, objectName='settings')
1954+
1955+ def get_switch(self):
1956+ return self.main_view.select_single(objectName='vibrateSwitch')
1957+
1958+ def get_entry(self):
1959+ return self.main_view.select_single(objectName='homepageEntry')
1960+
1961+ def get_selector(self):
1962+ return self.main_view.select_single(objectName='hairColorSelector')
1963+
1964+ def test_select_settings_must_return_custom_proxy_object(self):
1965+ self.assertIsInstance(self.get_settings(), emulators.Settings)
1966+
1967+ def test_application_must_start_with_default_values(self):
1968+ # FIXME: bug #1273956
1969+ #self.assertEqual(self.settings.get_option('vibrate').value, False)
1970+ switch = self.get_switch()
1971+
1972+ self.assertThat(switch.checked, Eventually(Equals(False)))
1973+ self.assertThat(switch.val, Equals(switch.checked))
1974+
1975+ def test_check_switch_must_update_option_value(self):
1976+ switch = self.get_switch()
1977+ switch.check()
1978+
1979+ self.assertThat(switch.checked, Eventually(Equals(True)))
1980+ self.assertThat(switch.val, Equals(switch.checked))
1981+
1982+ def test_updated_values_must_be_kept_after_app_restart(self):
1983+ switch = self.get_switch()
1984+ switch.check()
1985+ self.assertThat(switch.checked, Eventually(Equals(True)))
1986+ self.assertThat(switch.val, Equals(switch.checked))
1987+
1988+ domain = 'http://www.ubuntu.com'
1989+ entry = self.get_entry()
1990+ entry.clear()
1991+ entry.write(domain)
1992+ self.keyboard.press_and_release('Return')
1993+ self.assertThat(entry.text, Eventually(Equals(domain)))
1994+ self.assertThat(entry.text, Equals(entry.val))
1995+
1996+ selector = self.get_selector()
1997+ option = selector.select_single('Label', text='Ginger')
1998+ self.pointing_device.click_object(selector)
1999+ self.pointing_device.click_object(option)
2000+ self.assertThat(selector.selectedIndex, Eventually(Equals(1)))
2001+ self.assertThat(selector.selectedIndex, Equals(selector.val))
2002+
2003+ db_file = emulators.Settings._get_database_filename('once.upon.a.time')
2004+ assert(os.path.exists(db_file))
2005+
2006+ # TODO update this once the restart helpers are implemented in
2007+ # autopilot. See http://pad.lv/1302618 --elopio - 2014-04-04
2008+ os.killpg(self.app.pid, signal.SIGTERM)
2009+ self.launch_application()
2010+
2011+ switch = self.get_switch()
2012+ self.assertThat(switch.checked, Eventually(Equals(True)))
2013+ self.assertThat(switch.val, Equals(switch.checked))
2014+ entry = self.get_entry()
2015+ self.assertThat(entry.text, Eventually(Equals(domain)))
2016+ self.assertThat(entry.text, Equals(entry.val))
2017+ selector = self.get_selector()
2018+ index = selector.selectedIndex
2019+ list = selector.select_single('QQuickListView')
2020+ self.assertThat(list.currentIndex, Eventually(Equals(index)))
2021+ self.assertThat(index, Eventually(Equals(1)))
2022+ self.assertThat(index, Equals(selector.val))
2023+
2024
2025 class PageTestCase(tests.QMLStringAppTestCase):
2026
2027
2028=== modified file 'tests/resources/alarm/Alarms.qml'
2029--- tests/resources/alarm/Alarms.qml 2013-09-18 10:13:04 +0000
2030+++ tests/resources/alarm/Alarms.qml 2014-04-10 15:55:52 +0000
2031@@ -21,7 +21,7 @@
2032
2033 MainView {
2034 id: mainView
2035- width: units.gu(40)
2036+ width: units.gu(80)
2037 height: units.gu(71)
2038 objectName: "mainView"
2039
2040@@ -162,7 +162,11 @@
2041 clip: true
2042 model: alarmModel
2043 delegate: Standard {
2044- text: message
2045+ text: message + recurring(model) + "\n" + model.date
2046+ function recurring(alarmData) {
2047+ return (alarmData.type === Alarm.Repeating) ? "[Repeating]" : "[Onetime]";
2048+ }
2049+
2050 removable: true
2051 control: Switch {
2052 checked: model.enabled
2053
2054=== modified file 'tests/resources/navigation/Tabs.qml'
2055--- tests/resources/navigation/Tabs.qml 2014-04-07 10:03:39 +0000
2056+++ tests/resources/navigation/Tabs.qml 2014-04-10 15:55:52 +0000
2057@@ -19,9 +19,47 @@
2058 import Ubuntu.Components.ListItems 0.1 as ListItem
2059
2060 MainView {
2061+ id: root
2062 width: 800
2063 height: 600
2064
2065+ property var repeaterModel: 3
2066+
2067+ Component {
2068+ id: dynamicTab
2069+ Tab {
2070+ page: Page {
2071+ Label {
2072+ text: title + " at index " + index
2073+ anchors.centerIn: parent
2074+ }
2075+ tools: ToolbarItems {
2076+ ToolbarButton {
2077+ text: "move @1"
2078+ onTriggered: {
2079+ print("MOVE TAB TO #1")
2080+ tabs.moveTab(index, 1)
2081+ }
2082+ }
2083+ ToolbarButton {
2084+ text: "remove me"
2085+ onTriggered: {
2086+ print("REMOVE CURENT TAB")
2087+ tabs.removeTab(index)
2088+ }
2089+ }
2090+ ToolbarButton {
2091+ text: "remove first"
2092+ onTriggered: {
2093+ print("REMOVE TAB AT #0")
2094+ tabs.removeTab(0)
2095+ }
2096+ }
2097+ }
2098+ }
2099+ }
2100+ }
2101+
2102 Tabs {
2103 id: tabs
2104 selectedTabIndex: 0
2105@@ -31,6 +69,7 @@
2106
2107 Tab {
2108 id: simpleTab
2109+ objectName: title
2110 title: i18n.tr("Simple page #" + index)
2111 page: Page {
2112 Row {
2113@@ -55,13 +94,60 @@
2114 iconSource: "call_icon.png"
2115 onTriggered: print("action triggered")
2116 }
2117+ ToolbarButton {
2118+ text: "append"
2119+ onTriggered: {
2120+ print("APPEND TAB")
2121+ tabs.addTab("Appended tab", dynamicTab)
2122+ }
2123+ }
2124+ ToolbarButton {
2125+ text: "insert@1"
2126+ onTriggered: {
2127+ print("INSERT TAB TO #1")
2128+ tabs.insertTab(1, "Inserted tab", dynamicTab)
2129+ }
2130+ }
2131+ ToolbarButton {
2132+ text: "insert@2"
2133+ onTriggered: {
2134+ print("INSERT BETWEEN REPEATERS #1")
2135+ tabs.insertTab(2, "Between repeaters", dynamicTab)
2136+ }
2137+ }
2138+ ToolbarButton {
2139+ text: "insert@here"
2140+ onTriggered: {
2141+ print("INSERT AFTER ME")
2142+ tabs.insertTab(simpleTab.index, "Inserted tab", dynamicTab)
2143+ }
2144+ }
2145+ ToolbarButton {
2146+ text: "incRep"
2147+ onTriggered: {
2148+ print("INCREASE REPEATER MODEL")
2149+ root.repeaterModel += 1
2150+ }
2151+ }
2152+ ToolbarButton {
2153+ text: "remove last"
2154+ onTriggered: {
2155+ print("REMOVE LAST TAB")
2156+ tabs.removeTab(tabs.count - 1)
2157+ }
2158+ }
2159+ ToolbarButton {
2160+ text: "append predec"
2161+ onTriggered: tabs.addTab("Re-added ListView", listViewTab)
2162+ }
2163 }
2164 }
2165 }
2166 Repeater {
2167- model: 3
2168+ model: root.repeaterModel
2169 Tab {
2170 id: tab
2171+ objectName: title
2172 title: "Extra #" + tab.index
2173 page: Page {
2174 Column {
2175@@ -88,6 +174,7 @@
2176 }
2177 Tab {
2178 id: externalTab
2179+ objectName: title
2180 title: i18n.tr("External #" + index)
2181 page: Loader {
2182 parent: externalTab
2183@@ -96,6 +183,8 @@
2184 }
2185 }
2186 Tab {
2187+ id: listViewTab
2188+ objectName: title
2189 title: i18n.tr("List view #" + index)
2190 page: Page {
2191 ListView {
2192
2193=== modified file 'tests/unit/runtest.sh'
2194--- tests/unit/runtest.sh 2014-03-31 18:26:46 +0000
2195+++ tests/unit/runtest.sh 2014-04-10 15:55:52 +0000
2196@@ -33,7 +33,7 @@
2197 if [ $_TARGET != $_TESTFILE ]; then
2198 _CMD="$_CMD -input $_TESTFILE"
2199 fi
2200- _CMD="$_CMD -maxwarnings 4"
2201+ _CMD="$_CMD -maxwarnings 40"
2202 }
2203
2204 function execute_test_cmd {
2205@@ -71,7 +71,6 @@
2206 tst_listitems_base.qml \
2207 tst_statesaver \
2208 tst_theme_engine \
2209- tst_orientation \
2210 tst_tabs.qml \
2211 tst_textfield.qml \
2212 tst_mousefilters'
2213
2214=== modified file 'tests/unit/tst_alarms/tst_alarms.cpp'
2215--- tests/unit/tst_alarms/tst_alarms.cpp 2014-02-21 18:23:41 +0000
2216+++ tests/unit/tst_alarms/tst_alarms.cpp 2014-04-10 15:55:52 +0000
2217@@ -25,6 +25,7 @@
2218 #include "adapters/alarmsadapter_p.h"
2219 #undef protected
2220
2221+#include "uctestcase.h"
2222 #include <QtCore/QString>
2223 #include <QtCore/QTextCodec>
2224 #include <QtCore/QDebug>
2225@@ -64,11 +65,18 @@
2226 syncFetch();
2227 }
2228
2229- bool containsAlarm(UCAlarm *alarm)
2230+ bool containsAlarm(UCAlarm *alarm, bool trace = false)
2231 {
2232 UCAlarmPrivate *pAlarm = UCAlarmPrivate::get(alarm);
2233 QList<AlarmData> alarms = AlarmManager::instance().alarms();
2234 Q_FOREACH(AlarmData i, alarms) {
2235+ if (trace && (alarm->message() == i.message)) {
2236+ qDebug() << "----------------------";
2237+ qDebug() << "Alarm data:" << alarm->message();
2238+ qDebug() << alarm->date() << i.date;
2239+ qDebug() << alarm->type() << i.type;
2240+ qDebug() << alarm->daysOfWeek() << i.days;
2241+ }
2242 if (i == pAlarm->rawData) {
2243 return true;
2244 }
2245@@ -127,7 +135,7 @@
2246
2247 void test_repeating_autoDetect()
2248 {
2249- UCAlarm alarm(QDateTime::currentDateTime(), UCAlarm::AutoDetect, "test_repeating_autoDetect");
2250+ UCAlarm alarm(QDateTime::currentDateTime().addSecs(20), UCAlarm::AutoDetect, "test_repeating_autoDetect");
2251
2252 alarm.save();
2253 waitForRequest(&alarm);
2254@@ -137,28 +145,92 @@
2255
2256 void test_repeating_daily()
2257 {
2258- UCAlarm alarm(QDateTime::currentDateTime(), UCAlarm::Daily, "test_repeating_daily");
2259-
2260- alarm.save();
2261- waitForRequest(&alarm);
2262- QCOMPARE(alarm.error(), (int)UCAlarm::NoError);
2263- QVERIFY(containsAlarm(&alarm));
2264- }
2265-
2266- void test_repeating_givenDay()
2267- {
2268- UCAlarm alarm(QDateTime::currentDateTime(), UCAlarm::Wednesday, "test_repeating_givenDay");
2269-
2270- alarm.save();
2271- waitForRequest(&alarm);
2272- QCOMPARE(alarm.error(), (int)UCAlarm::NoError);
2273- QVERIFY(containsAlarm(&alarm));
2274+ UCAlarm alarm(QDateTime::currentDateTime().addSecs(10), UCAlarm::Daily, "test_repeating_daily");
2275+
2276+ alarm.save();
2277+ waitForRequest(&alarm);
2278+ QCOMPARE(alarm.error(), (int)UCAlarm::NoError);
2279+ QVERIFY(containsAlarm(&alarm));
2280+ }
2281+
2282+ void test_repeating_givenDay_exact_data()
2283+ {
2284+ QTest::addColumn<QString>("message");
2285+ QTest::addColumn<int>("day");
2286+
2287+ QTest::newRow("Monday") << "Monday" << (int)UCAlarm::Monday;
2288+ QTest::newRow("Tuesday") << "Tuesday" << (int)UCAlarm::Tuesday;
2289+ QTest::newRow("Wednesday") << "Wednesday" << (int)UCAlarm::Wednesday;
2290+ QTest::newRow("Thursday") << "Thursday" << (int)UCAlarm::Thursday;
2291+ QTest::newRow("Friday") << "Friday" << (int)UCAlarm::Friday;
2292+ QTest::newRow("Saturday") << "Saturday" << (int)UCAlarm::Saturday;
2293+ QTest::newRow("Sunday") << "Sunday" << (int)UCAlarm::Sunday;
2294+ }
2295+
2296+ void test_repeating_givenDay_exact()
2297+ {
2298+ QFETCH(QString, message);
2299+ QFETCH(int, day);
2300+ UCAlarm alarm(QDateTime::currentDateTime(), (UCAlarm::DaysOfWeek)day, "test_repeating_givenDay_exact_" + message);
2301+
2302+ alarm.save();
2303+ waitForRequest(&alarm);
2304+ QCOMPARE(alarm.error(), (int)UCAlarm::NoError);
2305+ QVERIFY(containsAlarm(&alarm));
2306+ }
2307+
2308+ void test_repeating_moreDays_data()
2309+ {
2310+ QTest::addColumn<QString>("message");
2311+ QTest::addColumn<int>("days");
2312+
2313+ QTest::newRow("First on Monday") << "Monday" << (int)(UCAlarm::Monday | UCAlarm::Sunday);
2314+ QTest::newRow("First on Tuesday") << "Tuesday" << (int)(UCAlarm::Tuesday | UCAlarm::Monday);
2315+ QTest::newRow("First on Wednesday") << "Wednesday" << (int)(UCAlarm::Wednesday | UCAlarm::Tuesday);
2316+ QTest::newRow("First on Thursday") << "Thursday" << (int)(UCAlarm::Thursday | UCAlarm::Wednesday);
2317+ QTest::newRow("First on Friday") << "Friday" << (int)(UCAlarm::Friday | UCAlarm::Thursday);
2318+ QTest::newRow("First on Saturday") << "Saturday" << (int)(UCAlarm::Saturday | UCAlarm::Friday);
2319+ QTest::newRow("First on Sunday") << "Sunday" << (int)(UCAlarm::Sunday | UCAlarm::Saturday);
2320 }
2321
2322 void test_repeating_moreDays()
2323 {
2324- UCAlarm alarm(QDateTime::currentDateTime(), UCAlarm::Monday | UCAlarm::Wednesday, "test_repeating_moreDays");
2325-
2326+ QFETCH(QString, message);
2327+ QFETCH(int, days);
2328+
2329+ QDateTime currentDate(QDateTime::currentDateTime());
2330+
2331+ UCAlarm alarm(currentDate, (UCAlarm::DaysOfWeek)days, "test_repeating_moreDays" + message);
2332+ // the distance is always 6 days between the occurrences
2333+ UCAlarm firstOccurrence(currentDate, alarm.daysOfWeek(), alarm.message());
2334+ // make sure we have the same setup as the original one; checkAlarm() adjusts all alarm data
2335+ UCAlarmPrivate::get(&firstOccurrence)->checkAlarm();
2336+
2337+ alarm.save();
2338+ waitForRequest(&alarm);
2339+ QCOMPARE(alarm.error(), (int)UCAlarm::NoError);
2340+ QVERIFY(containsAlarm(&alarm));
2341+ QVERIFY(containsAlarm(&firstOccurrence));
2342+ }
2343+
2344+ void test_repeating_weekly_data() {
2345+ QTest::addColumn<QString>("message");
2346+ QTest::addColumn<int>("dow");
2347+
2348+ QTest::newRow("Monday") << "Monday" << (int)UCAlarm::Monday;
2349+ QTest::newRow("Tuesday") << "Tuesday" << (int)UCAlarm::Tuesday;
2350+ QTest::newRow("Wednesday") << "Wednesday" << (int)UCAlarm::Wednesday;
2351+ QTest::newRow("Thursday") << "Thursday" << (int)UCAlarm::Thursday;
2352+ QTest::newRow("Friday") << "Friday" << (int)UCAlarm::Friday;
2353+ QTest::newRow("Saturday") << "Saturday" << (int)UCAlarm::Saturday;
2354+ QTest::newRow("Sunday") << "Sunday" << (int)UCAlarm::Sunday;
2355+ }
2356+ void test_repeating_weekly()
2357+ {
2358+ QFETCH(QString, message);
2359+ QFETCH(int, dow);
2360+
2361+ UCAlarm alarm(QDateTime::currentDateTime().addSecs(3600), (UCAlarm::DaysOfWeek)dow, "test_repeating_weekly_" + message);
2362 alarm.save();
2363 waitForRequest(&alarm);
2364 QCOMPARE(alarm.error(), (int)UCAlarm::NoError);
2365@@ -273,6 +345,85 @@
2366 QVERIFY(!containsAlarm(&copy));
2367 }
2368
2369+ void test_updateAlarm_Repeating()
2370+ {
2371+ UCAlarm alarm(QDateTime::currentDateTime().addMSecs(5000), UCAlarm::AutoDetect, "test_updateAlarm_Repeating");
2372+
2373+ alarm.save();
2374+ waitForRequest(&alarm);
2375+ QCOMPARE(alarm.error(), (int)UCAlarm::NoError);
2376+ QVERIFY(containsAlarm(&alarm));
2377+
2378+ alarm.setDate(alarm.date().addDays(1));
2379+ alarm.save();
2380+ waitForRequest(&alarm);
2381+ QCOMPARE(alarm.error(), (int)UCAlarm::NoError);
2382+ QVERIFY(containsAlarm(&alarm));
2383+
2384+ alarm.setDaysOfWeek(UCAlarm::Daily);
2385+ alarm.save();
2386+ waitForRequest(&alarm);
2387+ QCOMPARE(alarm.error(), (int)UCAlarm::NoError);
2388+ QVERIFY(containsAlarm(&alarm));
2389+ }
2390+
2391+ void test_fetchAlarmPlus7Days()
2392+ {
2393+ QDateTime dt = QDateTime::currentDateTime().addDays(10);
2394+ UCAlarm alarm(dt, "test_fetchAlarmPlus7Days");
2395+
2396+ alarm.save();
2397+ waitForRequest(&alarm);
2398+ QCOMPARE(alarm.error(), (int)UCAlarm::NoError);
2399+ QVERIFY(containsAlarm(&alarm));
2400+
2401+ UCAlarm nextMonth(dt.addMonths(1), "test_fetchAlarmPlus1Month");
2402+ nextMonth.save();
2403+ waitForRequest(&nextMonth);
2404+ QCOMPARE(nextMonth.error(), (int)UCAlarm::NoError);
2405+ QVERIFY(containsAlarm(&nextMonth));
2406+
2407+ UCAlarm nextYear(dt.addYears(1), "test_fetchAlarmPlus1Year");
2408+ nextYear.save();
2409+ waitForRequest(&nextYear);
2410+ QCOMPARE(nextYear.error(), (int)UCAlarm::NoError);
2411+ QVERIFY(containsAlarm(&nextYear));
2412+ }
2413+
2414+ void test_correctAlarmOrderDaily()
2415+ {
2416+ QDate cd = QDate::currentDate();
2417+ QTime ct = QTime::currentTime();
2418+ QDateTime dt(cd, QTime(ct.hour(), ct.minute(), ct.second() + 2));
2419+ QDateTime nextDt(cd.addDays(1), QTime(ct.hour(), ct.minute(), ct.second() + 2));
2420+ UCAlarm alarm(dt, UCAlarm::Daily, "test_correctAlarmOrderDaily");
2421+ UCAlarm nextAlarm(nextDt, UCAlarm::Daily, "test_correctAlarmOrderDaily");
2422+
2423+ alarm.save();
2424+ waitForRequest(&alarm);
2425+ QTest::qWait(2000);
2426+ syncFetch();
2427+ QCOMPARE(alarm.error(), (int)UCAlarm::NoError);
2428+ QVERIFY(containsAlarm(&nextAlarm));
2429+ }
2430+
2431+ void test_correctAlarmOrderWeekly()
2432+ {
2433+ QDate cd = QDate::currentDate();
2434+ QTime ct = QTime::currentTime();
2435+ QDateTime dt(cd, QTime(ct.hour(), ct.minute(), ct.second() + 1));
2436+ QDateTime nextDt(cd.addDays(7), QTime(ct.hour(), ct.minute(), ct.second() + 1));
2437+ UCAlarm alarm(dt, UCAlarm::AutoDetect, "test_correctAlarmOrderWeekly");
2438+ UCAlarm nextAlarm(nextDt, UCAlarm::AutoDetect, "test_correctAlarmOrderWeekly");
2439+
2440+ alarm.save();
2441+ waitForRequest(&alarm);
2442+ QTest::qWait(2000);
2443+ syncFetch();
2444+ QCOMPARE(alarm.error(), (int)UCAlarm::NoError);
2445+ QVERIFY(containsAlarm(&nextAlarm));
2446+ }
2447+
2448 };
2449
2450 QTEST_MAIN(tst_UCAlarms)
2451
2452=== modified file 'tests/unit/tst_mainview/tst_mainview.cpp'
2453--- tests/unit/tst_mainview/tst_mainview.cpp 2014-04-07 14:26:30 +0000
2454+++ tests/unit/tst_mainview/tst_mainview.cpp 2014-04-10 15:55:52 +0000
2455@@ -145,14 +145,11 @@
2456 }
2457
2458 void testNoWarnings_bug186065() {
2459- QSignalSpy spy(view->engine(), SIGNAL(warnings(QList<QQmlError>)));
2460- spy.setParent(view);
2461-
2462- QQuickItem *root = loadTest("AppName.qml"); // An empty MainView would suffice
2463- QVERIFY(root);
2464+ // An empty MainView would suffice
2465+ QScopedPointer<UbuntuTestCase>testCase (new UbuntuTestCase("AppName.qml"));
2466
2467 // No warnings from QML
2468- QCOMPARE(spy.count(), 0);
2469+ QCOMPARE(testCase->warnings(), 0);
2470 }
2471
2472 void testWindowTitleFromPage() {
2473
2474=== modified file 'tests/unit/tst_page/tst_page.cpp'
2475--- tests/unit/tst_page/tst_page.cpp 2013-11-08 20:19:58 +0000
2476+++ tests/unit/tst_page/tst_page.cpp 2014-04-10 15:55:52 +0000
2477@@ -23,7 +23,7 @@
2478 #include <QtQuick/QQuickItem>
2479 #include <QtCore/QDir>
2480
2481-#include "ucunits.h"
2482+#include "uctestcase.h"
2483
2484 class tst_Page : public QObject
2485 {
2486@@ -38,55 +38,19 @@
2487 {
2488 }
2489
2490- QQuickItem *loadTest(const QString &document)
2491- {
2492- // load the document
2493- view->setSource(QUrl::fromLocalFile(document));
2494- QTest::waitForEvents();
2495-
2496- return view->rootObject();
2497- }
2498-
2499- QQuickItem *testItem(QQuickItem *that, const QString &identifier)
2500- {
2501- if (that->property(identifier.toLocal8Bit()).isValid())
2502- return that->property(identifier.toLocal8Bit()).value<QQuickItem*>();
2503-
2504- QList<QQuickItem*> children = that->findChildren<QQuickItem*>(identifier);
2505- return (children.count() > 0) ? children[0] : 0;
2506- }
2507-
2508 private Q_SLOTS:
2509
2510 void initTestCase()
2511 {
2512- QString modules("../../../modules");
2513- QVERIFY(QDir(modules).exists());
2514-
2515- view = new QQuickView;
2516- QQmlEngine *quickEngine = view->engine();
2517-
2518- view->setGeometry(0,0, UCUnits::instance().gu(40), UCUnits::instance().gu(30));
2519- //add modules folder so we have access to the plugin from QML
2520- QStringList imports = quickEngine->importPathList();
2521- imports.prepend(QDir(modules).absolutePath());
2522- quickEngine->setImportPathList(imports);
2523 }
2524
2525 void cleanupTestCase()
2526 {
2527- delete view;
2528 }
2529
2530 void testAnchorToPage_bug1249386() {
2531- QSignalSpy spy(view->engine(), SIGNAL(warnings(QList<QQmlError>)));
2532- spy.setParent(view);
2533-
2534- QQuickItem *root = loadTest("AnchorToPage.qml");
2535- QVERIFY(root);
2536-
2537- // No warnings from QML
2538- QCOMPARE(spy.count(), 0);
2539+ QScopedPointer<UbuntuTestCase> testCase(new UbuntuTestCase("AnchorToPage.qml"));
2540+ QCOMPARE(testCase->warnings(), 0);
2541 }
2542 };
2543
2544
2545=== added file 'tests/unit_x11/tst_components/ExternalTab.qml'
2546--- tests/unit_x11/tst_components/ExternalTab.qml 1970-01-01 00:00:00 +0000
2547+++ tests/unit_x11/tst_components/ExternalTab.qml 2014-04-10 15:55:52 +0000
2548@@ -0,0 +1,21 @@
2549+/*
2550+ * Copyright 2014 Canonical Ltd.
2551+ *
2552+ * This program is free software; you can redistribute it and/or modify
2553+ * it under the terms of the GNU Lesser General Public License as published by
2554+ * the Free Software Foundation; version 3.
2555+ *
2556+ * This program is distributed in the hope that it will be useful,
2557+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2558+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2559+ * GNU Lesser General Public License for more details.
2560+ *
2561+ * You should have received a copy of the GNU Lesser General Public License
2562+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2563+ */
2564+
2565+import QtQuick 2.0
2566+import Ubuntu.Components 0.1
2567+
2568+Tab {
2569+}
2570
2571=== modified file 'tests/unit_x11/tst_components/tst_tabs.qml'
2572--- tests/unit_x11/tst_components/tst_tabs.qml 2014-01-13 12:43:12 +0000
2573+++ tests/unit_x11/tst_components/tst_tabs.qml 2014-04-10 15:55:52 +0000
2574@@ -27,6 +27,13 @@
2575 id: emptyTabs
2576 }
2577
2578+ Component {
2579+ id: dynamicTab
2580+ Tab{
2581+ title: "OriginalTitle"
2582+ }
2583+ }
2584+
2585 MainView {
2586 id: mainView
2587 anchors.fill: parent
2588@@ -424,5 +431,149 @@
2589 mouseRelease(tabs.tabBar, tabs.tabBar.width/2, tabs.tabBar.height/2);
2590 compare(tabs.tabBar.pressed, false, "After releasing, pressed is false");
2591 }
2592+
2593+
2594+
2595+ // these tests should not be mixed with Repeaters
2596+ function test_z_addTab() {
2597+ var newTab = tabs.addTab("Dynamic Tab", dynamicTab);
2598+ compare((newTab !== null), true, "tab added");
2599+ compare(newTab.active, false, "the inserted tab is inactive");
2600+ compare(newTab.index, tabs.count - 1, "the tab is the last one");
2601+ }
2602+
2603+ function test_z_addExternalTab() {
2604+ var newTab = tabs.addTab("External Tab", Qt.resolvedUrl("ExternalTab.qml"));
2605+ compare((newTab !== null), true, "tab added");
2606+ compare(newTab.active, false, "the inserted tab is inactive");
2607+ compare(newTab.index, tabs.count - 1, "the tab is the last one");
2608+ }
2609+
2610+ function test_z_addTabWithDefaultTitle() {
2611+ var newTab = tabs.addTab("", dynamicTab);
2612+ compare((newTab !== null), true, "tab added");
2613+ compare(newTab.title, "OriginalTitle", "tab created with original title");
2614+ }
2615+
2616+ function test_z_insertTab() {
2617+ var tabIndex = Math.ceil(tabs.count / 2);
2618+ var newTab = tabs.insertTab(tabIndex, "Inserted tab", dynamicTab);
2619+ compare((newTab !== null), true, "tab inserted");
2620+ compare(newTab.index, tabIndex, "this is the first tab");
2621+ compare(tabs.selectedTab !== newTab, true, "the new tab is not the active one");
2622+ }
2623+
2624+ function test_z_insertExternalTab() {
2625+ var tabIndex = Math.ceil(tabs.count / 2);
2626+ var newTab = tabs.insertTab(tabIndex, "Inserted External tab", Qt.resolvedUrl("ExternalTab.qml"));
2627+ compare((newTab !== null), true, "tab inserted");
2628+ compare(newTab.index, tabIndex, "this is the first tab");
2629+ compare(tabs.selectedTab !== newTab, true, "the new tab is not the active one");
2630+ }
2631+
2632+ function test_z_insertTabAtSelectedIndex() {
2633+ tabs.selectedTabIndex = 1;
2634+ var tabIndex = tabs.selectedTabIndex - 1;
2635+ var newTab = tabs.insertTab(tabIndex, "InsertedAtSelected tab", dynamicTab);
2636+ compare((newTab !== null), true, "tab inserted");
2637+ compare(newTab.index, tabIndex, "inserted at selected tab");
2638+ compare(tabs.selectedTabIndex != (tabIndex + 1), true, "it is not the selected tab");
2639+ }
2640+
2641+ function test_z_insertTabFront() {
2642+ var newTab = tabs.insertTab(-1, "PreTab", dynamicTab);
2643+ compare(newTab !== null, true, "pre-tab inserted");
2644+ compare(newTab.index, 0, "this is the new first tab");
2645+ compare(tabs.selectedTab !== newTab, true, "the new tab is not the active one");
2646+ }
2647+
2648+ function test_z_insertTabEnd() {
2649+ var newTab = tabs.insertTab(tabs.count, "PostTab", dynamicTab);
2650+ compare(newTab !== null, true, "post-tab inserted");
2651+ compare(newTab.index, tabs.count - 1, "thsi is the new last tab");
2652+ compare(tabs.selectedTab !== newTab, true, "the new tab is not the active one");
2653+ }
2654+
2655+ function test_z_insertTabAndActivate() {
2656+ var newTab = tabs.addTab("Inserted tab", dynamicTab);
2657+ compare((newTab !== null), true, "tab inserted");
2658+ compare(newTab.index, tabs.count - 1, "the tab is the last one");
2659+ tabs.selectedTabIndex = newTab.index;
2660+ compare(tabs.selectedTab, newTab, "the inserted tab is selected");
2661+ compare(newTab.active, true, "the new tab is active");
2662+ }
2663+
2664+ function test_z_moveTab() {
2665+ var selectedIndex = tabs.count - 1;
2666+ tabs.selectedTabIndex = selectedIndex;
2667+ compare(tabs.moveTab(0, selectedIndex), true, "first tab moved to last");
2668+ compare(tabs.selectedTabIndex, selectedIndex - 1, "the selected index moved backwards");
2669+ tabs.selectedTabIndex = selectedIndex = 0;
2670+ compare(tabs.moveTab(selectedIndex, selectedIndex + 1), true, "selected tab moved as next");
2671+ compare(tabs.selectedTabIndex, selectedIndex + 1, "the selected index moved forewards");
2672+ }
2673+
2674+ function test_z_moveSelectedTab() {
2675+ tabs.selectedTabIndex = 0;
2676+ tabs.moveTab(0, 1);
2677+ compare(tabs.selectedTabIndex, 1, "selected tab moved");
2678+ }
2679+
2680+ function test_z_moveTabFail() {
2681+ compare(tabs.moveTab(-1, tabs.count - 1), false, "from-parameter out of range");
2682+ compare(tabs.moveTab(0, tabs.count), false, "to-parameter out of range");
2683+ }
2684+
2685+ function test_z_removeTab() {
2686+ compare(tabs.removeTab(tabs.count - 1), true, "last tab removed");
2687+ tabs.selectedTabIndex = 0;
2688+ compare(tabs.removeTab(0), true, "active tab removed");
2689+ compare(tabs.selectedTabIndex, 0, "the next tab is selected")
2690+ }
2691+
2692+ function test_z_removeActiveTab() {
2693+ tabs.selectedTabIndex = 1;
2694+ compare(tabs.removeTab(1), true, "selected tab removed");
2695+ compare(tabs.selectedTabIndex, 1, "selected tab is next");
2696+
2697+ tabs.selectedTabIndex = tabs.count - 1;
2698+ compare(tabs.removeTab(tabs.count - 1), true, "last tab removed");
2699+ compare(tabs.selectedTabIndex, tabs.count - 1, "selected tab moved to last item");
2700+ }
2701+
2702+ function test_z_removeTabAfterActiveTab() {
2703+ var activeTab = tabs.count - 2;
2704+ tabs.selectedTabIndex = activeTab;
2705+ compare(tabs.removeTab(tabs.count - 1), true, "last tab removed");
2706+ compare(tabs.selectedTabIndex, activeTab, "the selected tab wasn't moved");
2707+ }
2708+
2709+ function test_z_removeTabBeforeActiveTab() {
2710+ var activeTab = tabs.count - 1;
2711+ tabs.selectedTabIndex = activeTab;
2712+ compare(tabs.removeTab(0), true, "first tab removed");
2713+ compare(tabs.selectedTabIndex, activeTab - 1, "the selected tab index decreased");
2714+ }
2715+
2716+ function test_zz_addTabAfterCleaningUpTabs() {
2717+ while (tabs.count > 1) {
2718+ tabs.removeTab(tabs.count - 1);
2719+ }
2720+ compare(tabs.selectedTabIndex, 0, "the only tab is the selected one");
2721+ // add a new tab anc check the count (default added tas should not be added anymore
2722+ tabs.addTab("Second tab", dynamicTab);
2723+ compare(tabs.count, 2, "we have two tabs only");
2724+ }
2725+
2726+ function test_zz_addPredeclaredTab() {
2727+ tabs.removeTab(tab1.index);
2728+
2729+ // add a predeclared tab back with original title
2730+ compare(tabs.addTab("", tab1), tab1, "tab1 was not added back");
2731+ compare(tab1.title, "tab 1", "the original title differs");
2732+
2733+ // add a predeclared tab which was added already
2734+ compare(tabs.addTab("", tab1), null, "tab1 is already in tabs");
2735+ }
2736 }
2737 }
2738
2739=== modified file 'tests/unit_x11/tst_mousefilters/tst_mousefilterstest.cpp'
2740--- tests/unit_x11/tst_mousefilters/tst_mousefilterstest.cpp 2014-02-13 17:16:06 +0000
2741+++ tests/unit_x11/tst_mousefilters/tst_mousefilterstest.cpp 2014-04-10 15:55:52 +0000
2742@@ -24,6 +24,7 @@
2743
2744 #include "ucmouse.h"
2745 #include "ucinversemouse.h"
2746+#include "uctestcase.h"
2747
2748 #include "ucunits.h"
2749 #include "quickutils.h"
2750@@ -56,24 +57,10 @@
2751 bool insideClick;
2752 bool oskClick;
2753
2754- QQuickView * loadTest(const QString &file, QSignalSpy **spy = 0)
2755+ QQuickView * loadTest(const QString &file)
2756 {
2757- QQuickView *view = new QQuickView;
2758- if (spy) {
2759- *spy = new QSignalSpy(view->engine(), SIGNAL(warnings(QList<QQmlError>)));
2760- (*spy)->setParent(view);
2761- }
2762- view->engine()->addImportPath(m_modulePath);
2763-
2764- view->setSource(QUrl::fromLocalFile(file));
2765- if (!view->rootObject()) {
2766- delete view;
2767- view = 0;
2768- } else {
2769- view->show();
2770- QTest::qWaitForWindowExposed(view);
2771- }
2772- return view;
2773+ UbuntuTestCase* testCase = new UbuntuTestCase(file);
2774+ return qobject_cast<QQuickView*>(testCase);
2775 }
2776
2777 void mousePressAndHold(QWindow *view, Qt::MouseButton button, Qt::KeyboardModifiers modifiers, const QPoint &point, int delay = DefaultPressAndHoldDelay + 200)
2778@@ -202,10 +189,9 @@
2779
2780 void testCase_pressedOutsideTextInputAfter()
2781 {
2782- QSignalSpy *spy;
2783- QScopedPointer<QQuickView> view(loadTest("FilterInverseTextInputAfter.qml", &spy));
2784- QVERIFY(view);
2785- QCOMPARE(spy->count(), 1);
2786+ QScopedPointer<UbuntuTestCase> view(new UbuntuTestCase("FilterInverseTextInputAfter.qml"));
2787+ QCOMPARE(view->warnings(), 1);
2788+
2789 UCInverseMouse *filter = attachedFilter<UCInverseMouse>(view->rootObject(), "FilterOwner");
2790 QVERIFY(filter);
2791 QSignalSpy pressed(filter, SIGNAL(pressed(QQuickMouseEvent*)));
2792@@ -554,18 +540,14 @@
2793
2794 void testCase_mouseFilterAttachedToNonItem()
2795 {
2796- QSignalSpy *warningSpy;
2797- QScopedPointer<QQuickView> view(loadTest("MouseFilterAttachedToNonItem.qml", &warningSpy));
2798- QVERIFY(view);
2799- QCOMPARE(warningSpy->count(), 1);
2800+ QScopedPointer<UbuntuTestCase> testCase(new UbuntuTestCase("MouseFilterAttachedToNonItem.qml"));
2801+ QCOMPARE(testCase->warnings(), 1);
2802 }
2803
2804 void testCase_inverseMouseFilterAttachedToNonItem()
2805 {
2806- QSignalSpy *warningSpy;
2807- QScopedPointer<QQuickView> view(loadTest("InverseMouseFilterAttachedToNonItem.qml", &warningSpy));
2808- QVERIFY(view);
2809- QCOMPARE(warningSpy->count(), 1);
2810+ QScopedPointer<UbuntuTestCase> testCase(new UbuntuTestCase("InverseMouseFilterAttachedToNonItem.qml"));
2811+ QCOMPARE(testCase->warnings(), 1);
2812 }
2813
2814 void testCase_forwardedEventsToItem()
2815
2816=== modified file 'tests/unit_x11/tst_orientation/tst_orientation.cpp'
2817--- tests/unit_x11/tst_orientation/tst_orientation.cpp 2014-03-19 11:24:16 +0000
2818+++ tests/unit_x11/tst_orientation/tst_orientation.cpp 2014-04-10 15:55:52 +0000
2819@@ -29,75 +29,37 @@
2820 #include <QtQuick/QQuickItem>
2821 #include <QtQml/QQmlProperty>
2822
2823+#include "uctestcase.h"
2824+
2825 class tst_OrientationTest : public QObject
2826 {
2827 Q_OBJECT
2828 public:
2829 tst_OrientationTest() {}
2830
2831-private:
2832- QString m_modulePath;
2833-
2834- QQuickView *createView(const QString &file, QSignalSpy **spy = 0)
2835- {
2836- QQuickView *view = new QQuickView(0);
2837- if (spy) {
2838- *spy = new QSignalSpy(view->engine(), SIGNAL(warnings(QList<QQmlError>)));
2839- (*spy)->setParent(view);
2840- }
2841- view->engine()->addImportPath(m_modulePath);
2842- view->setSource(QUrl::fromLocalFile(file));
2843- if (!view->rootObject()) {
2844- return 0;
2845- }
2846- view->show();
2847- QTest::qWaitForWindowExposed(view);
2848- return view;
2849- }
2850-
2851 private Q_SLOTS:
2852
2853- void initTestCase()
2854- {
2855- QDir modules ("../../../modules");
2856- QVERIFY(modules.exists());
2857- m_modulePath = modules.absolutePath();
2858- }
2859-
2860- void cleanupTestCase()
2861- {
2862- }
2863-
2864 void test_defaults()
2865 {
2866- QSignalSpy *spy;
2867- QQuickView *view = createView("Defaults.qml", &spy);
2868- QVERIFY(view);
2869- QQuickItem *helper = view->rootObject()->findChild<QQuickItem*>("helper");
2870- QVERIFY(helper);
2871+ QScopedPointer<UbuntuTestCase> testCase(new UbuntuTestCase("Defaults.qml"));
2872+ QQuickItem *helper = testCase->findItem<QQuickItem*>("helper");
2873 QCOMPARE(helper->property("automaticOrientation").toBool(), true);
2874 // No warnings expected
2875- QCOMPARE(spy->count(), 0);
2876- delete view;
2877+ QCOMPARE(testCase->warnings(), 0);
2878 }
2879
2880 void test_manualAngle()
2881 {
2882- QSignalSpy *spy;
2883- QQuickView *view = createView("ManualAngle.qml", &spy);
2884- QVERIFY(view);
2885- QQuickItem *helper = view->rootObject()->findChild<QQuickItem*>("helper");
2886- QVERIFY(helper);
2887+ QScopedPointer<UbuntuTestCase> testCase(new UbuntuTestCase("ManualAngle.qml"));
2888+ QQuickItem *helper = testCase->findItem<QQuickItem*>("helper");
2889 // No warning about "window" being undefined must appear
2890 QSKIP("TypeError: Cannot set property 'contentOrientation' of null");
2891- QCOMPARE(spy->count(), 0);
2892+ QCOMPARE(testCase->warnings(), 0);
2893 QCOMPARE(helper->property("orientationAngle").toInt(), 90);
2894 // Verify expected values
2895- QQuickItem *checkpoint = view->rootObject()->findChild<QQuickItem*>("checkpoint");
2896- QVERIFY(checkpoint);
2897+ QQuickItem *checkpoint = testCase->findItem<QQuickItem*>("checkpoint");
2898 // window.contentOrientation
2899 QCOMPARE(checkpoint->property("contentOrientation").toInt(), helper->property("orientationAngle").toInt());
2900- delete view;
2901 }
2902 };
2903
2904
2905=== modified file 'tests/unit_x11/tst_statesaver/tst_statesaver.cpp'
2906--- tests/unit_x11/tst_statesaver/tst_statesaver.cpp 2014-03-26 19:25:55 +0000
2907+++ tests/unit_x11/tst_statesaver/tst_statesaver.cpp 2014-04-10 15:55:52 +0000
2908@@ -32,6 +32,7 @@
2909 #include "ucapplication.h"
2910 #include <QtCore/QProcess>
2911 #include <QtCore/QProcessEnvironment>
2912+#include "uctestcase.h"
2913 #include <signal.h>
2914
2915 #define protected public
2916@@ -48,30 +49,24 @@
2917 private:
2918 QString m_modulePath;
2919
2920- QQuickView *createView(const QString &file, QSignalSpy **spy = 0)
2921- {
2922- QQuickView *view = new QQuickView(0);
2923- if (spy) {
2924- *spy = new QSignalSpy(view->engine(), SIGNAL(warnings(QList<QQmlError>)));
2925- (*spy)->setParent(view);
2926- }
2927- view->engine()->addImportPath(m_modulePath);
2928- view->setSource(QUrl::fromLocalFile(file));
2929- if (!view->rootObject()) {
2930- return 0;
2931- }
2932- view->show();
2933- QTest::qWaitForWindowExposed(view);
2934-
2935- // connect StateSaverBackend's initiateStateSaving() to view destroyed
2936- return view;
2937- }
2938-
2939- void resetView(QScopedPointer<QQuickView> &view, const QString &file, QSignalSpy **spy = 0)
2940- {
2941- Q_EMIT StateSaverBackend::instance().initiateStateSaving();
2942- view.reset();
2943- view.reset(createView(file, spy));
2944+ QQuickView *createView(const QString &file)
2945+ {
2946+ UbuntuTestCase* testCase = new UbuntuTestCase(file);
2947+ return qobject_cast<QQuickView*>(testCase);
2948+ }
2949+
2950+ void resetView(QScopedPointer<UbuntuTestCase> &view, const QString &file)
2951+ {
2952+ Q_EMIT StateSaverBackend::instance().initiateStateSaving();
2953+ view.reset();
2954+ view.reset(new UbuntuTestCase(file));
2955+ }
2956+
2957+ void resetView(QScopedPointer<QQuickView> &view, const QString &file)
2958+ {
2959+ Q_EMIT StateSaverBackend::instance().initiateStateSaving();
2960+ view.reset();
2961+ view.reset(createView(file));
2962 }
2963
2964 QString stateFile(const QString &appId)
2965@@ -236,18 +231,17 @@
2966
2967 void test_InvalidUID()
2968 {
2969- QSignalSpy *spy;
2970- QScopedPointer<QQuickView> view(createView("InvalidUID.qml", &spy));
2971+ QScopedPointer<UbuntuTestCase> view(new UbuntuTestCase("InvalidUID.qml"));
2972 QVERIFY(view);
2973- QCOMPARE(spy->count(), 1);
2974+ QCOMPARE(view->warnings(), 1);
2975 QObject *testItem = view->rootObject()->findChild<QObject*>("testItem");
2976 QVERIFY(testItem);
2977
2978 testItem->setObjectName("updated");
2979
2980- resetView(view, "InvalidUID.qml", &spy);
2981+ resetView(view, "InvalidUID.qml");
2982 QVERIFY(view);
2983- QCOMPARE(spy->count(), 1);
2984+ QCOMPARE(view->warnings(), 1);
2985 testItem = view->rootObject()->findChild<QObject*>("updated");
2986 QVERIFY(testItem == 0);
2987 }
2988@@ -269,18 +263,17 @@
2989
2990 void test_InvalidGroupProperty()
2991 {
2992- QSignalSpy *spy;
2993- QScopedPointer<QQuickView> view(createView("InvalidGroupProperty.qml", &spy));
2994+ QScopedPointer<UbuntuTestCase> view(new UbuntuTestCase("InvalidGroupProperty.qml"));
2995 QVERIFY(view);
2996- QCOMPARE(spy->count(), 1);
2997+ QCOMPARE(view->warnings(), 1);
2998 QObject *testItem = view->rootObject()->findChild<QObject*>("testItem");
2999 QVERIFY(testItem);
3000
3001 testItem->setObjectName("group");
3002
3003- resetView(view, "InvalidGroupProperty.qml", &spy);
3004+ resetView(view, "InvalidGroupProperty.qml");
3005 QVERIFY(view);
3006- QCOMPARE(spy->count(), 1);
3007+ QCOMPARE(view->warnings(), 1);
3008 testItem = view->rootObject()->findChild<QObject*>("group");
3009 QVERIFY(testItem == 0);
3010 }
3011@@ -389,8 +382,7 @@
3012
3013 void test_ComponentsWithStateSaversNoId()
3014 {
3015- QSignalSpy *spy;
3016- QScopedPointer<QQuickView> view(createView("ComponentsWithStateSaversNoId.qml", &spy));
3017+ QScopedPointer<UbuntuTestCase> view(new UbuntuTestCase("ComponentsWithStateSaversNoId.qml"));
3018 QVERIFY(view);
3019 QObject *control1 = view->rootObject()->findChild<QObject*>("control1");
3020 QVERIFY(control1);
3021@@ -399,7 +391,7 @@
3022 UCStateSaverAttached *stateSaver1 = qobject_cast<UCStateSaverAttached*>(qmlAttachedPropertiesObject<UCStateSaver>(control1, false));
3023 QVERIFY(stateSaver1);
3024 QVERIFY(stateSaver1->enabled() == false);
3025- QCOMPARE(spy->count(), 1);
3026+ QCOMPARE(view->warnings(), 1);
3027 UCStateSaverAttached *stateSaver2 = qobject_cast<UCStateSaverAttached*>(qmlAttachedPropertiesObject<UCStateSaver>(control2, false));
3028 QVERIFY(stateSaver2);
3029 QVERIFY(stateSaver2->enabled());
3030
3031=== modified file 'tests/unit_x11/tst_test/tst_ubuntutestcase.qml'
3032--- tests/unit_x11/tst_test/tst_ubuntutestcase.qml 2014-02-13 10:27:14 +0000
3033+++ tests/unit_x11/tst_test/tst_ubuntutestcase.qml 2014-04-10 15:55:52 +0000
3034@@ -23,30 +23,57 @@
3035 width: 800
3036 height: 600
3037
3038- MouseArea {
3039- id: mouseArea
3040- objectName: "myMouseArea"
3041- anchors.fill: parent
3042- hoverEnabled: true
3043- property int testX : 0
3044- property int testY : 0
3045- property int steps : 0
3046+ Column {
3047+ anchors.fill: parent
3048+ MouseArea {
3049+ id: mouseArea
3050+ objectName: "myMouseArea"
3051+ width: parent.width
3052+ height: 300
3053+ hoverEnabled: true
3054+ acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
3055+ property int testX : 0
3056+ property int testY : 0
3057+ property int steps : 0
3058
3059- onPositionChanged: {
3060- testX = mouseX;
3061- testY = mouseY;
3062- steps++;
3063- }
3064+ onPositionChanged: {
3065+ testX = mouseX;
3066+ testY = mouseY;
3067+ steps++;
3068+ }
3069+ }
3070+ Flickable {
3071+ id: flicker
3072+ width: parent.width
3073+ height: 400
3074+ contentWidth: rect.width
3075+ contentHeight: rect.height
3076+ clip: true
3077+ Rectangle {
3078+ id: rect
3079+ color: "blue"
3080+ width: 1000
3081+ height: 1000
3082+ }
3083+ }
3084 }
3085
3086 UbuntuTestCase {
3087 name: "TestTheUbuntuTestCase"
3088 when: windowShown
3089
3090+ function init() {
3091+ mouseArea.steps = 0;
3092+ }
3093+ function cleanup() {
3094+ movementSpy.clear();
3095+ longPressSpy.clear();
3096+ }
3097+
3098 function test_mouseMoveSlowly() {
3099- mouseMoveSlowly(root,0,0,800,600,10,100);
3100+ mouseMoveSlowly(root,0,0,800,300,10,100);
3101 compare(mouseArea.testX,800);
3102- compare(mouseArea.testY,600);
3103+ compare(mouseArea.testY,300);
3104 compare(mouseArea.steps,10);
3105 }
3106
3107@@ -58,5 +85,66 @@
3108 child = findChild(root,"NoSuchChildHere");
3109 compare(child===null,true,"When there is no child, function should return null");
3110 }
3111+
3112+ SignalSpy {
3113+ id: longPressSpy
3114+ target: mouseArea
3115+ signalName: "onPressAndHold"
3116+ }
3117+
3118+ function test_longPress_left() {
3119+ longPressSpy.clear();
3120+ mouseLongPress(mouseArea, mouseArea.width / 2, mouseArea.height / 2);
3121+ longPressSpy.wait();
3122+ // cleanup
3123+ mouseRelease(mouseArea, mouseArea.width / 2, mouseArea.height / 2);
3124+ }
3125+
3126+ function test_longPress_right() {
3127+ longPressSpy.clear();
3128+ mouseLongPress(mouseArea, mouseArea.width / 2, mouseArea.height / 2, Qt.RightButton);
3129+ longPressSpy.wait();
3130+ // cleanup
3131+ mouseRelease(mouseArea, mouseArea.width / 2, mouseArea.height / 2, Qt.RightButton);
3132+ }
3133+
3134+ function test_longPress_middle() {
3135+ longPressSpy.clear();
3136+ mouseLongPress(mouseArea, mouseArea.width / 2, mouseArea.height / 2, Qt.MiddleButton);
3137+ longPressSpy.wait();
3138+ // cleanup
3139+ mouseRelease(mouseArea, mouseArea.width / 2, mouseArea.height / 2, Qt.MiddleButton);
3140+ }
3141+
3142+ SignalSpy {
3143+ id: movementSpy
3144+ target: flicker
3145+ signalName: "onMovementEnded"
3146+ }
3147+
3148+ function test_flick_default() {
3149+ flick(flicker, 0, 0, flicker.width, flicker.height);
3150+ movementSpy.wait();
3151+ }
3152+ function test_flick_long() {
3153+ flick(flicker, 0, 0, flicker.width, flicker.height, -1, 10);
3154+ movementSpy.wait();
3155+ }
3156+ function test_flick_short() {
3157+ flick(flicker, 0, 0, flicker.width, flicker.height, -1, 1);
3158+ movementSpy.wait();
3159+ }
3160+ function test_flick_pressTimeout() {
3161+ flick(flicker, 0, 0, flicker.width, flicker.height, 400);
3162+ movementSpy.wait();
3163+ }
3164+ function test_flick_pressTimeout_short() {
3165+ flick(flicker, flicker.width, flicker.height, -flicker.width, -flicker.height, 400, 1);
3166+ movementSpy.wait();
3167+ }
3168+ function test_flick_pressTimeout_long() {
3169+ flick(flicker, flicker.width, flicker.height, -flicker.width, -flicker.height, 400, 100);
3170+ movementSpy.wait();
3171+ }
3172 }
3173 }
3174
3175=== modified file 'tests/unit_x11/tst_theme_engine/tst_theme_enginetest.cpp'
3176--- tests/unit_x11/tst_theme_engine/tst_theme_enginetest.cpp 2013-12-03 13:09:51 +0000
3177+++ tests/unit_x11/tst_theme_engine/tst_theme_enginetest.cpp 2014-04-10 15:55:52 +0000
3178@@ -21,6 +21,8 @@
3179 #include <QtQml/QQmlComponent>
3180 #include "uctheme.h"
3181
3182+Q_DECLARE_METATYPE(QList<QQmlError>)
3183+
3184 class tst_UCTheme : public QObject
3185 {
3186 Q_OBJECT
3187@@ -154,6 +156,7 @@
3188
3189 UCTheme theme;
3190 QQmlEngine engine;
3191+ qRegisterMetaType<QList <QQmlError> >();
3192 QSignalSpy spy(&engine, SIGNAL(warnings(QList<QQmlError>)));
3193 theme.registerToContext(engine.rootContext());
3194 QQmlComponent parentComponent(&engine, "Parent.qml");

Subscribers

People subscribed via source and target branches

to status/vote changes: