Merge lp:~kalikiana/ubuntu-ui-toolkit/appsettings into lp:ubuntu-ui-toolkit
- appsettings
- Merge into trunk
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 | ||||
Related bugs: |
|
||||
Related blueprints: |
SDK: Application Settings
(High)
Storage APIs in the Ubuntu SDK
(Undefined)
|
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
Description of the change
Cris Dywan (kalikiana) wrote : | # |
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:715
http://
Executed test runs:
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
UNSTABLE: http://
UNSTABLE: http://
Click here to trigger a rebuild:
http://
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?
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:720
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
Cris Dywan (kalikiana) wrote : | # |
Meh…
+++ components.api.new 2013-11-07 14:57:07.914501464 +0000
@@ -36,6 +36,7 @@ modules/
modules/
AbstractButton
property bool checked
+ function trigger()
modules/
Item
property url source
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?
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).
Zsombor Egri (zsombi) wrote : | # |
753 + Component.
What if children get changed after this point? Do we exclude them from state saving?
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?
Zsombor Egri (zsombi) wrote : | # |
Please close each JS line with semicolon.
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.
> 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.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:725
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
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/
>
> > 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.
> > 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.
Cris Dywan (kalikiana) wrote : | # |
> A use case is when you define different profiles for instance. Assume you have a host/port/
> 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.
Zsombor Egri (zsombi) wrote : | # |
> > A use case is when you define different profiles for instance. Assume you
> have a host/port/
> > 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.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:728
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:730
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
Cris Dywan (kalikiana) wrote : | # |
FAIL! : tst_MainView:
Loc: [tst_mainview.
- 731. By Cris Dywan
-
Merge lp:ubuntu-ui-toolkit
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:731
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 732. By Cris Dywan
-
Merge lp:ubuntu-ui-toolkit
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:732
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 733. By Cris Dywan
-
Merge lp:ubuntu-ui-toolkit
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:733
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
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.
Zsombor Egri (zsombi) wrote : | # |
915 +//#include <QtQuick/
916 +//#include <QtQuick/
Please remove these two lines.
- 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
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.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:739
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 740. By Cris Dywan
-
More refinements and more sanity checks in unit test
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:740
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 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
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
- 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
- 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
- 771. By Cris Dywan
-
Move Settings and Option to 1.1
- 772. By Cris Dywan
- 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
Florian Boucault (fboucault) wrote : | # |
Unless there is a solid reason against it, being API compatible with http://
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.
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:/
> Your team Ubuntu SDK team is subscribed to branch lp:ubuntu-ui-toolkit.
>
Unmerged revisions
- 777. By Cris Dywan
- 776. By Cris Dywan
- 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
- 771. By Cris Dywan
-
Move Settings and Option to 1.1
- 770. By Cris Dywan
- 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
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(©)); |
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"); |
TODO: Add document on how to use Settings API