Merge lp:~elopio/ubuntu-ui-toolkit/reorg_autopilot_helpers into lp:ubuntu-ui-toolkit
- reorg_autopilot_helpers
- Merge into trunk
Status: | Superseded |
---|---|
Proposed branch: | lp:~elopio/ubuntu-ui-toolkit/reorg_autopilot_helpers |
Merge into: | lp:ubuntu-ui-toolkit |
Prerequisite: | lp:~elopio/ubuntu-ui-toolkit/flake8 |
Diff against target: |
7625 lines (+3882/-2405) 54 files modified
components.api (+10/-0) debian/control (+4/-0) modules/Ubuntu/Components/plugin/adapters/alarmsadapter_organizer.cpp (+61/-24) modules/Ubuntu/Components/plugin/adapters/alarmsadapter_p.h (+2/-0) modules/Ubuntu/Components/plugin/alarmmanager_p.h (+10/-0) modules/Ubuntu/Components/plugin/plugin.cpp (+3/-5) modules/Ubuntu/Components/plugin/ucalarm.cpp (+33/-27) modules/Ubuntu/Components/plugin/ucalarm_p.h (+1/-1) modules/Ubuntu/Components/plugin/ucmouse.h (+52/-11) modules/Ubuntu/Components/plugin/ucmousefilters.cpp (+220/-84) 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/__init__.py (+0/-17) tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/__init__.py (+66/-781) tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_checkbox.py (+65/-0) tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_common.py (+69/-0) tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_flickable.py (+150/-0) tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_header.py (+57/-0) tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_listitems.py (+114/-0) tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_mainview.py (+162/-0) tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_popups.py (+82/-0) tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_qquicklistview.py (+65/-0) tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_tabbar.py (+68/-0) tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_tabs.py (+41/-0) tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_textfield.py (+88/-0) tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_toolbar.py (+106/-0) tests/autopilot/ubuntuuitoolkit/emulators.py (+85/-0) tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_checkbox.py (+136/-0) tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_common.py (+68/-0) tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_header.py (+41/-0) tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_listitems.py (+170/-0) tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_main_view.py (+132/-0) tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_popups.py (+172/-0) tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_qquicklistview.py (+193/-0) tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_tabs.py (+149/-0) tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_textfield.py (+93/-0) tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_toolbar.py (+121/-0) tests/autopilot/ubuntuuitoolkit/tests/test_emulators.py (+0/-1081) tests/resources/alarm/Alarms.qml (+6/-2) tests/unit/runtest.sh (+1/-2) tests/unit/tst_alarms/tst_alarms.cpp (+204/-21) tests/unit/tst_mainview/tst_mainview.cpp (+3/-6) tests/unit/tst_page/tst_page.cpp (+3/-39) tests/unit_x11/tst_mousefilters/ForwardComposedEvents.qml (+55/-0) tests/unit_x11/tst_mousefilters/ForwardEventChained.qml (+51/-0) tests/unit_x11/tst_mousefilters/HoverEvent.qml (+38/-0) tests/unit_x11/tst_mousefilters/tst_mousefilters.pro (+4/-1) tests/unit_x11/tst_mousefilters/tst_mousefilterstest.cpp (+396/-201) 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:~elopio/ubuntu-ui-toolkit/reorg_autopilot_helpers |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot | continuous-integration | Needs Fixing | |
Cris Dywan | Approve | ||
Review via email:
|
This proposal supersedes a proposal from 2014-04-11.
This proposal has been superseded by a proposal from 2014-04-17.
Commit message
Deprecated the ubuntuuitoolkit
Description of the change
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1013
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1014
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1017
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Cris Dywan (kalikiana) wrote : | # |
Makes sense. I presume we need to wait on having this altered in the Jenkins configuration since it'll always fail otherwise.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1018
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1019
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 1020. By Leo Arias
-
Split the tests.
- 1021. By Leo Arias
-
Use __all__.
- 1022. By Leo Arias
-
Fixed flake8.
- 1023. By Leo Arias
-
Rename the rest of emulators.
- 1024. By Leo Arias
-
Merged with trunk.
- 1025. By Leo Arias
-
Merged with staging.
- 1026. By Leo Arias
-
Added the missing option selector files.
- 1027. By Leo Arias
-
Flake8 is now not prerequisite.
- 1028. By Leo Arias
-
Flake8 is now not prerequisite.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1024
http://
Executed test runs:
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
- 1029. By Leo Arias
-
Use a reload compatible with py3
. - 1030. By Leo Arias
-
Expose the rest of the modules.
- 1031. By Leo Arias
-
Fixed the new circular dep.
- 1032. By Leo Arias
-
Fixed the new circular dep.
- 1033. By Leo Arias
-
Fixed the new circular dep.
- 1034. By Leo Arias
-
Merged with 30-optIn-
tabsDrawer. - 1035. By Leo Arias
-
Merged with 40-back-in-header.
- 1036. By Leo Arias
-
Reverted the change on the control.
- 1037. By Leo Arias
-
Reverted the change in debina/rules.
- 1038. By Leo Arias
-
Merged with staging.
- 1039. By Leo Arias
-
Merged with staging.
- 1040. By Leo Arias
-
Changed the namespace for listitems and popups.
- 1041. By Leo Arias
-
Merged with staging.
- 1042. By Leo Arias
-
Fixed the popups import.
Unmerged revisions
Preview Diff
1 | === modified file 'components.api' | |||
2 | --- components.api 2014-04-01 12:57:27 +0000 | |||
3 | +++ components.api 2014-04-17 01:29:24 +0000 | |||
4 | @@ -593,6 +593,8 @@ | |||
5 | 593 | function findChild(obj,objectName) | 593 | function findChild(obj,objectName) |
6 | 594 | function findInvisibleChild(obj,objectName) | 594 | function findInvisibleChild(obj,objectName) |
7 | 595 | function mouseMoveSlowly(item,x,y,dx,dy,steps,stepdelay) | 595 | function mouseMoveSlowly(item,x,y,dx,dy,steps,stepdelay) |
8 | 596 | function flick(item, x, y, dx, dy, pressTimeout, steps, button, modifiers, delay) | ||
9 | 597 | function mouseLongPress(item, x, y, button, modifiers, delay) | ||
10 | 596 | function tryCompareFunction(func, expectedResult, timeout) | 598 | function tryCompareFunction(func, expectedResult, timeout) |
11 | 597 | plugins.qmltypes | 599 | plugins.qmltypes |
12 | 598 | name: "InverseMouseAreaType" | 600 | name: "InverseMouseAreaType" |
13 | @@ -687,27 +689,35 @@ | |||
14 | 687 | Signal { | 689 | Signal { |
15 | 688 | name: "pressed" | 690 | name: "pressed" |
16 | 689 | Parameter { name: "mouse"; type: "QQuickMouseEvent"; isPointer: true } | 691 | Parameter { name: "mouse"; type: "QQuickMouseEvent"; isPointer: true } |
17 | 692 | Parameter { name: "host"; type: "QQuickItem"; isPointer: true } | ||
18 | 690 | Signal { | 693 | Signal { |
19 | 691 | name: "released" | 694 | name: "released" |
20 | 692 | Parameter { name: "mouse"; type: "QQuickMouseEvent"; isPointer: true } | 695 | Parameter { name: "mouse"; type: "QQuickMouseEvent"; isPointer: true } |
21 | 696 | Parameter { name: "host"; type: "QQuickItem"; isPointer: true } | ||
22 | 693 | Signal { | 697 | Signal { |
23 | 694 | name: "clicked" | 698 | name: "clicked" |
24 | 695 | Parameter { name: "mouse"; type: "QQuickMouseEvent"; isPointer: true } | 699 | Parameter { name: "mouse"; type: "QQuickMouseEvent"; isPointer: true } |
25 | 700 | Parameter { name: "host"; type: "QQuickItem"; isPointer: true } | ||
26 | 696 | Signal { | 701 | Signal { |
27 | 697 | name: "pressAndHold" | 702 | name: "pressAndHold" |
28 | 698 | Parameter { name: "mouse"; type: "QQuickMouseEvent"; isPointer: true } | 703 | Parameter { name: "mouse"; type: "QQuickMouseEvent"; isPointer: true } |
29 | 704 | Parameter { name: "host"; type: "QQuickItem"; isPointer: true } | ||
30 | 699 | Signal { | 705 | Signal { |
31 | 700 | name: "doubleClicked" | 706 | name: "doubleClicked" |
32 | 701 | Parameter { name: "mouse"; type: "QQuickMouseEvent"; isPointer: true } | 707 | Parameter { name: "mouse"; type: "QQuickMouseEvent"; isPointer: true } |
33 | 708 | Parameter { name: "host"; type: "QQuickItem"; isPointer: true } | ||
34 | 702 | Signal { | 709 | Signal { |
35 | 703 | name: "positionChanged" | 710 | name: "positionChanged" |
36 | 704 | Parameter { name: "mouse"; type: "QQuickMouseEvent"; isPointer: true } | 711 | Parameter { name: "mouse"; type: "QQuickMouseEvent"; isPointer: true } |
37 | 712 | Parameter { name: "host"; type: "QQuickItem"; isPointer: true } | ||
38 | 705 | Signal { | 713 | Signal { |
39 | 706 | name: "entered" | 714 | name: "entered" |
40 | 707 | Parameter { name: "event"; type: "QQuickMouseEvent"; isPointer: true } | 715 | Parameter { name: "event"; type: "QQuickMouseEvent"; isPointer: true } |
41 | 716 | Parameter { name: "host"; type: "QQuickItem"; isPointer: true } | ||
42 | 708 | Signal { | 717 | Signal { |
43 | 709 | name: "exited" | 718 | name: "exited" |
44 | 710 | Parameter { name: "event"; type: "QQuickMouseEvent"; isPointer: true } | 719 | Parameter { name: "event"; type: "QQuickMouseEvent"; isPointer: true } |
45 | 720 | Parameter { name: "host"; type: "QQuickItem"; isPointer: true } | ||
46 | 711 | name: "UCQQuickImageExtension" | 721 | name: "UCQQuickImageExtension" |
47 | 712 | prototype: "QQuickImageBase" | 722 | prototype: "QQuickImageBase" |
48 | 713 | exports: ["QQuickImageBase 0.1"] | 723 | exports: ["QQuickImageBase 0.1"] |
49 | 714 | 724 | ||
50 | === modified file 'debian/control' | |||
51 | --- debian/control 2014-04-17 01:29:24 +0000 | |||
52 | +++ debian/control 2014-04-17 01:29:24 +0000 | |||
53 | @@ -135,7 +135,11 @@ | |||
54 | 135 | python-autopilot (>= 1.4), | 135 | python-autopilot (>= 1.4), |
55 | 136 | python-fixtures, | 136 | python-fixtures, |
56 | 137 | python-mock, | 137 | python-mock, |
57 | 138 | python-testscenarios, | ||
58 | 139 | python-testtools, | ||
59 | 138 | python3-fixtures, | 140 | python3-fixtures, |
60 | 141 | python3-testscenarios, | ||
61 | 142 | python3-testtools, | ||
62 | 139 | python3-autopilot (>= 1.4), | 143 | python3-autopilot (>= 1.4), |
63 | 140 | ubuntu-ui-toolkit-examples (>= ${source:Version}), | 144 | ubuntu-ui-toolkit-examples (>= ${source:Version}), |
64 | 141 | Description: Test package for Ubuntu UI Toolkit | 145 | Description: Test package for Ubuntu UI Toolkit |
65 | 142 | 146 | ||
66 | === modified file 'modules/Ubuntu/Components/plugin/adapters/alarmsadapter_organizer.cpp' | |||
67 | --- modules/Ubuntu/Components/plugin/adapters/alarmsadapter_organizer.cpp 2014-02-21 20:02:29 +0000 | |||
68 | +++ modules/Ubuntu/Components/plugin/adapters/alarmsadapter_organizer.cpp 2014-04-17 01:29:24 +0000 | |||
69 | @@ -21,6 +21,7 @@ | |||
70 | 21 | #include "alarmmanager_p.h" | 21 | #include "alarmmanager_p.h" |
71 | 22 | #include "alarmrequest_p.h" | 22 | #include "alarmrequest_p.h" |
72 | 23 | #include "alarmsadapter_p.h" | 23 | #include "alarmsadapter_p.h" |
73 | 24 | #include <qorganizertodooccurrence.h> | ||
74 | 24 | 25 | ||
75 | 25 | #include <QtCore/QFile> | 26 | #include <QtCore/QFile> |
76 | 26 | #include <QtCore/QDir> | 27 | #include <QtCore/QDir> |
77 | @@ -55,11 +56,15 @@ | |||
78 | 55 | , manager(0) | 56 | , manager(0) |
79 | 56 | , fetchRequest(0) | 57 | , fetchRequest(0) |
80 | 57 | { | 58 | { |
84 | 58 | QOrganizerManager local; | 59 | QString envManager(qgetenv("ALARM_BACKEND")); |
85 | 59 | bool usingDefaultManager = local.availableManagers().contains(ALARM_MANAGER); | 60 | if (!envManager.isEmpty() && QOrganizerManager::availableManagers().contains(envManager)) { |
86 | 60 | manager = (usingDefaultManager) ? new QOrganizerManager(ALARM_MANAGER) : new QOrganizerManager(ALARM_MANAGER_FALLBACK); | 61 | manager = new QOrganizerManager(envManager); |
87 | 62 | } else { | ||
88 | 63 | manager = QOrganizerManager::availableManagers().contains(ALARM_MANAGER) ? | ||
89 | 64 | new QOrganizerManager(ALARM_MANAGER) : new QOrganizerManager(ALARM_MANAGER_FALLBACK); | ||
90 | 65 | } | ||
91 | 61 | manager->setParent(q_ptr); | 66 | manager->setParent(q_ptr); |
93 | 62 | if (!usingDefaultManager) { | 67 | if (manager->managerName() != ALARM_MANAGER) { |
94 | 63 | qWarning() << "WARNING: default alarm manager not installed, using" << manager->managerName() << "manager."; | 68 | qWarning() << "WARNING: default alarm manager not installed, using" << manager->managerName() << "manager."; |
95 | 64 | qWarning() << "This manager may not provide all the needed features."; | 69 | qWarning() << "This manager may not provide all the needed features."; |
96 | 65 | } | 70 | } |
97 | @@ -114,6 +119,7 @@ | |||
98 | 114 | 119 | ||
99 | 115 | int type, days; | 120 | int type, days; |
100 | 116 | in >> alarm.message >> alarm.date >> alarm.sound >> type >> days >> alarm.enabled; | 121 | in >> alarm.message >> alarm.date >> alarm.sound >> type >> days >> alarm.enabled; |
101 | 122 | alarm.originalDate = alarm.date = AlarmData::transcodeDate(alarm.date, Qt::LocalTime); | ||
102 | 117 | alarm.type = static_cast<UCAlarm::AlarmType>(type); | 123 | alarm.type = static_cast<UCAlarm::AlarmType>(type); |
103 | 118 | alarm.days = static_cast<UCAlarm::DaysOfWeek>(days); | 124 | alarm.days = static_cast<UCAlarm::DaysOfWeek>(days); |
104 | 119 | 125 | ||
105 | @@ -142,7 +148,7 @@ | |||
106 | 142 | 148 | ||
107 | 143 | Q_FOREACH(const AlarmData &alarm, alarmList) { | 149 | Q_FOREACH(const AlarmData &alarm, alarmList) { |
108 | 144 | out << alarm.message | 150 | out << alarm.message |
110 | 145 | << alarm.date | 151 | << AlarmData::transcodeDate(alarm.originalDate, Qt::UTC) |
111 | 146 | << alarm.sound | 152 | << alarm.sound |
112 | 147 | << alarm.type | 153 | << alarm.type |
113 | 148 | << alarm.days | 154 | << alarm.days |
114 | @@ -156,8 +162,7 @@ | |||
115 | 156 | { | 162 | { |
116 | 157 | event.setCollectionId(collection.id()); | 163 | event.setCollectionId(collection.id()); |
117 | 158 | event.setAllDay(false); | 164 | event.setAllDay(false); |
120 | 159 | event.setStartDateTime(alarm.date); | 165 | event.setStartDateTime(AlarmData::transcodeDate(alarm.date, Qt::UTC)); |
119 | 160 | event.setDueDateTime(alarm.date); | ||
121 | 161 | event.setDisplayLabel(alarm.message); | 166 | event.setDisplayLabel(alarm.message); |
122 | 162 | 167 | ||
123 | 163 | if (alarm.enabled) { | 168 | if (alarm.enabled) { |
124 | @@ -224,8 +229,9 @@ | |||
125 | 224 | 229 | ||
126 | 225 | alarm.cookie = QVariant::fromValue<QOrganizerItemId>(event.id()); | 230 | alarm.cookie = QVariant::fromValue<QOrganizerItemId>(event.id()); |
127 | 226 | alarm.message = event.displayLabel(); | 231 | alarm.message = event.displayLabel(); |
129 | 227 | alarm.date = AlarmData::normalizeDate(event.dueDateTime()); | 232 | alarm.date = AlarmData::transcodeDate(event.startDateTime().toUTC(), Qt::LocalTime); |
130 | 228 | alarm.sound = QUrl(event.description()); | 233 | alarm.sound = QUrl(event.description()); |
131 | 234 | alarm.originalDate = alarm.date; | ||
132 | 229 | 235 | ||
133 | 230 | // check if the alarm is enabled or not | 236 | // check if the alarm is enabled or not |
134 | 231 | QOrganizerItemVisualReminder visual = event.detail(QOrganizerItemDetail::TypeVisualReminder); | 237 | QOrganizerItemVisualReminder visual = event.detail(QOrganizerItemDetail::TypeVisualReminder); |
135 | @@ -303,8 +309,8 @@ | |||
136 | 303 | Q_FOREACH(const QOrganizerItem &item, alarms) { | 309 | Q_FOREACH(const QOrganizerItem &item, alarms) { |
137 | 304 | // repeating alarms may be fetched as occurences, therefore check their parent event | 310 | // repeating alarms may be fetched as occurences, therefore check their parent event |
138 | 305 | if (item.type() == QOrganizerItemType::TypeTodoOccurrence) { | 311 | if (item.type() == QOrganizerItemType::TypeTodoOccurrence) { |
141 | 306 | QOrganizerTodoOccurrence occurence = static_cast<QOrganizerTodoOccurrence>(item); | 312 | QOrganizerTodoOccurrence occurrence = static_cast<QOrganizerTodoOccurrence>(item); |
142 | 307 | QOrganizerItemId eventId = occurence.parentId(); | 313 | QOrganizerItemId eventId = occurrence.parentId(); |
143 | 308 | if (parentId.contains(eventId)) { | 314 | if (parentId.contains(eventId)) { |
144 | 309 | continue; | 315 | continue; |
145 | 310 | } | 316 | } |
146 | @@ -317,6 +323,7 @@ | |||
147 | 317 | } | 323 | } |
148 | 318 | AlarmData alarm; | 324 | AlarmData alarm; |
149 | 319 | if (alarmDataFromOrganizerEvent(event, alarm) == UCAlarm::NoError) { | 325 | if (alarmDataFromOrganizerEvent(event, alarm) == UCAlarm::NoError) { |
150 | 326 | adjustAlarmOccurrence(event, alarm); | ||
151 | 320 | alarmList << alarm; | 327 | alarmList << alarm; |
152 | 321 | } | 328 | } |
153 | 322 | } | 329 | } |
154 | @@ -324,9 +331,52 @@ | |||
155 | 324 | saveAlarms(); | 331 | saveAlarms(); |
156 | 325 | Q_EMIT q_ptr->alarmsChanged(); | 332 | Q_EMIT q_ptr->alarmsChanged(); |
157 | 326 | completed = true; | 333 | completed = true; |
158 | 334 | fetchRequest->deleteLater(); | ||
159 | 327 | fetchRequest = 0; | 335 | fetchRequest = 0; |
160 | 328 | } | 336 | } |
161 | 329 | 337 | ||
162 | 338 | void AlarmsAdapter::adjustAlarmOccurrence(const QOrganizerTodo &event, AlarmData &alarm) | ||
163 | 339 | { | ||
164 | 340 | if (alarm.type == UCAlarm::OneTime) { | ||
165 | 341 | return; | ||
166 | 342 | } | ||
167 | 343 | // with EDS we need to query the occurrences separately as the fetch reports only the main events | ||
168 | 344 | // with fallback manager this does not reduce the performance and does work the same way. | ||
169 | 345 | QDateTime currentDate = AlarmData::normalizeDate(QDateTime::currentDateTime()); | ||
170 | 346 | if (alarm.date > currentDate) { | ||
171 | 347 | // no need to adjust date, the event occurs in the future | ||
172 | 348 | return; | ||
173 | 349 | } | ||
174 | 350 | QDateTime startDate; | ||
175 | 351 | QDateTime endDate; | ||
176 | 352 | if (alarm.type == UCAlarm::Repeating) { | ||
177 | 353 | // 8 days is enough from the starting date (or current date depending on the start date) | ||
178 | 354 | startDate = (alarm.date > currentDate) ? alarm.date : currentDate; | ||
179 | 355 | endDate = startDate.addDays(8); | ||
180 | 356 | } | ||
181 | 357 | |||
182 | 358 | // transcode both dates | ||
183 | 359 | startDate = AlarmData::transcodeDate(startDate, Qt::UTC); | ||
184 | 360 | endDate = AlarmData::transcodeDate(endDate, Qt::UTC); | ||
185 | 361 | |||
186 | 362 | QList<QOrganizerItem> occurrences = manager->itemOccurrences(event, startDate, endDate, 10); | ||
187 | 363 | // get the first occurrence and use the date from it | ||
188 | 364 | if ((occurrences.length() > 0) && (occurrences[0].type() == QOrganizerItemType::TypeTodoOccurrence)) { | ||
189 | 365 | // loop till we get a proper future due date | ||
190 | 366 | for (int i = 0; i < occurrences.count(); i++) { | ||
191 | 367 | QOrganizerTodoOccurrence occurrence = static_cast<QOrganizerTodoOccurrence>(occurrences[i]); | ||
192 | 368 | // check if the date is after the current datetime | ||
193 | 369 | // the first occurrence is the one closest to the currentDate, therefore we can safely | ||
194 | 370 | // set that startDate to the alarm | ||
195 | 371 | alarm.date = AlarmData::transcodeDate(occurrence.startDateTime().toUTC(), Qt::LocalTime); | ||
196 | 372 | if (alarm.date > currentDate) { | ||
197 | 373 | // we have the proper date set, leave | ||
198 | 374 | break; | ||
199 | 375 | } | ||
200 | 376 | } | ||
201 | 377 | } | ||
202 | 378 | } | ||
203 | 379 | |||
204 | 330 | /*----------------------------------------------------------------------------- | 380 | /*----------------------------------------------------------------------------- |
205 | 331 | * AlarmRequestAdapter implementation | 381 | * AlarmRequestAdapter implementation |
206 | 332 | */ | 382 | */ |
207 | @@ -410,23 +460,10 @@ | |||
208 | 410 | QOrganizerItemFetchRequest *operation = new QOrganizerItemFetchRequest(q_ptr); | 460 | QOrganizerItemFetchRequest *operation = new QOrganizerItemFetchRequest(q_ptr); |
209 | 411 | operation->setManager(owner->manager); | 461 | operation->setManager(owner->manager); |
210 | 412 | 462 | ||
211 | 413 | // FIXME: Since returning all events without a limit of date is not a good solution we need to find | ||
212 | 414 | // a better solution for that. | ||
213 | 415 | // The current solution filters only the next 7 days (one week). | ||
214 | 416 | // This will be enough for now, since the current alarms occur weekly, but for the future | ||
215 | 417 | // we want to allow create alarms with monthly or yearly recurrence | ||
216 | 418 | QDate currentDate = QDate::currentDate(); | ||
217 | 419 | QDateTime startDate(currentDate, | ||
218 | 420 | QTime(0,0,0)); | ||
219 | 421 | QDateTime endDate(currentDate.addDays(7), | ||
220 | 422 | QTime(23,59,59)); | ||
221 | 423 | operation->setStartDate(startDate); | ||
222 | 424 | operation->setEndDate(endDate); | ||
223 | 425 | |||
224 | 426 | // set sort order | 463 | // set sort order |
225 | 427 | QOrganizerItemSortOrder sortOrder; | 464 | QOrganizerItemSortOrder sortOrder; |
226 | 428 | sortOrder.setDirection(Qt::AscendingOrder); | 465 | sortOrder.setDirection(Qt::AscendingOrder); |
228 | 429 | sortOrder.setDetail(QOrganizerItemDetail::TypeTodoTime, QOrganizerTodoTime::FieldDueDateTime); | 466 | sortOrder.setDetail(QOrganizerItemDetail::TypeTodoTime, QOrganizerTodoTime::FieldStartDateTime); |
229 | 430 | operation->setSorting(QList<QOrganizerItemSortOrder>() << sortOrder); | 467 | operation->setSorting(QList<QOrganizerItemSortOrder>() << sortOrder); |
230 | 431 | 468 | ||
231 | 432 | // set filter | 469 | // set filter |
232 | 433 | 470 | ||
233 | === modified file 'modules/Ubuntu/Components/plugin/adapters/alarmsadapter_p.h' | |||
234 | --- modules/Ubuntu/Components/plugin/adapters/alarmsadapter_p.h 2013-09-18 10:13:04 +0000 | |||
235 | +++ modules/Ubuntu/Components/plugin/adapters/alarmsadapter_p.h 2014-04-17 01:29:24 +0000 | |||
236 | @@ -24,6 +24,7 @@ | |||
237 | 24 | 24 | ||
238 | 25 | #include <qorganizer.h> | 25 | #include <qorganizer.h> |
239 | 26 | #include <qorganizermanager.h> | 26 | #include <qorganizermanager.h> |
240 | 27 | #include <qorganizertodo.h> | ||
241 | 27 | 28 | ||
242 | 28 | QTORGANIZER_USE_NAMESPACE | 29 | QTORGANIZER_USE_NAMESPACE |
243 | 29 | 30 | ||
244 | @@ -78,6 +79,7 @@ | |||
245 | 78 | QOrganizerCollection collection; | 79 | QOrganizerCollection collection; |
246 | 79 | 80 | ||
247 | 80 | void completeFetchAlarms(const QList<QOrganizerItem> &alarmList); | 81 | void completeFetchAlarms(const QList<QOrganizerItem> &alarmList); |
248 | 82 | void adjustAlarmOccurrence(const QOrganizerTodo &event, AlarmData &alarm); | ||
249 | 81 | 83 | ||
250 | 82 | void loadAlarms(); | 84 | void loadAlarms(); |
251 | 83 | void saveAlarms(); | 85 | void saveAlarms(); |
252 | 84 | 86 | ||
253 | === modified file 'modules/Ubuntu/Components/plugin/alarmmanager_p.h' | |||
254 | --- modules/Ubuntu/Components/plugin/alarmmanager_p.h 2013-09-12 05:27:40 +0000 | |||
255 | +++ modules/Ubuntu/Components/plugin/alarmmanager_p.h 2014-04-17 01:29:24 +0000 | |||
256 | @@ -48,6 +48,7 @@ | |||
257 | 48 | AlarmData(const AlarmData &other) | 48 | AlarmData(const AlarmData &other) |
258 | 49 | : changes(0) | 49 | : changes(0) |
259 | 50 | , cookie(other.cookie) | 50 | , cookie(other.cookie) |
260 | 51 | , originalDate(other.originalDate) | ||
261 | 51 | , date(other.date) | 52 | , date(other.date) |
262 | 52 | , message(other.message) | 53 | , message(other.message) |
263 | 53 | , type(other.type) | 54 | , type(other.type) |
264 | @@ -100,10 +101,19 @@ | |||
265 | 100 | return QDateTime(dt.date(), time, dt.timeSpec()); | 101 | return QDateTime(dt.date(), time, dt.timeSpec()); |
266 | 101 | } | 102 | } |
267 | 102 | 103 | ||
268 | 104 | // the function normalizes and transcodes the date into UTC/LocalTime equivalent | ||
269 | 105 | static QDateTime transcodeDate(const QDateTime &dt, Qt::TimeSpec targetSpec) { | ||
270 | 106 | if (dt.timeSpec() == targetSpec) { | ||
271 | 107 | return normalizeDate(dt); | ||
272 | 108 | } | ||
273 | 109 | return QDateTime(dt.date(), normalizeDate(dt).time(), targetSpec); | ||
274 | 110 | } | ||
275 | 111 | |||
276 | 103 | unsigned int changes; | 112 | unsigned int changes; |
277 | 104 | QVariant cookie; | 113 | QVariant cookie; |
278 | 105 | 114 | ||
279 | 106 | // data members | 115 | // data members |
280 | 116 | QDateTime originalDate; | ||
281 | 107 | QDateTime date; | 117 | QDateTime date; |
282 | 108 | QString message; | 118 | QString message; |
283 | 109 | QUrl sound; | 119 | QUrl sound; |
284 | 110 | 120 | ||
285 | === modified file 'modules/Ubuntu/Components/plugin/plugin.cpp' | |||
286 | --- modules/Ubuntu/Components/plugin/plugin.cpp 2014-03-05 12:29:58 +0000 | |||
287 | +++ modules/Ubuntu/Components/plugin/plugin.cpp 2014-04-17 01:29:24 +0000 | |||
288 | @@ -53,9 +53,6 @@ | |||
289 | 53 | #include <unistd.h> | 53 | #include <unistd.h> |
290 | 54 | #include <stdexcept> | 54 | #include <stdexcept> |
291 | 55 | 55 | ||
292 | 56 | // Needed for unit tests | ||
293 | 57 | Q_DECLARE_METATYPE(QList<QQmlError>) | ||
294 | 58 | |||
295 | 59 | /* | 56 | /* |
296 | 60 | * Type registration functions. | 57 | * Type registration functions. |
297 | 61 | */ | 58 | */ |
298 | @@ -177,10 +174,11 @@ | |||
299 | 177 | qmlRegisterSingletonType<UCUriHandler>(uri, 0, 1, "UriHandler", registerUriHandler); | 174 | qmlRegisterSingletonType<UCUriHandler>(uri, 0, 1, "UriHandler", registerUriHandler); |
300 | 178 | qmlRegisterType<UCMouse>(uri, 0, 1, "Mouse"); | 175 | qmlRegisterType<UCMouse>(uri, 0, 1, "Mouse"); |
301 | 179 | qmlRegisterType<UCInverseMouse>(uri, 0, 1, "InverseMouse"); | 176 | qmlRegisterType<UCInverseMouse>(uri, 0, 1, "InverseMouse"); |
302 | 180 | // Needed for unit tests | ||
303 | 181 | qRegisterMetaType<QList <QQmlError> >(); | ||
304 | 182 | // register QML singletons | 177 | // register QML singletons |
305 | 183 | qmlRegisterSingletonType<QObject>(uri, 0, 1, "PickerPanel", registerPickerPanel); | 178 | qmlRegisterSingletonType<QObject>(uri, 0, 1, "PickerPanel", registerPickerPanel); |
306 | 179 | |||
307 | 180 | // register custom event | ||
308 | 181 | ForwardedEvent::registerForwardedEvent(); | ||
309 | 184 | } | 182 | } |
310 | 185 | 183 | ||
311 | 186 | void UbuntuComponentsPlugin::initializeEngine(QQmlEngine *engine, const char *uri) | 184 | void UbuntuComponentsPlugin::initializeEngine(QQmlEngine *engine, const char *uri) |
312 | 187 | 185 | ||
313 | === modified file 'modules/Ubuntu/Components/plugin/ucalarm.cpp' | |||
314 | --- modules/Ubuntu/Components/plugin/ucalarm.cpp 2013-09-18 10:25:18 +0000 | |||
315 | +++ modules/Ubuntu/Components/plugin/ucalarm.cpp 2014-04-17 01:29:24 +0000 | |||
316 | @@ -102,15 +102,21 @@ | |||
317 | 102 | 102 | ||
318 | 103 | int UCAlarmPrivate::nextDayOfWeek(UCAlarm::DaysOfWeek days, int fromDay) | 103 | int UCAlarmPrivate::nextDayOfWeek(UCAlarm::DaysOfWeek days, int fromDay) |
319 | 104 | { | 104 | { |
321 | 105 | if (fromDay <= 0) { | 105 | if (fromDay <= 0 || fromDay >= Qt::Sunday) { |
322 | 106 | // start from the beginning of the week | ||
323 | 106 | fromDay = Qt::Monday; | 107 | fromDay = Qt::Monday; |
324 | 108 | } else { | ||
325 | 109 | // start checking from the next day onwards | ||
326 | 110 | fromDay++; | ||
327 | 107 | } | 111 | } |
328 | 108 | for (int d = fromDay; d <= Qt::Sunday; d++) { | 112 | for (int d = fromDay; d <= Qt::Sunday; d++) { |
329 | 109 | if ((1 << (d - 1)) & days) { | 113 | if ((1 << (d - 1)) & days) { |
330 | 110 | return d; | 114 | return d; |
331 | 111 | } | 115 | } |
332 | 112 | } | 116 | } |
334 | 113 | return 0; | 117 | |
335 | 118 | // none found for the rest of the week, return the fist day set | ||
336 | 119 | return firstDayOfWeek(days); | ||
337 | 114 | } | 120 | } |
338 | 115 | 121 | ||
339 | 116 | // checks whether the given num has more than one bit set | 122 | // checks whether the given num has more than one bit set |
340 | @@ -147,23 +153,14 @@ | |||
341 | 147 | return UCAlarm::NoError; | 153 | return UCAlarm::NoError; |
342 | 148 | } | 154 | } |
343 | 149 | 155 | ||
345 | 150 | UCAlarm::Error UCAlarmPrivate::checkDow() | 156 | // adjust dayOfWeek |
346 | 157 | UCAlarm::Error UCAlarmPrivate::adjustDow() | ||
347 | 151 | { | 158 | { |
348 | 152 | if (!rawData.days) { | 159 | if (!rawData.days) { |
349 | 153 | return UCAlarm::NoDaysOfWeek; | 160 | return UCAlarm::NoDaysOfWeek; |
350 | 154 | } else if (rawData.days == UCAlarm::AutoDetect) { | 161 | } else if (rawData.days == UCAlarm::AutoDetect) { |
351 | 155 | rawData.days = dayOfWeek(rawData.date); | 162 | rawData.days = dayOfWeek(rawData.date); |
352 | 156 | rawData.changes |= AlarmData::Days; | 163 | rawData.changes |= AlarmData::Days; |
353 | 157 | } else if (rawData.days != UCAlarm::Daily) { | ||
354 | 158 | int alarmDay = firstDayOfWeek(rawData.days); | ||
355 | 159 | int dayOfWeek = rawData.date.date().dayOfWeek(); | ||
356 | 160 | if (alarmDay < dayOfWeek) { | ||
357 | 161 | rawData.date = rawData.date.addDays(7 - dayOfWeek + alarmDay); | ||
358 | 162 | rawData.changes |= AlarmData::Date; | ||
359 | 163 | } else if (alarmDay > dayOfWeek) { | ||
360 | 164 | rawData.date = rawData.date.addDays(alarmDay - dayOfWeek); | ||
361 | 165 | rawData.changes |= AlarmData::Date; | ||
362 | 166 | } | ||
363 | 167 | } | 164 | } |
364 | 168 | return UCAlarm::NoError; | 165 | return UCAlarm::NoError; |
365 | 169 | } | 166 | } |
366 | @@ -175,8 +172,7 @@ | |||
367 | 175 | return UCAlarm::OneTimeOnMoreDays; | 172 | return UCAlarm::OneTimeOnMoreDays; |
368 | 176 | } | 173 | } |
369 | 177 | 174 | ||
372 | 178 | // adjust start date and/or dayOfWeek according to their values | 175 | UCAlarm::Error result = adjustDow(); |
371 | 179 | UCAlarm::Error result = checkDow(); | ||
373 | 180 | if (result != UCAlarm::NoError) { | 176 | if (result != UCAlarm::NoError) { |
374 | 181 | return result; | 177 | return result; |
375 | 182 | } | 178 | } |
376 | @@ -193,23 +189,25 @@ | |||
377 | 193 | // start date is adjusted depending on the days value; | 189 | // start date is adjusted depending on the days value; |
378 | 194 | // start date can be set to be the current time, as scheduling will move | 190 | // start date can be set to be the current time, as scheduling will move |
379 | 195 | // it to the first occurence. | 191 | // it to the first occurence. |
381 | 196 | UCAlarm::Error result = checkDow(); | 192 | UCAlarm::Error result = adjustDow(); |
382 | 197 | if (result != UCAlarm::NoError) { | 193 | if (result != UCAlarm::NoError) { |
383 | 198 | return result; | 194 | return result; |
384 | 199 | } | 195 | } |
385 | 200 | 196 | ||
387 | 201 | // move start time to the first occurence if needed | 197 | // move start time of the first occurence if needed |
388 | 202 | int dayOfWeek = rawData.date.date().dayOfWeek(); | 198 | int dayOfWeek = rawData.date.date().dayOfWeek(); |
389 | 203 | if (!isDaySet(dayOfWeek, rawData.days) || (rawData.date <= QDateTime::currentDateTime())) { | 199 | if (!isDaySet(dayOfWeek, rawData.days) || (rawData.date <= QDateTime::currentDateTime())) { |
390 | 204 | // check the next occurence of the alarm | 200 | // check the next occurence of the alarm |
396 | 205 | int nextOccurence = nextDayOfWeek(rawData.days, dayOfWeek); | 201 | int nextOccurrence = nextDayOfWeek(rawData.days, dayOfWeek); |
397 | 206 | if (nextOccurence <= 0) { | 202 | if (nextOccurrence == dayOfWeek) { |
398 | 207 | // the starting date should be moved to the next week | 203 | // move the date to the same day next week |
399 | 208 | nextOccurence = firstDayOfWeek(rawData.days); | 204 | rawData.date = rawData.date.addDays(7); |
400 | 209 | rawData.date.addDays(7 - dayOfWeek + nextOccurence); | 205 | } else if (nextOccurrence < dayOfWeek) { |
401 | 206 | // the starting date should be moved to the next week's occurrence | ||
402 | 207 | rawData.date = rawData.date.addDays(7 - dayOfWeek + nextOccurrence); | ||
403 | 210 | } else { | 208 | } else { |
404 | 211 | // the starting date is still this week | 209 | // the starting date is still this week |
406 | 212 | rawData.date.addDays(nextOccurence - dayOfWeek); | 210 | rawData.date = rawData.date.addDays(nextOccurrence - dayOfWeek); |
407 | 213 | } | 211 | } |
408 | 214 | rawData.changes |= AlarmData::Date; | 212 | rawData.changes |= AlarmData::Date; |
409 | 215 | } | 213 | } |
410 | @@ -304,7 +302,7 @@ | |||
411 | 304 | : QObject(parent) | 302 | : QObject(parent) |
412 | 305 | , d_ptr(new UCAlarmPrivate(this)) | 303 | , d_ptr(new UCAlarmPrivate(this)) |
413 | 306 | { | 304 | { |
415 | 307 | d_ptr->rawData.date = dt; | 305 | d_ptr->rawData.date = AlarmData::normalizeDate(dt); |
416 | 308 | if (!message.isEmpty()) { | 306 | if (!message.isEmpty()) { |
417 | 309 | d_ptr->rawData.message = message; | 307 | d_ptr->rawData.message = message; |
418 | 310 | } | 308 | } |
419 | @@ -315,7 +313,7 @@ | |||
420 | 315 | : QObject(parent) | 313 | : QObject(parent) |
421 | 316 | , d_ptr(new UCAlarmPrivate(this)) | 314 | , d_ptr(new UCAlarmPrivate(this)) |
422 | 317 | { | 315 | { |
424 | 318 | d_ptr->rawData.date = dt; | 316 | d_ptr->rawData.date = AlarmData::normalizeDate(dt); |
425 | 319 | d_ptr->rawData.type = Repeating; | 317 | d_ptr->rawData.type = Repeating; |
426 | 320 | d_ptr->rawData.days = days; | 318 | d_ptr->rawData.days = days; |
427 | 321 | if (!message.isEmpty()) { | 319 | if (!message.isEmpty()) { |
428 | @@ -366,12 +364,16 @@ | |||
429 | 366 | void UCAlarm::setDate(const QDateTime &date) | 364 | void UCAlarm::setDate(const QDateTime &date) |
430 | 367 | { | 365 | { |
431 | 368 | Q_D(UCAlarm); | 366 | Q_D(UCAlarm); |
433 | 369 | if (d->rawData.date == date) { | 367 | if (d->rawData.date == AlarmData::normalizeDate(date)) { |
434 | 370 | return; | 368 | return; |
435 | 371 | } | 369 | } |
437 | 372 | d->rawData.date = date; | 370 | d->rawData.date = AlarmData::normalizeDate(date); |
438 | 373 | d->rawData.changes |= AlarmData::Date; | 371 | d->rawData.changes |= AlarmData::Date; |
439 | 374 | Q_EMIT dateChanged(); | 372 | Q_EMIT dateChanged(); |
440 | 373 | if (d->rawData.type == UCAlarm::OneTime) { | ||
441 | 374 | // adjust dayOfWeek as well | ||
442 | 375 | setDaysOfWeek(UCAlarm::AutoDetect); | ||
443 | 376 | } | ||
444 | 375 | } | 377 | } |
445 | 376 | 378 | ||
446 | 377 | /*! | 379 | /*! |
447 | @@ -520,6 +522,8 @@ | |||
448 | 520 | Q_EMIT soundChanged(); | 522 | Q_EMIT soundChanged(); |
449 | 521 | } | 523 | } |
450 | 522 | 524 | ||
451 | 525 | |||
452 | 526 | |||
453 | 523 | /*! | 527 | /*! |
454 | 524 | * \qmlproperty Error Alarm::error | 528 | * \qmlproperty Error Alarm::error |
455 | 525 | * The property holds the error code occurred during the last performed operation. | 529 | * The property holds the error code occurred during the last performed operation. |
456 | @@ -670,6 +674,8 @@ | |||
457 | 670 | if (result != UCAlarm::NoError) { | 674 | if (result != UCAlarm::NoError) { |
458 | 671 | d->_q_syncStatus(Saving, Fail, result); | 675 | d->_q_syncStatus(Saving, Fail, result); |
459 | 672 | } else { | 676 | } else { |
460 | 677 | // the alarm has been modified, therefore update the original date as well | ||
461 | 678 | d->rawData.originalDate = d->rawData.date; | ||
462 | 673 | if (d->createRequest()) { | 679 | if (d->createRequest()) { |
463 | 674 | d->request->save(d->rawData); | 680 | d->request->save(d->rawData); |
464 | 675 | } | 681 | } |
465 | 676 | 682 | ||
466 | === modified file 'modules/Ubuntu/Components/plugin/ucalarm_p.h' | |||
467 | --- modules/Ubuntu/Components/plugin/ucalarm_p.h 2013-09-18 10:25:18 +0000 | |||
468 | +++ modules/Ubuntu/Components/plugin/ucalarm_p.h 2014-04-17 01:29:24 +0000 | |||
469 | @@ -50,7 +50,7 @@ | |||
470 | 50 | static int nextDayOfWeek(UCAlarm::DaysOfWeek days, int fromDay); | 50 | static int nextDayOfWeek(UCAlarm::DaysOfWeek days, int fromDay); |
471 | 51 | static bool multipleDaysSet(UCAlarm::DaysOfWeek days); | 51 | static bool multipleDaysSet(UCAlarm::DaysOfWeek days); |
472 | 52 | UCAlarm::Error checkAlarm(); | 52 | UCAlarm::Error checkAlarm(); |
474 | 53 | UCAlarm::Error checkDow(); | 53 | UCAlarm::Error adjustDow(); |
475 | 54 | UCAlarm::Error checkOneTime(); | 54 | UCAlarm::Error checkOneTime(); |
476 | 55 | UCAlarm::Error checkRepeatingWeekly(); | 55 | UCAlarm::Error checkRepeatingWeekly(); |
477 | 56 | 56 | ||
478 | 57 | 57 | ||
479 | === modified file 'modules/Ubuntu/Components/plugin/ucmouse.h' | |||
480 | --- modules/Ubuntu/Components/plugin/ucmouse.h 2014-02-13 17:16:06 +0000 | |||
481 | +++ modules/Ubuntu/Components/plugin/ucmouse.h 2014-04-17 01:29:24 +0000 | |||
482 | @@ -20,10 +20,47 @@ | |||
483 | 20 | 20 | ||
484 | 21 | #include <QtCore/QObject> | 21 | #include <QtCore/QObject> |
485 | 22 | #include <QtQml> | 22 | #include <QtQml> |
486 | 23 | #include <QtQuick/QQuickItem> | ||
487 | 23 | #include <private/qquickevents_p_p.h> | 24 | #include <private/qquickevents_p_p.h> |
488 | 24 | #include <QtCore/qbasictimer.h> | 25 | #include <QtCore/qbasictimer.h> |
489 | 25 | 26 | ||
491 | 26 | class QQuickItem; | 27 | class ForwardedEvent : public QEvent { |
492 | 28 | public: | ||
493 | 29 | enum EventType { | ||
494 | 30 | MousePress, | ||
495 | 31 | MouseRelease, | ||
496 | 32 | MouseMove, | ||
497 | 33 | MouseDblClick, | ||
498 | 34 | HoverEnter, | ||
499 | 35 | HoverExit, | ||
500 | 36 | MouseClick, | ||
501 | 37 | MouseLongPress, | ||
502 | 38 | }; | ||
503 | 39 | ForwardedEvent(EventType type, QQuickItem *sender, QEvent *originalEvent, QQuickMouseEvent *quickEvent) | ||
504 | 40 | : QEvent((QEvent::Type)m_eventBase) | ||
505 | 41 | , m_subType(type) | ||
506 | 42 | , m_sender(sender) | ||
507 | 43 | , m_originalEvent(originalEvent) | ||
508 | 44 | , m_quickEvent(quickEvent) | ||
509 | 45 | { | ||
510 | 46 | setAccepted(false); | ||
511 | 47 | } | ||
512 | 48 | |||
513 | 49 | static void registerForwardedEvent(); | ||
514 | 50 | |||
515 | 51 | EventType subType() const { return m_subType; } | ||
516 | 52 | QQuickItem *sender() const { return m_sender; } | ||
517 | 53 | QQuickMouseEvent *quickEvent() const { return m_quickEvent; } | ||
518 | 54 | QEvent *originalEvent() const { return m_originalEvent; } | ||
519 | 55 | static QEvent::Type baseType() { return m_eventBase; } | ||
520 | 56 | private: | ||
521 | 57 | EventType m_subType; | ||
522 | 58 | QPointer<QQuickItem> m_sender; | ||
523 | 59 | QEvent *m_originalEvent; | ||
524 | 60 | QPointer<QQuickMouseEvent> m_quickEvent; | ||
525 | 61 | static QEvent::Type m_eventBase; | ||
526 | 62 | }; | ||
527 | 63 | |||
528 | 27 | class UCMouse : public QObject | 64 | class UCMouse : public QObject |
529 | 28 | { | 65 | { |
530 | 29 | Q_OBJECT | 66 | Q_OBJECT |
531 | @@ -62,23 +99,24 @@ | |||
532 | 62 | void clickAndHoldThresholdChanged(); | 99 | void clickAndHoldThresholdChanged(); |
533 | 63 | void priorityChanged(); | 100 | void priorityChanged(); |
534 | 64 | 101 | ||
543 | 65 | void pressed(QQuickMouseEvent *mouse); | 102 | void pressed(QQuickMouseEvent *mouse, QQuickItem *host); |
544 | 66 | void released(QQuickMouseEvent *mouse); | 103 | void released(QQuickMouseEvent *mouse, QQuickItem *host); |
545 | 67 | void clicked(QQuickMouseEvent *mouse); | 104 | void clicked(QQuickMouseEvent *mouse, QQuickItem *host); |
546 | 68 | void pressAndHold(QQuickMouseEvent *mouse); | 105 | void pressAndHold(QQuickMouseEvent *mouse, QQuickItem *host); |
547 | 69 | void doubleClicked(QQuickMouseEvent *mouse); | 106 | void doubleClicked(QQuickMouseEvent *mouse, QQuickItem *host); |
548 | 70 | void positionChanged(QQuickMouseEvent *mouse); | 107 | void positionChanged(QQuickMouseEvent *mouse, QQuickItem *host); |
549 | 71 | void entered(QQuickMouseEvent *event); | 108 | void entered(QQuickMouseEvent *event, QQuickItem *host); |
550 | 72 | void exited(QQuickMouseEvent *event); | 109 | void exited(QQuickMouseEvent *event, QQuickItem *host); |
551 | 73 | 110 | ||
552 | 74 | protected: | 111 | protected: |
553 | 75 | virtual bool eventFilter(QObject *, QEvent *); | 112 | virtual bool eventFilter(QObject *, QEvent *); |
554 | 76 | virtual void timerEvent(QTimerEvent *event); | 113 | virtual void timerEvent(QTimerEvent *event); |
555 | 77 | virtual bool mouseEvents(QObject *target, QMouseEvent *event); | 114 | virtual bool mouseEvents(QObject *target, QMouseEvent *event); |
556 | 78 | virtual bool hoverEvents(QObject *target, QHoverEvent *event); | 115 | virtual bool hoverEvents(QObject *target, QHoverEvent *event); |
557 | 116 | virtual bool forwardedEvents(ForwardedEvent *event); | ||
558 | 79 | virtual bool hasAttachedFilter(QQuickItem *item); | 117 | virtual bool hasAttachedFilter(QQuickItem *item); |
559 | 80 | 118 | ||
561 | 81 | void setHovered(bool hovered); | 119 | void setHovered(bool hovered, QEvent *hoverEvent); |
562 | 82 | bool mousePressed(QMouseEvent *event); | 120 | bool mousePressed(QMouseEvent *event); |
563 | 83 | bool mouseReleased(QMouseEvent *event); | 121 | bool mouseReleased(QMouseEvent *event); |
564 | 84 | bool mouseDblClick(QMouseEvent *event); | 122 | bool mouseDblClick(QMouseEvent *event); |
565 | @@ -91,7 +129,7 @@ | |||
566 | 91 | bool isDoubleClickConnected(); | 129 | bool isDoubleClickConnected(); |
567 | 92 | bool isMouseEvent(QEvent::Type type); | 130 | bool isMouseEvent(QEvent::Type type); |
568 | 93 | bool isHoverEvent(QEvent::Type type); | 131 | bool isHoverEvent(QEvent::Type type); |
570 | 94 | void forwardEvent(QEvent *event); | 132 | bool forwardEvent(ForwardedEvent::EventType type, QEvent *event, QQuickMouseEvent *quickEvent); |
571 | 95 | 133 | ||
572 | 96 | protected: | 134 | protected: |
573 | 97 | QQuickItem *m_owner; | 135 | QQuickItem *m_owner; |
574 | @@ -107,6 +145,7 @@ | |||
575 | 107 | Priority m_priority; | 145 | Priority m_priority; |
576 | 108 | int m_moveThreshold; | 146 | int m_moveThreshold; |
577 | 109 | 147 | ||
578 | 148 | bool m_signalWhenContains:1; | ||
579 | 110 | bool m_enabled: 1; | 149 | bool m_enabled: 1; |
580 | 111 | bool m_moved:1; | 150 | bool m_moved:1; |
581 | 112 | bool m_longPress:1; | 151 | bool m_longPress:1; |
582 | @@ -115,4 +154,6 @@ | |||
583 | 115 | }; | 154 | }; |
584 | 116 | QML_DECLARE_TYPEINFO(UCMouse, QML_HAS_ATTACHED_PROPERTIES) | 155 | QML_DECLARE_TYPEINFO(UCMouse, QML_HAS_ATTACHED_PROPERTIES) |
585 | 117 | 156 | ||
586 | 157 | extern const int DefaultPressAndHoldDelay; | ||
587 | 158 | |||
588 | 118 | #endif // UCMOUSE_H | 159 | #endif // UCMOUSE_H |
589 | 119 | 160 | ||
590 | === modified file 'modules/Ubuntu/Components/plugin/ucmousefilters.cpp' | |||
591 | --- modules/Ubuntu/Components/plugin/ucmousefilters.cpp 2014-02-13 17:16:06 +0000 | |||
592 | +++ modules/Ubuntu/Components/plugin/ucmousefilters.cpp 2014-04-17 01:29:24 +0000 | |||
593 | @@ -18,7 +18,6 @@ | |||
594 | 18 | #include "ucmouse.h" | 18 | #include "ucmouse.h" |
595 | 19 | #include "ucinversemouse.h" | 19 | #include "ucinversemouse.h" |
596 | 20 | #include <QtQml/QQmlInfo> | 20 | #include <QtQml/QQmlInfo> |
597 | 21 | #include <QtQuick/QQuickItem> | ||
598 | 22 | #include <QtGui/QGuiApplication> | 21 | #include <QtGui/QGuiApplication> |
599 | 23 | #include <private/qqmlglobal_p.h> | 22 | #include <private/qqmlglobal_p.h> |
600 | 24 | #include <QtQuick/private/qquickmousearea_p.h> | 23 | #include <QtQuick/private/qquickmousearea_p.h> |
601 | @@ -31,6 +30,15 @@ | |||
602 | 31 | // keep in sync with QQuickMouseArea PressAndHoldDelay | 30 | // keep in sync with QQuickMouseArea PressAndHoldDelay |
603 | 32 | const int DefaultPressAndHoldDelay = 800; | 31 | const int DefaultPressAndHoldDelay = 800; |
604 | 33 | 32 | ||
605 | 33 | QEvent::Type ForwardedEvent::m_eventBase = QEvent::None; | ||
606 | 34 | void ForwardedEvent::registerForwardedEvent() | ||
607 | 35 | { | ||
608 | 36 | if (m_eventBase > 0) { | ||
609 | 37 | return; | ||
610 | 38 | } | ||
611 | 39 | m_eventBase = (QEvent::Type)QEvent::registerEventType(); | ||
612 | 40 | } | ||
613 | 41 | |||
614 | 34 | /* | 42 | /* |
615 | 35 | * Attached filter instantiator template | 43 | * Attached filter instantiator template |
616 | 36 | */ | 44 | */ |
617 | @@ -158,7 +166,8 @@ | |||
618 | 158 | their parent. In this way mouse events will land in these items too, and mouse | 166 | their parent. In this way mouse events will land in these items too, and mouse |
619 | 159 | filter attached to those can also handle the event. This is useful when creating | 167 | filter attached to those can also handle the event. This is useful when creating |
620 | 160 | custom types where the mouse handling item is nested into a non-mouse handling | 168 | custom types where the mouse handling item is nested into a non-mouse handling |
622 | 161 | one, and we want to provide additional filtering possibility to the user. | 169 | one, and we want to provide additional filtering possibility to the user. These |
623 | 170 | type of items are called proxy handlers. | ||
624 | 162 | 171 | ||
625 | 163 | \qml | 172 | \qml |
626 | 164 | Item { | 173 | Item { |
627 | @@ -180,7 +189,54 @@ | |||
628 | 180 | In this example the mouse press is first handled by the mouse filter attached | 189 | In this example the mouse press is first handled by the mouse filter attached |
629 | 181 | to TextInput, then it is forwarded to the top item and finally to the TextInput. | 190 | to TextInput, then it is forwarded to the top item and finally to the TextInput. |
630 | 182 | Accepting the mouse event will stop propagation to the top item as well as to | 191 | Accepting the mouse event will stop propagation to the top item as well as to |
632 | 183 | the TextInput. | 192 | the TextInput. The topmost item itself does not handle mouse events, therefore |
633 | 193 | it will be a sinple proxy handler item. However, proxies can themself handle | ||
634 | 194 | mouse events. Therefore each mouse event signal has the \a host parameter specifying | ||
635 | 195 | the sender of the mouse event reported. | ||
636 | 196 | |||
637 | 197 | \note The forwarded events are handled in the proxy handlers only if the mouse | ||
638 | 198 | position points inside their area. If the forwarded mouse position falls outside | ||
639 | 199 | the target area, the event will not be reported, however will be forwarded further | ||
640 | 200 | to the items in the list. In the following example the mouse press in red rectangle | ||
641 | 201 | will be printed as well as the proxied mouse press from the main item. | ||
642 | 202 | \qml | ||
643 | 203 | import QtQuick 2.0 | ||
644 | 204 | import Ubuntu.Components 0.1 | ||
645 | 205 | |||
646 | 206 | Item { | ||
647 | 207 | id: main | ||
648 | 208 | width: units.gu(40) | ||
649 | 209 | height: units.gu(71) | ||
650 | 210 | |||
651 | 211 | Mouse.onPressed: console.log("got the mouse press forwarded by " + host.objectName) | ||
652 | 212 | |||
653 | 213 | Column { | ||
654 | 214 | anchors.fill: parent | ||
655 | 215 | spacing: units.gu(1) | ||
656 | 216 | |||
657 | 217 | Rectangle { | ||
658 | 218 | id: blueRect | ||
659 | 219 | objectName: "BlueRect" | ||
660 | 220 | width: parent.width | ||
661 | 221 | height: units.gu(20) | ||
662 | 222 | color: "blue" | ||
663 | 223 | Mouse.forwardTo: [main] | ||
664 | 224 | Mouse.onPressed: console.log("This should not be printed") | ||
665 | 225 | } | ||
666 | 226 | Rectangle { | ||
667 | 227 | objectName: "RedRect" | ||
668 | 228 | width: parent.width | ||
669 | 229 | height: units.gu(20) | ||
670 | 230 | color: "red" | ||
671 | 231 | MouseArea { | ||
672 | 232 | anchors.fill: parent | ||
673 | 233 | Mouse.forwardTo: [blueRect] | ||
674 | 234 | Mouse.onPressed: console.log("Pressed in " + host.objectName) | ||
675 | 235 | } | ||
676 | 236 | } | ||
677 | 237 | } | ||
678 | 238 | } | ||
679 | 239 | \endqml | ||
680 | 184 | 240 | ||
681 | 185 | An interesting feature that can be achieved using Mouse filter is the event | 241 | An interesting feature that can be achieved using Mouse filter is the event |
682 | 186 | "transparency" towards the MouseArea lying behind the items which handle mouse | 242 | "transparency" towards the MouseArea lying behind the items which handle mouse |
683 | @@ -226,6 +282,7 @@ | |||
684 | 226 | } | 282 | } |
685 | 227 | \endqml | 283 | \endqml |
686 | 228 | 284 | ||
687 | 285 | |||
688 | 229 | Mouse filter provides ability to control the order of the event dispatching. | 286 | Mouse filter provides ability to control the order of the event dispatching. |
689 | 230 | The filter can receive the events prior the owner or after the owner. This | 287 | The filter can receive the events prior the owner or after the owner. This |
690 | 231 | can be controlled through the \l priority property. In the following example | 288 | can be controlled through the \l priority property. In the following example |
691 | @@ -281,6 +338,9 @@ | |||
692 | 281 | UCMouse::UCMouse(QObject *parent) | 338 | UCMouse::UCMouse(QObject *parent) |
693 | 282 | : QObject(parent) | 339 | : QObject(parent) |
694 | 283 | , m_owner(qobject_cast<QQuickItem*>(parent)) | 340 | , m_owner(qobject_cast<QQuickItem*>(parent)) |
695 | 341 | , m_lastButton(Qt::NoButton) | ||
696 | 342 | , m_lastButtons(Qt::NoButton) | ||
697 | 343 | , m_lastModifiers(Qt::NoModifier) | ||
698 | 284 | , m_pressedButtons(Qt::NoButton) | 344 | , m_pressedButtons(Qt::NoButton) |
699 | 285 | , m_priority(BeforeItem) | 345 | , m_priority(BeforeItem) |
700 | 286 | , m_moveThreshold(0.0) | 346 | , m_moveThreshold(0.0) |
701 | @@ -328,6 +388,9 @@ | |||
702 | 328 | } else { | 388 | } else { |
703 | 329 | return hoverEvents(target, static_cast<QHoverEvent*>(event)); | 389 | return hoverEvents(target, static_cast<QHoverEvent*>(event)); |
704 | 330 | } | 390 | } |
705 | 391 | } else if (type == ForwardedEvent::baseType()) { | ||
706 | 392 | // this is a forwarded event, handle it as such | ||
707 | 393 | return forwardedEvents(static_cast<ForwardedEvent*>(event)); | ||
708 | 331 | } | 394 | } |
709 | 332 | 395 | ||
710 | 333 | return QObject::eventFilter(target, event); | 396 | return QObject::eventFilter(target, event); |
711 | @@ -341,7 +404,8 @@ | |||
712 | 341 | QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), m_lastButton, m_lastButtons, m_lastModifiers, | 404 | QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), m_lastButton, m_lastButtons, m_lastModifiers, |
713 | 342 | false, m_longPress); | 405 | false, m_longPress); |
714 | 343 | mev.setAccepted(false); | 406 | mev.setAccepted(false); |
716 | 344 | Q_EMIT pressAndHold(&mev); | 407 | Q_EMIT pressAndHold(&mev, m_owner); |
717 | 408 | mev.setAccepted(forwardEvent(ForwardedEvent::MouseLongPress, 0, &mev)); | ||
718 | 345 | // if the event wasn't handled, allow click | 409 | // if the event wasn't handled, allow click |
719 | 346 | if (!mev.isAccepted()) { | 410 | if (!mev.isAccepted()) { |
720 | 347 | m_longPress = false; | 411 | m_longPress = false; |
721 | @@ -376,7 +440,6 @@ | |||
722 | 376 | default: | 440 | default: |
723 | 377 | break; | 441 | break; |
724 | 378 | } | 442 | } |
725 | 379 | forwardEvent(event); | ||
726 | 380 | return result || event->isAccepted(); | 443 | return result || event->isAccepted(); |
727 | 381 | } | 444 | } |
728 | 382 | 445 | ||
729 | @@ -400,27 +463,67 @@ | |||
730 | 400 | default: | 463 | default: |
731 | 401 | break; | 464 | break; |
732 | 402 | } | 465 | } |
733 | 403 | forwardEvent(event); | ||
734 | 404 | return result || event->isAccepted(); | 466 | return result || event->isAccepted(); |
735 | 405 | } | 467 | } |
736 | 406 | 468 | ||
737 | 469 | // emit signals and forward the events further | ||
738 | 470 | bool UCMouse::forwardedEvents(ForwardedEvent *event) | ||
739 | 471 | { | ||
740 | 472 | // the quick event is always specified! | ||
741 | 473 | switch (event->subType()) { | ||
742 | 474 | case ForwardedEvent::MousePress: { | ||
743 | 475 | Q_EMIT pressed(event->quickEvent(), event->sender()); | ||
744 | 476 | } break; | ||
745 | 477 | case ForwardedEvent::MouseRelease: { | ||
746 | 478 | Q_EMIT released(event->quickEvent(), event->sender()); | ||
747 | 479 | } break; | ||
748 | 480 | case ForwardedEvent::MouseMove: { | ||
749 | 481 | Q_EMIT positionChanged(event->quickEvent(), event->sender()); | ||
750 | 482 | } break; | ||
751 | 483 | case ForwardedEvent::MouseDblClick: { | ||
752 | 484 | Q_EMIT doubleClicked(event->quickEvent(), event->sender()); | ||
753 | 485 | } break; | ||
754 | 486 | case ForwardedEvent::HoverEnter: { | ||
755 | 487 | Q_EMIT entered(event->quickEvent(), event->sender()); | ||
756 | 488 | } break; | ||
757 | 489 | case ForwardedEvent::HoverExit: { | ||
758 | 490 | Q_EMIT exited(event->quickEvent(), event->sender()); | ||
759 | 491 | } break; | ||
760 | 492 | // composed events | ||
761 | 493 | case ForwardedEvent::MouseClick: { | ||
762 | 494 | Q_EMIT clicked(event->quickEvent(), event->sender()); | ||
763 | 495 | } break; | ||
764 | 496 | case ForwardedEvent::MouseLongPress: { | ||
765 | 497 | Q_EMIT pressAndHold(event->quickEvent(), event->sender()); | ||
766 | 498 | } break; | ||
767 | 499 | default: break; | ||
768 | 500 | } | ||
769 | 501 | |||
770 | 502 | // forward event, but use the current owner as sender | ||
771 | 503 | event->setAccepted(forwardEvent(event->subType(), event->originalEvent(), event->quickEvent())); | ||
772 | 504 | return event->isAccepted(); | ||
773 | 505 | } | ||
774 | 506 | |||
775 | 407 | bool UCMouse::hasAttachedFilter(QQuickItem *item) | 507 | bool UCMouse::hasAttachedFilter(QQuickItem *item) |
776 | 408 | { | 508 | { |
777 | 409 | return (qmlAttachedPropertiesObject<UCMouse>(item, false) != 0); | 509 | return (qmlAttachedPropertiesObject<UCMouse>(item, false) != 0); |
778 | 410 | } | 510 | } |
779 | 411 | 511 | ||
781 | 412 | void UCMouse::setHovered(bool hovered) | 512 | void UCMouse::setHovered(bool hovered, QEvent *hoverEvent) |
782 | 413 | { | 513 | { |
783 | 414 | if (m_hovered != hovered) { | 514 | if (m_hovered != hovered) { |
784 | 415 | m_hovered = hovered; | 515 | m_hovered = hovered; |
785 | 416 | QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), m_lastButton, m_lastButtons, m_lastModifiers, | 516 | QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), m_lastButton, m_lastButtons, m_lastModifiers, |
786 | 417 | false, false); | 517 | false, false); |
787 | 418 | mev.setAccepted(false); | 518 | mev.setAccepted(false); |
788 | 519 | ForwardedEvent::EventType type = (m_hovered) ? ForwardedEvent::HoverEnter : ForwardedEvent::HoverExit; | ||
789 | 419 | if (m_hovered) { | 520 | if (m_hovered) { |
791 | 420 | Q_EMIT entered(&mev); | 521 | Q_EMIT entered(&mev, m_owner); |
792 | 421 | } else { | 522 | } else { |
794 | 422 | Q_EMIT exited(&mev); | 523 | m_pressAndHoldTimer.stop(); |
795 | 524 | Q_EMIT exited(&mev, m_owner); | ||
796 | 423 | } | 525 | } |
797 | 526 | forwardEvent(type, hoverEvent, &mev); | ||
798 | 424 | } | 527 | } |
799 | 425 | } | 528 | } |
800 | 426 | 529 | ||
801 | @@ -432,13 +535,13 @@ | |||
802 | 432 | m_pressedButtons |= m_lastButton; | 535 | m_pressedButtons |= m_lastButton; |
803 | 433 | m_longPress = m_doubleClicked = false; | 536 | m_longPress = m_doubleClicked = false; |
804 | 434 | 537 | ||
806 | 435 | setHovered(true); | 538 | setHovered(true, 0); |
807 | 436 | 539 | ||
808 | 437 | QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), m_lastButton, m_lastButtons, m_lastModifiers, | 540 | QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), m_lastButton, m_lastButtons, m_lastModifiers, |
809 | 438 | false, m_longPress); | 541 | false, m_longPress); |
810 | 439 | mev.setAccepted(false); | 542 | mev.setAccepted(false); |
813 | 440 | Q_EMIT pressed(&mev); | 543 | Q_EMIT pressed(&mev, m_owner); |
814 | 441 | event->setAccepted(mev.isAccepted()); | 544 | event->setAccepted(forwardEvent(ForwardedEvent::MousePress, event, &mev)); |
815 | 442 | 545 | ||
816 | 443 | // start long press timer | 546 | // start long press timer |
817 | 444 | m_pressAndHoldTimer.start(DefaultPressAndHoldDelay, this); | 547 | m_pressAndHoldTimer.start(DefaultPressAndHoldDelay, this); |
818 | @@ -460,14 +563,14 @@ | |||
819 | 460 | m_pressAndHoldTimer.stop(); | 563 | m_pressAndHoldTimer.stop(); |
820 | 461 | } | 564 | } |
821 | 462 | 565 | ||
823 | 463 | setHovered(true); | 566 | setHovered(true, 0); |
824 | 464 | m_moved = true; | 567 | m_moved = true; |
825 | 465 | m_doubleClicked = false; | 568 | m_doubleClicked = false; |
826 | 466 | QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), m_lastButton, m_lastButtons, m_lastModifiers, | 569 | QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), m_lastButton, m_lastButtons, m_lastModifiers, |
827 | 467 | false, m_longPress); | 570 | false, m_longPress); |
828 | 468 | mev.setAccepted(false); | 571 | mev.setAccepted(false); |
831 | 469 | Q_EMIT positionChanged(&mev); | 572 | Q_EMIT positionChanged(&mev, m_owner); |
832 | 470 | event->setAccepted(mev.isAccepted()); | 573 | event->setAccepted(forwardEvent(ForwardedEvent::MouseMove, event, &mev)); |
833 | 471 | return mev.isAccepted(); | 574 | return mev.isAccepted(); |
834 | 472 | } | 575 | } |
835 | 473 | 576 | ||
836 | @@ -487,18 +590,20 @@ | |||
837 | 487 | QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), m_lastButton, m_lastButtons, m_lastModifiers, | 590 | QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), m_lastButton, m_lastButtons, m_lastModifiers, |
838 | 488 | isClicked, m_longPress); | 591 | isClicked, m_longPress); |
839 | 489 | mev.setAccepted(false); | 592 | mev.setAccepted(false); |
842 | 490 | Q_EMIT released(&mev); | 593 | Q_EMIT released(&mev, m_owner); |
843 | 491 | event->setAccepted(mev.isAccepted()); | 594 | event->setAccepted(forwardEvent(ForwardedEvent::MouseRelease, event, &mev)); |
844 | 492 | 595 | ||
845 | 493 | // remove button from press | 596 | // remove button from press |
846 | 494 | m_pressedButtons &= ~m_lastButton; | 597 | m_pressedButtons &= ~m_lastButton; |
847 | 495 | if (isClicked) { | 598 | if (isClicked) { |
848 | 496 | // emit clicked | 599 | // emit clicked |
850 | 497 | Q_EMIT clicked(&mev); | 600 | mev.setAccepted(false); |
851 | 601 | Q_EMIT clicked(&mev, m_owner); | ||
852 | 602 | forwardEvent(ForwardedEvent::MouseClick, 0, &mev); | ||
853 | 498 | } | 603 | } |
854 | 499 | 604 | ||
855 | 500 | if (!m_pressedButtons && !m_owner->acceptHoverEvents()) { | 605 | if (!m_pressedButtons && !m_owner->acceptHoverEvents()) { |
857 | 501 | setHovered(false); | 606 | setHovered(false, 0); |
858 | 502 | } | 607 | } |
859 | 503 | return mev.isAccepted(); | 608 | return mev.isAccepted(); |
860 | 504 | } | 609 | } |
861 | @@ -511,18 +616,17 @@ | |||
862 | 511 | { | 616 | { |
863 | 512 | if (m_pressedButtons) { | 617 | if (m_pressedButtons) { |
864 | 513 | saveEvent(event); | 618 | saveEvent(event); |
865 | 619 | |||
866 | 620 | QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), m_lastButton, m_lastButtons, m_lastModifiers, | ||
867 | 621 | true, m_longPress); | ||
868 | 622 | mev.setAccepted(false); | ||
869 | 514 | // if double click connected, suppress release() and click() signals | 623 | // if double click connected, suppress release() and click() signals |
870 | 515 | if (isDoubleClickConnected()) { | 624 | if (isDoubleClickConnected()) { |
876 | 516 | QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), m_lastButton, m_lastButtons, m_lastModifiers, | 625 | Q_EMIT doubleClicked(&mev, m_owner); |
872 | 517 | true, m_longPress); | ||
873 | 518 | mev.setAccepted(false); | ||
874 | 519 | Q_EMIT doubleClicked(&mev); | ||
875 | 520 | event->setAccepted(mev.isAccepted()); | ||
877 | 521 | m_doubleClicked = true; | 626 | m_doubleClicked = true; |
878 | 522 | } else { | ||
879 | 523 | // set accepted to false still | ||
880 | 524 | event->setAccepted(false); | ||
881 | 525 | } | 627 | } |
882 | 628 | // forward event even if it wasn't handled for the owner | ||
883 | 629 | event->setAccepted(forwardEvent(ForwardedEvent::MouseDblClick, event, &mev)); | ||
884 | 526 | return event->isAccepted(); | 630 | return event->isAccepted(); |
885 | 527 | } | 631 | } |
886 | 528 | 632 | ||
887 | @@ -534,7 +638,9 @@ | |||
888 | 534 | { | 638 | { |
889 | 535 | m_lastPos = event->posF(); | 639 | m_lastPos = event->posF(); |
890 | 536 | m_lastModifiers = event->modifiers(); | 640 | m_lastModifiers = event->modifiers(); |
892 | 537 | setHovered(true); | 641 | m_lastButton = Qt::NoButton; |
893 | 642 | m_lastButtons = Qt::NoButton; | ||
894 | 643 | setHovered(true, event); | ||
895 | 538 | return false; | 644 | return false; |
896 | 539 | } | 645 | } |
897 | 540 | 646 | ||
898 | @@ -542,11 +648,11 @@ | |||
899 | 542 | { | 648 | { |
900 | 543 | m_lastPos = event->posF(); | 649 | m_lastPos = event->posF(); |
901 | 544 | m_lastModifiers = event->modifiers(); | 650 | m_lastModifiers = event->modifiers(); |
903 | 545 | QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), m_lastButton, m_lastButtons, m_lastModifiers, | 651 | QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), Qt::NoButton, Qt::NoButton, m_lastModifiers, |
904 | 546 | false, m_longPress); | 652 | false, m_longPress); |
905 | 547 | mev.setAccepted(false); | 653 | mev.setAccepted(false); |
908 | 548 | Q_EMIT positionChanged(&mev); | 654 | Q_EMIT positionChanged(&mev, m_owner); |
909 | 549 | forwardEvent(event); | 655 | event->setAccepted(forwardEvent(ForwardedEvent::MouseMove, event, &mev)); |
910 | 550 | return false; | 656 | return false; |
911 | 551 | } | 657 | } |
912 | 552 | 658 | ||
913 | @@ -554,7 +660,9 @@ | |||
914 | 554 | { | 660 | { |
915 | 555 | m_lastPos = event->posF(); | 661 | m_lastPos = event->posF(); |
916 | 556 | m_lastModifiers = event->modifiers(); | 662 | m_lastModifiers = event->modifiers(); |
918 | 557 | setHovered(false); | 663 | m_lastButton = Qt::NoButton; |
919 | 664 | m_lastButtons = Qt::NoButton; | ||
920 | 665 | setHovered(false, event); | ||
921 | 558 | return false; | 666 | return false; |
922 | 559 | } | 667 | } |
923 | 560 | 668 | ||
924 | @@ -562,8 +670,10 @@ | |||
925 | 562 | { | 670 | { |
926 | 563 | m_lastPos = event->localPos(); | 671 | m_lastPos = event->localPos(); |
927 | 564 | m_lastScenePos = event->windowPos(); | 672 | m_lastScenePos = event->windowPos(); |
930 | 565 | m_lastButton = event->button(); | 673 | if (event->type() != QEvent::MouseMove) { |
931 | 566 | m_lastButtons = event->buttons(); | 674 | m_lastButton = event->button(); |
932 | 675 | m_lastButtons = event->buttons(); | ||
933 | 676 | } | ||
934 | 567 | m_lastModifiers = event->modifiers(); | 677 | m_lastModifiers = event->modifiers(); |
935 | 568 | if ((event->type() == QEvent::MouseButtonPress) && (m_moveThreshold > 0.0)) { | 678 | if ((event->type() == QEvent::MouseButtonPress) && (m_moveThreshold > 0.0)) { |
936 | 569 | m_toleranceArea.setX(m_lastPos.x() - m_moveThreshold); | 679 | m_toleranceArea.setX(m_lastPos.x() - m_moveThreshold); |
937 | @@ -574,7 +684,7 @@ | |||
938 | 574 | } | 684 | } |
939 | 575 | bool UCMouse::isDoubleClickConnected() | 685 | bool UCMouse::isDoubleClickConnected() |
940 | 576 | { | 686 | { |
942 | 577 | IS_SIGNAL_CONNECTED(this, UCMouse, doubleClicked, (QQuickMouseEvent*)); | 687 | IS_SIGNAL_CONNECTED(this, UCMouse, doubleClicked, (QQuickMouseEvent*,QQuickItem*)); |
943 | 578 | } | 688 | } |
944 | 579 | 689 | ||
945 | 580 | bool UCMouse::isMouseEvent(QEvent::Type type) | 690 | bool UCMouse::isMouseEvent(QEvent::Type type) |
946 | @@ -588,57 +698,73 @@ | |||
947 | 588 | return (type == QEvent::HoverEnter) || (type == QEvent::HoverMove) || (type == QEvent::HoverLeave); | 698 | return (type == QEvent::HoverEnter) || (type == QEvent::HoverMove) || (type == QEvent::HoverLeave); |
948 | 589 | } | 699 | } |
949 | 590 | 700 | ||
952 | 591 | // forwards the events to the listed items; only mouse and hover events qualify | 701 | |
953 | 592 | void UCMouse::forwardEvent(QEvent *event) | 702 | /* |
954 | 703 | * Forwards the events to the listed items. The event coordinates are mapped to the destination's coordinates | ||
955 | 704 | * and sent to the destination in case the destination has no filter attached. Otherwise the quick event | ||
956 | 705 | * coordinates will be mapped and sent as ForwardedEvents. | ||
957 | 706 | */ | ||
958 | 707 | bool UCMouse::forwardEvent(ForwardedEvent::EventType type, QEvent *event, QQuickMouseEvent *quickEvent) | ||
959 | 593 | { | 708 | { |
964 | 594 | /* | 709 | // first set the accepted flag to the original event |
965 | 595 | * alter acceptedButtons and hoverEnabled for the time the event is delivered | 710 | if (event && quickEvent) { |
966 | 596 | * exclude MouseArea and InverseMouseArea!! | 711 | event->setAccepted(quickEvent->isAccepted()); |
967 | 597 | */ | 712 | } |
968 | 713 | bool accepted = event ? event->isAccepted() : (quickEvent ? quickEvent->isAccepted() : false); | ||
969 | 714 | |||
970 | 598 | Q_FOREACH(QQuickItem *item, m_forwardList) { | 715 | Q_FOREACH(QQuickItem *item, m_forwardList) { |
971 | 599 | 716 | ||
973 | 600 | if (event->isAccepted()) { | 717 | if (accepted) { |
974 | 601 | // the event got accepted, therefore should not be forwarded | 718 | // the event got accepted, therefore should not be forwarded |
976 | 602 | return; | 719 | return accepted; |
977 | 603 | } | 720 | } |
978 | 604 | // skip InverseMouseArea otherwise those will get the event twice | 721 | // skip InverseMouseArea otherwise those will get the event twice |
979 | 605 | if (qobject_cast<InverseMouseAreaType*>(item)) { | 722 | if (qobject_cast<InverseMouseAreaType*>(item)) { |
980 | 606 | continue; | 723 | continue; |
981 | 607 | } | 724 | } |
1015 | 608 | /* | 725 | |
1016 | 609 | * temporarily enable mouse buttons and hover for items which have Mouse or InverseMouse | 726 | // map the normal event coordinates to item |
1017 | 610 | * filter attached, but exclude targets which is MouseArea or InverseMouseArea | 727 | QEvent *mappedEvent = 0; |
1018 | 611 | */ | 728 | if (event) { |
1019 | 612 | Qt::MouseButtons acceptedButtons = item->acceptedMouseButtons(); | 729 | if (isMouseEvent(event->type())) { |
1020 | 613 | bool hoverEnabled = item->acceptHoverEvents(); | 730 | QMouseEvent *mouse = static_cast<QMouseEvent*>(event); |
1021 | 614 | if (!qobject_cast<QQuickMouseArea*>(item) && hasAttachedFilter(item)) { | 731 | QPointF itemPos = item->mapFromScene(m_owner->mapToScene(mouse->pos())); |
1022 | 615 | // set accepted buttons and hover temporarily | 732 | mappedEvent = new QMouseEvent(event->type(), itemPos, mouse->button(), mouse->buttons(), mouse->modifiers()); |
1023 | 616 | item->setAcceptedMouseButtons(m_owner->acceptedMouseButtons()); | 733 | } else if (isHoverEvent(event->type())){ |
1024 | 617 | item->setAcceptHoverEvents(m_owner->acceptHoverEvents()); | 734 | QHoverEvent *hover = static_cast<QHoverEvent*>(event); |
1025 | 618 | } | 735 | QPointF itemPos = item->mapFromScene(m_owner->mapToScene(hover->pos())); |
1026 | 619 | 736 | QPointF itemOldPos = item->mapFromScene(m_owner->mapToScene(hover->oldPos())); | |
1027 | 620 | // forward events | 737 | mappedEvent = new QHoverEvent(event->type(), itemPos, itemOldPos, hover->modifiers()); |
1028 | 621 | bool accepted = false; | 738 | } |
1029 | 622 | if (isMouseEvent(event->type())) { | 739 | } |
1030 | 623 | QMouseEvent *mouse = static_cast<QMouseEvent*>(event); | 740 | |
1031 | 624 | QPointF itemPos = item->mapFromScene(m_owner->mapToScene(mouse->pos())); | 741 | // if the item has no filter attached, deliver the mapped event to it as it is |
1032 | 625 | QMouseEvent me = QMouseEvent(event->type(), itemPos, mouse->button(), mouse->buttons(), mouse->modifiers()); | 742 | UCMouse *filter = qobject_cast<UCMouse*>(qmlAttachedPropertiesObject<UCMouse>(item, false)); |
1033 | 626 | QGuiApplication::sendEvent(item, &me); | 743 | if (!filter && mappedEvent) { |
1034 | 627 | accepted = me.isAccepted(); | 744 | QGuiApplication::sendEvent(item, mappedEvent); |
1035 | 628 | } else { | 745 | accepted = mappedEvent->isAccepted(); |
1036 | 629 | QHoverEvent *hover = static_cast<QHoverEvent*>(event); | 746 | } else if (quickEvent) { |
1037 | 630 | QPointF itemPos = item->mapFromScene(m_owner->mapToScene(hover->pos())); | 747 | // map the quick event coordinates as well |
1038 | 631 | QPointF itemOldPos = item->mapFromScene(m_owner->mapToScene(hover->oldPos())); | 748 | QPoint itemPos(item->mapFromScene(m_owner->mapToScene(QPointF(quickEvent->x(), quickEvent->y()))).toPoint()); |
1039 | 632 | QHoverEvent he = QHoverEvent(event->type(), itemPos, itemOldPos, hover->modifiers()); | 749 | QQuickMouseEvent mev(itemPos.x(), itemPos.y(), (Qt::MouseButton)quickEvent->button(), (Qt::MouseButtons)quickEvent->buttons(), |
1040 | 633 | QGuiApplication::sendEvent(item, &he); | 750 | (Qt::KeyboardModifiers)quickEvent->modifiers(), quickEvent->isClick(), quickEvent->wasHeld()); |
1041 | 634 | accepted = he.isAccepted(); | 751 | mev.setAccepted(false); |
1042 | 635 | } | 752 | ForwardedEvent forwardedEvent(type, m_owner, mappedEvent, &mev); |
1043 | 636 | event->setAccepted(accepted); | 753 | QGuiApplication::sendEvent(item, &forwardedEvent); |
1044 | 637 | 754 | accepted = mev.isAccepted(); | |
1045 | 638 | // restore acceptedButtons and hover | 755 | } |
1046 | 639 | item->setAcceptedMouseButtons(acceptedButtons); | 756 | |
1047 | 640 | item->setAcceptHoverEvents(hoverEnabled); | 757 | // cleanup and transfer accepted flag |
1048 | 758 | delete mappedEvent; | ||
1049 | 759 | if (event) { | ||
1050 | 760 | event->setAccepted(accepted); | ||
1051 | 761 | } | ||
1052 | 762 | if (quickEvent) { | ||
1053 | 763 | quickEvent->setAccepted(accepted); | ||
1054 | 764 | } | ||
1055 | 641 | } | 765 | } |
1056 | 766 | |||
1057 | 767 | return accepted; | ||
1058 | 642 | } | 768 | } |
1059 | 643 | 769 | ||
1060 | 644 | 770 | ||
1061 | @@ -697,6 +823,9 @@ | |||
1062 | 697 | composed events will come until the mouse is moved inside the owner's area. | 823 | composed events will come until the mouse is moved inside the owner's area. |
1063 | 698 | 824 | ||
1064 | 699 | The default value is 0. | 825 | The default value is 0. |
1065 | 826 | |||
1066 | 827 | \note The value has no effect for the forwarded events. The threshold is only | ||
1067 | 828 | valid when the host handles mouse events. | ||
1068 | 700 | */ | 829 | */ |
1069 | 701 | int UCMouse::clickAndHoldThreshold() const | 830 | int UCMouse::clickAndHoldThreshold() const |
1070 | 702 | { | 831 | { |
1071 | @@ -767,29 +896,34 @@ | |||
1072 | 767 | } | 896 | } |
1073 | 768 | 897 | ||
1074 | 769 | /*! | 898 | /*! |
1077 | 770 | \qmlsignal Mouse::onPressed(MouseEvent event) | 899 | \qmlsignal Mouse::onPressed(MouseEvent event, Item host) |
1078 | 771 | The signal reports the mouse press. | 900 | The signal reports the mouse press. The \a host specifies the item that triggered |
1079 | 901 | the event. | ||
1080 | 772 | */ | 902 | */ |
1081 | 773 | 903 | ||
1082 | 774 | /*! | 904 | /*! |
1083 | 775 | \qmlsignal Mouse::onReleased(MouseEvent event) | 905 | \qmlsignal Mouse::onReleased(MouseEvent event) |
1085 | 776 | The signal reports the mouse release. | 906 | The signal reports the mouse release. The \a host specifies the item that triggered |
1086 | 907 | the event. | ||
1087 | 777 | */ | 908 | */ |
1088 | 778 | 909 | ||
1089 | 779 | /*! | 910 | /*! |
1090 | 780 | \qmlsignal Mouse::onClicked(MouseEvent event) | 911 | \qmlsignal Mouse::onClicked(MouseEvent event) |
1091 | 781 | The signal reports the mouse click. The signal is not emitted if the onPressAndHold | 912 | The signal reports the mouse click. The signal is not emitted if the onPressAndHold |
1093 | 782 | got triggered or if onDoubleClicked is handled (a slot is connected to it). | 913 | got triggered or if onDoubleClicked is handled (a slot is connected to it). The \a |
1094 | 914 | host specifies the item that triggered the event. | ||
1095 | 783 | */ | 915 | */ |
1096 | 784 | 916 | ||
1097 | 785 | /*! | 917 | /*! |
1098 | 786 | \qmlsignal Mouse::onPressAndHold(MouseEvent event) | 918 | \qmlsignal Mouse::onPressAndHold(MouseEvent event) |
1100 | 787 | The signal reports the mouse press and hold. | 919 | The signal reports the mouse press and hold. The \a host specifies the item that triggered |
1101 | 920 | the event. | ||
1102 | 788 | */ | 921 | */ |
1103 | 789 | 922 | ||
1104 | 790 | /*! | 923 | /*! |
1105 | 791 | \qmlsignal Mouse::onDoubleClicked(MouseEvent event) | 924 | \qmlsignal Mouse::onDoubleClicked(MouseEvent event) |
1107 | 792 | The signal reports mouse double click. | 925 | The signal reports mouse double click. The \a host specifies the item that triggered |
1108 | 926 | the event. | ||
1109 | 793 | */ | 927 | */ |
1110 | 794 | 928 | ||
1111 | 795 | /*! | 929 | /*! |
1112 | @@ -797,20 +931,22 @@ | |||
1113 | 797 | The signal reports the mouse pointer position change. If the hover events are | 931 | The signal reports the mouse pointer position change. If the hover events are |
1114 | 798 | enabled for the owner, the signal will come continuously. Otherwise the position | 932 | enabled for the owner, the signal will come continuously. Otherwise the position |
1115 | 799 | chanes are reported when one of the accepted mouse buttons are being kept pressed. | 933 | chanes are reported when one of the accepted mouse buttons are being kept pressed. |
1116 | 934 | The \a host specifies the item that triggered the event. | ||
1117 | 800 | */ | 935 | */ |
1118 | 801 | 936 | ||
1119 | 802 | /*! | 937 | /*! |
1120 | 803 | \qmlsignal Mouse::onEntered(MouseEvent event) | 938 | \qmlsignal Mouse::onEntered(MouseEvent event) |
1121 | 804 | The signal reports that the mouse has entered into the area. The signal is | 939 | The signal reports that the mouse has entered into the area. The signal is |
1122 | 805 | emitted when the hover events are enabled and the mouse enters the area or | 940 | emitted when the hover events are enabled and the mouse enters the area or |
1124 | 806 | when one of the accepted mouse button is pressed. | 941 | when one of the accepted mouse button is pressed. The \a host specifies the |
1125 | 942 | item that triggered the event. | ||
1126 | 807 | */ | 943 | */ |
1127 | 808 | 944 | ||
1128 | 809 | /*! | 945 | /*! |
1129 | 810 | \qmlsignal Mouse::onExited(MouseEvent event) | 946 | \qmlsignal Mouse::onExited(MouseEvent event) |
1130 | 811 | The signal reports that the mouse has left the area. The signal is emitted when | 947 | The signal reports that the mouse has left the area. The signal is emitted when |
1131 | 812 | the hover events are enabled for the owner or if not, when one of the accepted | 948 | the hover events are enabled for the owner or if not, when one of the accepted |
1133 | 813 | button is released. | 949 | button is released. The \a host specifies the item that triggered the event. |
1134 | 814 | */ | 950 | */ |
1135 | 815 | 951 | ||
1136 | 816 | /****************************************************************************** | 952 | /****************************************************************************** |
1137 | 817 | 953 | ||
1138 | === modified file 'modules/Ubuntu/Test/UbuntuTestCase.qml' | |||
1139 | --- modules/Ubuntu/Test/UbuntuTestCase.qml 2014-02-25 12:36:27 +0000 | |||
1140 | +++ modules/Ubuntu/Test/UbuntuTestCase.qml 2014-04-17 01:29:24 +0000 | |||
1141 | @@ -87,7 +87,75 @@ | |||
1142 | 87 | } | 87 | } |
1143 | 88 | } | 88 | } |
1144 | 89 | 89 | ||
1146 | 90 | /*! | 90 | /*! |
1147 | 91 | \qmlmethod UbuntuTestCase::flick(item, x, y, dx, dy, pressTimeout = -1, steps = -1, button = Qt.LeftButton, modifiers = Qt.NoModifiers, delay = -1) | ||
1148 | 92 | |||
1149 | 93 | The function produces a flick event when executed on Flickables. When used | ||
1150 | 94 | on other components it provides the same functionality as \l mouseDrag() | ||
1151 | 95 | function. The optional \a pressTimeout parameter can be used to introduce | ||
1152 | 96 | a small delay between the mouse press and the first mouse move. Setting a | ||
1153 | 97 | negative or zero value will disable the timeout. | ||
1154 | 98 | |||
1155 | 99 | The default flick velocity is built up using 5 move points. This can be altered | ||
1156 | 100 | by setting a positive value to \a steps parameter. The bigger the number the | ||
1157 | 101 | longer the flick will be. When a negative or zero value is given, the default | ||
1158 | 102 | of 5 move points will be used. | ||
1159 | 103 | |||
1160 | 104 | \note The function can be used to select a text in a TextField or TextArea by | ||
1161 | 105 | specifying at least 400 millisecods to \a pressTimeout. | ||
1162 | 106 | */ | ||
1163 | 107 | function flick(item, x, y, dx, dy, pressTimeout, steps, button, modifiers, delay) { | ||
1164 | 108 | if (item === undefined || item.x === undefined || item.y === undefined) | ||
1165 | 109 | return | ||
1166 | 110 | if (button === undefined) | ||
1167 | 111 | button = Qt.LeftButton | ||
1168 | 112 | if (modifiers === undefined) | ||
1169 | 113 | modifiers = Qt.NoModifier | ||
1170 | 114 | if (steps === undefined || steps <= 0) | ||
1171 | 115 | steps = 4; | ||
1172 | 116 | // make sure we have at least two move steps so the flick will be sensed | ||
1173 | 117 | steps += 1; | ||
1174 | 118 | if (delay === undefined) | ||
1175 | 119 | delay = -1; | ||
1176 | 120 | |||
1177 | 121 | var ddx = dx / steps; | ||
1178 | 122 | var ddy = dy / steps; | ||
1179 | 123 | |||
1180 | 124 | mousePress(item, x, y, button, modifiers, delay); | ||
1181 | 125 | if (pressTimeout !== undefined && pressTimeout > 0) { | ||
1182 | 126 | wait(pressTimeout); | ||
1183 | 127 | } | ||
1184 | 128 | for (var i = 1; i <= steps; i++) { | ||
1185 | 129 | // mouse moves are all processed immediately, without delay in between events | ||
1186 | 130 | mouseMove(item, x + i * ddx, y + i * ddy, -1, button); | ||
1187 | 131 | } | ||
1188 | 132 | mouseRelease(item, x + dx, y + dy, button, modifiers, delay); | ||
1189 | 133 | // empty event buffer | ||
1190 | 134 | wait(200); | ||
1191 | 135 | } | ||
1192 | 136 | |||
1193 | 137 | /*! | ||
1194 | 138 | \qmlmethod UbuntuTestCase::mouseLongPress(item, x, y, button = Qt.LeftButton, modifiers = Qt.NoModifiers, delay = -1) | ||
1195 | 139 | |||
1196 | 140 | Simulates a long press on a mouse \a button with an optional \a modifier | ||
1197 | 141 | on an \a item. The position is defined by \a x and \a y. If \a delay is | ||
1198 | 142 | specified, the test will wait the specified amount of milliseconds before | ||
1199 | 143 | the press. | ||
1200 | 144 | |||
1201 | 145 | The position given by \a x and \a y is transformed from the co-ordinate | ||
1202 | 146 | system of \a item into window co-ordinates and then delivered. | ||
1203 | 147 | If \a item is obscured by another item, or a child of \a item occupies | ||
1204 | 148 | that position, then the event will be delivered to the other item instead. | ||
1205 | 149 | |||
1206 | 150 | \sa mouseRelease(), mouseClick(), mouseDoubleClick(), mouseMove(), mouseDrag(), mouseWheel() | ||
1207 | 151 | */ | ||
1208 | 152 | function mouseLongPress(item, x, y, button, modifiers, delay) { | ||
1209 | 153 | mousePress(item, x, y, button, modifiers, delay); | ||
1210 | 154 | // the delay is taken from QQuickMouseArea | ||
1211 | 155 | wait(800); | ||
1212 | 156 | } | ||
1213 | 157 | |||
1214 | 158 | /*! | ||
1215 | 91 | Keeps executing a given parameter-less function until it returns the given | 159 | Keeps executing a given parameter-less function until it returns the given |
1216 | 92 | expected result or the timemout is reached (in which case a test failure | 160 | expected result or the timemout is reached (in which case a test failure |
1217 | 93 | is generated) | 161 | is generated) |
1218 | 94 | 162 | ||
1219 | === modified file 'modules/Ubuntu/Test/deployment.pri' | |||
1220 | --- modules/Ubuntu/Test/deployment.pri 2014-01-17 12:30:05 +0000 | |||
1221 | +++ modules/Ubuntu/Test/deployment.pri 2014-04-17 01:29:24 +0000 | |||
1222 | @@ -7,9 +7,14 @@ | |||
1223 | 7 | # make found deployables visible in Qt Creator | 7 | # make found deployables visible in Qt Creator |
1224 | 8 | OTHER_FILES += $$QMLDIR_FILE | 8 | OTHER_FILES += $$QMLDIR_FILE |
1225 | 9 | 9 | ||
1226 | 10 | QML_FILES = $$system(ls *.qml) | ||
1227 | 11 | JS_FILES = $$system(ls *.js) | ||
1228 | 12 | |||
1229 | 10 | # define deployment for found deployables | 13 | # define deployment for found deployables |
1230 | 11 | qmldir_file.path = $$installPath | 14 | qmldir_file.path = $$installPath |
1231 | 12 | qmldir_file.files = $$QMLDIR_FILE | 15 | qmldir_file.files = $$QMLDIR_FILE |
1232 | 16 | qml_files.path = $$installPath | ||
1233 | 17 | qml_files.files = $$QML_FILES | ||
1234 | 13 | js_files.path = $$installPath | 18 | js_files.path = $$installPath |
1235 | 14 | js_files.files = $$JS_FILES | 19 | js_files.files = $$JS_FILES |
1236 | 15 | 20 | ||
1237 | @@ -20,4 +25,4 @@ | |||
1238 | 20 | # https://bugreports.qt-project.org/browse/QTBUG-36243 | 25 | # https://bugreports.qt-project.org/browse/QTBUG-36243 |
1239 | 21 | plugins_qmltypes.extra = $$[QT_INSTALL_BINS]/qmlplugindump -notrelocatable Ubuntu.Test 0.1 ../../ 2>/dev/null > $(INSTALL_ROOT)/$$installPath/plugins.qmltypes | 26 | plugins_qmltypes.extra = $$[QT_INSTALL_BINS]/qmlplugindump -notrelocatable Ubuntu.Test 0.1 ../../ 2>/dev/null > $(INSTALL_ROOT)/$$installPath/plugins.qmltypes |
1240 | 22 | 27 | ||
1242 | 23 | INSTALLS += qmldir_file plugins_qmltypes | 28 | INSTALLS += qmldir_file plugins_qmltypes qml_files js_files |
1243 | 24 | 29 | ||
1244 | === modified file 'modules/Ubuntu/Test/plugin/uctestcase.cpp' | |||
1245 | --- modules/Ubuntu/Test/plugin/uctestcase.cpp 2014-03-19 12:48:33 +0000 | |||
1246 | +++ modules/Ubuntu/Test/plugin/uctestcase.cpp 2014-04-17 01:29:24 +0000 | |||
1247 | @@ -26,6 +26,8 @@ | |||
1248 | 26 | #include <QtTest/QtTest> | 26 | #include <QtTest/QtTest> |
1249 | 27 | #include <QtQuick/QQuickItem> | 27 | #include <QtQuick/QQuickItem> |
1250 | 28 | 28 | ||
1251 | 29 | Q_DECLARE_METATYPE(QList<QQmlError>) | ||
1252 | 30 | |||
1253 | 29 | /*! | 31 | /*! |
1254 | 30 | * \ingroup ubuntu | 32 | * \ingroup ubuntu |
1255 | 31 | * \brief UbuntuTestCase is the C++ pendant to the QML UbuntuTestCase. | 33 | * \brief UbuntuTestCase is the C++ pendant to the QML UbuntuTestCase. |
1256 | @@ -37,6 +39,7 @@ | |||
1257 | 37 | QString modulePath(QDir(modules).absolutePath()); | 39 | QString modulePath(QDir(modules).absolutePath()); |
1258 | 38 | engine()->addImportPath(modulePath); | 40 | engine()->addImportPath(modulePath); |
1259 | 39 | 41 | ||
1260 | 42 | qRegisterMetaType<QList <QQmlError> >(); | ||
1261 | 40 | m_spy = new QSignalSpy(engine(), SIGNAL(warnings(QList<QQmlError>))); | 43 | m_spy = new QSignalSpy(engine(), SIGNAL(warnings(QList<QQmlError>))); |
1262 | 41 | m_spy->setParent(this); | 44 | m_spy->setParent(this); |
1263 | 42 | 45 | ||
1264 | @@ -48,3 +51,12 @@ | |||
1265 | 48 | QTest::qWaitForWindowExposed(this); | 51 | QTest::qWaitForWindowExposed(this); |
1266 | 49 | } | 52 | } |
1267 | 50 | 53 | ||
1268 | 54 | /*! | ||
1269 | 55 | * The number of all warnings from the point of loading the first line of QML code. | ||
1270 | 56 | */ | ||
1271 | 57 | int | ||
1272 | 58 | UbuntuTestCase::warnings() const | ||
1273 | 59 | { | ||
1274 | 60 | return m_spy->count(); | ||
1275 | 61 | } | ||
1276 | 62 | |||
1277 | 51 | 63 | ||
1278 | === modified file 'modules/Ubuntu/Test/plugin/uctestcase.h' | |||
1279 | --- modules/Ubuntu/Test/plugin/uctestcase.h 2014-03-19 12:48:33 +0000 | |||
1280 | +++ modules/Ubuntu/Test/plugin/uctestcase.h 2014-04-17 01:29:24 +0000 | |||
1281 | @@ -29,6 +29,7 @@ | |||
1282 | 29 | 29 | ||
1283 | 30 | public: | 30 | public: |
1284 | 31 | UbuntuTestCase(const QString& file, QWindow* parent = 0); | 31 | UbuntuTestCase(const QString& file, QWindow* parent = 0); |
1285 | 32 | int warnings() const; | ||
1286 | 32 | // getter | 33 | // getter |
1287 | 33 | template<class T> | 34 | template<class T> |
1288 | 34 | inline T findItem(const QString& objectName) const { | 35 | inline T findItem(const QString& objectName) const { |
1289 | @@ -39,7 +40,6 @@ | |||
1290 | 39 | qFatal("Item '%s' found with unexpected type", qPrintable(objectName)); | 40 | qFatal("Item '%s' found with unexpected type", qPrintable(objectName)); |
1291 | 40 | qFatal("No item '%s' found", qPrintable(objectName)); | 41 | qFatal("No item '%s' found", qPrintable(objectName)); |
1292 | 41 | } | 42 | } |
1293 | 42 | |||
1294 | 43 | private: | 43 | private: |
1295 | 44 | QSignalSpy* m_spy; | 44 | QSignalSpy* m_spy; |
1296 | 45 | }; | 45 | }; |
1297 | 46 | 46 | ||
1298 | === removed file 'tests/autopilot/ubuntuuitoolkit/__init__.py' | |||
1299 | --- tests/autopilot/ubuntuuitoolkit/__init__.py 2013-07-10 16:07:33 +0000 | |||
1300 | +++ tests/autopilot/ubuntuuitoolkit/__init__.py 1970-01-01 00:00:00 +0000 | |||
1301 | @@ -1,17 +0,0 @@ | |||
1302 | 1 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- | ||
1303 | 2 | # | ||
1304 | 3 | # Copyright (C) 2012, 2013 Canonical Ltd. | ||
1305 | 4 | # | ||
1306 | 5 | # This program is free software; you can redistribute it and/or modify | ||
1307 | 6 | # it under the terms of the GNU Lesser General Public License as published by | ||
1308 | 7 | # the Free Software Foundation; version 3. | ||
1309 | 8 | # | ||
1310 | 9 | # This program is distributed in the hope that it will be useful, | ||
1311 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
1312 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
1313 | 12 | # GNU Lesser General Public License for more details. | ||
1314 | 13 | # | ||
1315 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
1316 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
1317 | 16 | |||
1318 | 17 | """Ubuntu UI Toolkit autopilot tests and emulators - top level package.""" | ||
1319 | 18 | 0 | ||
1320 | === added directory 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects' | |||
1321 | === renamed file 'tests/autopilot/ubuntuuitoolkit/emulators.py' => 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/__init__.py' | |||
1322 | --- tests/autopilot/ubuntuuitoolkit/emulators.py 2014-04-08 14:46:25 +0000 | |||
1323 | +++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/__init__.py 2014-04-17 01:29:24 +0000 | |||
1324 | @@ -14,784 +14,69 @@ | |||
1325 | 14 | # You should have received a copy of the GNU Lesser General Public License | 14 | # You should have received a copy of the GNU Lesser General Public License |
1326 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
1327 | 16 | 16 | ||
2109 | 17 | import logging | 17 | """Ubuntu UI Toolkit Autopilot custom proxy objects.""" |
2110 | 18 | from distutils import version | 18 | |
2111 | 19 | 19 | ||
2112 | 20 | import autopilot | 20 | __all__ = [ |
2113 | 21 | from autopilot import ( | 21 | 'ActionSelectionPopover', |
2114 | 22 | input, | 22 | 'Base', |
2115 | 23 | logging as autopilot_logging, | 23 | 'check_autopilot_version', |
2116 | 24 | platform | 24 | 'CheckBox', |
2117 | 25 | ) | 25 | 'ComposerSheet', |
2118 | 26 | from autopilot.introspection import dbus | 26 | 'Empty', |
2119 | 27 | 27 | 'Flickable', | |
2120 | 28 | 28 | 'get_keyboard', | |
2121 | 29 | _NO_TABS_ERROR = 'The MainView has no Tabs.' | 29 | 'get_pointing_device', |
2122 | 30 | 30 | 'Header', | |
2123 | 31 | logger = logging.getLogger(__name__) | 31 | 'ItemSelector', |
2124 | 32 | 32 | 'MainView', | |
2125 | 33 | 33 | 'MultiValue', | |
2126 | 34 | class ToolkitEmulatorException(Exception): | 34 | 'OptionSelector', |
2127 | 35 | """Exception raised when there is an error with the emulator.""" | 35 | 'QQuickListView', |
2128 | 36 | 36 | 'SingleControl', | |
2129 | 37 | 37 | 'SingleValue', | |
2130 | 38 | def get_pointing_device(): | 38 | 'Standard', |
2131 | 39 | """Return the pointing device depending on the platform. | 39 | 'Subtitled', |
2132 | 40 | 40 | 'TabBar', | |
2133 | 41 | If the platform is `Desktop`, the pointing device will be a `Mouse`. | 41 | 'Tabs', |
2134 | 42 | If not, the pointing device will be `Touch`. | 42 | 'TextField', |
2135 | 43 | 43 | 'Toolbar', | |
2136 | 44 | """ | 44 | 'ToolkitException', |
2137 | 45 | if platform.model() == 'Desktop': | 45 | 'UbuntuUIToolkitCustomProxyObjectBase', |
2138 | 46 | input_device_class = input.Mouse | 46 | ] |
2139 | 47 | else: | 47 | |
2140 | 48 | input_device_class = input.Touch | 48 | from ubuntuuitoolkit._custom_proxy_objects._checkbox import CheckBox |
2141 | 49 | return input.Pointer(device=input_device_class.create()) | 49 | from ubuntuuitoolkit._custom_proxy_objects._common import ( |
2142 | 50 | 50 | check_autopilot_version, | |
2143 | 51 | 51 | get_keyboard, | |
2144 | 52 | def get_keyboard(): | 52 | get_pointing_device, |
2145 | 53 | """Return the keyboard device.""" | 53 | ToolkitException, |
2146 | 54 | # TODO return the OSK if we are on the phone. --elopio - 2014-01-13 | 54 | UbuntuUIToolkitCustomProxyObjectBase, |
2147 | 55 | return input.Keyboard.create() | 55 | ) |
2148 | 56 | 56 | from ubuntuuitoolkit._custom_proxy_objects._flickable import Flickable | |
2149 | 57 | 57 | from ubuntuuitoolkit._custom_proxy_objects._header import Header | |
2150 | 58 | def check_autopilot_version(): | 58 | from ubuntuuitoolkit._custom_proxy_objects._listitems import ( |
2151 | 59 | """Check that the Autopilot installed version matches the one required. | 59 | Base, |
2152 | 60 | 60 | Empty, | |
2153 | 61 | :raise ToolkitEmulatorException: If the installed Autopilot version does't | 61 | ItemSelector, |
2154 | 62 | match the required by the emulators. | 62 | MultiValue, |
2155 | 63 | 63 | SingleControl, | |
2156 | 64 | """ | 64 | SingleValue, |
2157 | 65 | installed_version = version.LooseVersion(autopilot.version) | 65 | Standard, |
2158 | 66 | if installed_version < version.LooseVersion('1.4'): | 66 | Subtitled, |
2159 | 67 | raise ToolkitEmulatorException( | 67 | ) |
2160 | 68 | 'The emulators need Autopilot 1.4 or higher.') | 68 | from ubuntuuitoolkit._custom_proxy_objects._mainview import MainView |
2161 | 69 | 69 | from ubuntuuitoolkit._custom_proxy_objects._optionselector import ( | |
2162 | 70 | 70 | OptionSelector | |
2163 | 71 | # Containers helpers. | 71 | ) |
2164 | 72 | 72 | from ubuntuuitoolkit._custom_proxy_objects._popups import ( | |
2165 | 73 | def _get_visible_container_top(containers): | 73 | ActionSelectionPopover, |
2166 | 74 | containers_top = [container.globalRect.y for container in containers] | 74 | ComposerSheet, |
2167 | 75 | return max(containers_top) | 75 | ) |
2168 | 76 | 76 | from ubuntuuitoolkit._custom_proxy_objects._qquicklistview import ( | |
2169 | 77 | 77 | QQuickListView | |
2170 | 78 | def _get_visible_container_bottom(containers): | 78 | ) |
2171 | 79 | containers_bottom = [ | 79 | from ubuntuuitoolkit._custom_proxy_objects._tabbar import TabBar |
2172 | 80 | container.globalRect.y + container.globalRect.height | 80 | from ubuntuuitoolkit._custom_proxy_objects._tabs import Tabs |
2173 | 81 | for container in containers if container.globalRect.height > 0] | 81 | from ubuntuuitoolkit._custom_proxy_objects._textfield import TextField |
2174 | 82 | return min(containers_bottom) | 82 | from ubuntuuitoolkit._custom_proxy_objects._toolbar import Toolbar |
1394 | 83 | |||
1395 | 84 | |||
1396 | 85 | class UbuntuUIToolkitEmulatorBase(dbus.CustomEmulatorBase): | ||
1397 | 86 | """A base class for all the Ubuntu UI Toolkit emulators.""" | ||
1398 | 87 | |||
1399 | 88 | def __init__(self, *args): | ||
1400 | 89 | check_autopilot_version() | ||
1401 | 90 | super(UbuntuUIToolkitEmulatorBase, self).__init__(*args) | ||
1402 | 91 | self.pointing_device = get_pointing_device() | ||
1403 | 92 | |||
1404 | 93 | |||
1405 | 94 | class MainView(UbuntuUIToolkitEmulatorBase): | ||
1406 | 95 | """MainView Autopilot emulator.""" | ||
1407 | 96 | |||
1408 | 97 | def get_header(self): | ||
1409 | 98 | """Return the Header emulator of the MainView.""" | ||
1410 | 99 | try: | ||
1411 | 100 | return self.select_single('Header', objectName='MainView_Header') | ||
1412 | 101 | except dbus.StateNotFoundError: | ||
1413 | 102 | raise ToolkitEmulatorException('The main view has no header.') | ||
1414 | 103 | |||
1415 | 104 | def get_toolbar(self): | ||
1416 | 105 | """Return the Toolbar emulator of the MainView.""" | ||
1417 | 106 | return self.select_single(Toolbar) | ||
1418 | 107 | |||
1419 | 108 | @autopilot_logging.log_action(logger.info) | ||
1420 | 109 | def open_toolbar(self): | ||
1421 | 110 | """Open the toolbar if it's not already opened. | ||
1422 | 111 | |||
1423 | 112 | :return: The toolbar. | ||
1424 | 113 | |||
1425 | 114 | """ | ||
1426 | 115 | return self.get_toolbar().open() | ||
1427 | 116 | |||
1428 | 117 | @autopilot_logging.log_action(logger.info) | ||
1429 | 118 | def close_toolbar(self): | ||
1430 | 119 | """Close the toolbar if it's opened.""" | ||
1431 | 120 | self.get_toolbar().close() | ||
1432 | 121 | |||
1433 | 122 | def get_tabs(self): | ||
1434 | 123 | """Return the Tabs emulator of the MainView. | ||
1435 | 124 | |||
1436 | 125 | :raise ToolkitEmulatorException: If the main view has no tabs. | ||
1437 | 126 | |||
1438 | 127 | """ | ||
1439 | 128 | try: | ||
1440 | 129 | return self.select_single(Tabs) | ||
1441 | 130 | except dbus.StateNotFoundError: | ||
1442 | 131 | raise ToolkitEmulatorException(_NO_TABS_ERROR) | ||
1443 | 132 | |||
1444 | 133 | @autopilot_logging.log_action(logger.info) | ||
1445 | 134 | def switch_to_next_tab(self): | ||
1446 | 135 | """Open the next tab. | ||
1447 | 136 | |||
1448 | 137 | :return: The newly opened tab. | ||
1449 | 138 | |||
1450 | 139 | """ | ||
1451 | 140 | logger.debug('Switch to next tab.') | ||
1452 | 141 | self.get_header().switch_to_next_tab() | ||
1453 | 142 | current_tab = self.get_tabs().get_current_tab() | ||
1454 | 143 | current_tab.visible.wait_for(True) | ||
1455 | 144 | return current_tab | ||
1456 | 145 | |||
1457 | 146 | @autopilot_logging.log_action(logger.info) | ||
1458 | 147 | def switch_to_tab_by_index(self, index): | ||
1459 | 148 | """Open a tab. | ||
1460 | 149 | |||
1461 | 150 | :parameter index: The index of the tab to open. | ||
1462 | 151 | :return: The newly opened tab. | ||
1463 | 152 | :raise ToolkitEmulatorException: If the tab index is out of range. | ||
1464 | 153 | |||
1465 | 154 | """ | ||
1466 | 155 | logger.debug('Switch to tab with index {0}.'.format(index)) | ||
1467 | 156 | tabs = self.get_tabs() | ||
1468 | 157 | number_of_tabs = tabs.get_number_of_tabs() | ||
1469 | 158 | if index >= number_of_tabs: | ||
1470 | 159 | raise ToolkitEmulatorException('Tab index out of range.') | ||
1471 | 160 | current_tab = tabs.get_current_tab() | ||
1472 | 161 | number_of_switches = 0 | ||
1473 | 162 | while not tabs.selectedTabIndex == index: | ||
1474 | 163 | logger.debug( | ||
1475 | 164 | 'Current tab index: {0}.'.format(tabs.selectedTabIndex)) | ||
1476 | 165 | if number_of_switches >= number_of_tabs - 1: | ||
1477 | 166 | # This prevents a loop. But if this error is ever raised, it's | ||
1478 | 167 | # likely there's a bug on the emulator or on the QML Tab. | ||
1479 | 168 | raise ToolkitEmulatorException( | ||
1480 | 169 | 'The tab with index {0} was not selected.'.format(index)) | ||
1481 | 170 | current_tab = self.switch_to_next_tab() | ||
1482 | 171 | number_of_switches += 1 | ||
1483 | 172 | return current_tab | ||
1484 | 173 | |||
1485 | 174 | @autopilot_logging.log_action(logger.info) | ||
1486 | 175 | def switch_to_previous_tab(self): | ||
1487 | 176 | """Open the previous tab. | ||
1488 | 177 | |||
1489 | 178 | :return: The newly opened tab. | ||
1490 | 179 | |||
1491 | 180 | """ | ||
1492 | 181 | tabs = self.get_tabs() | ||
1493 | 182 | if tabs.selectedTabIndex == 0: | ||
1494 | 183 | previous_tab_index = tabs.get_number_of_tabs() - 1 | ||
1495 | 184 | else: | ||
1496 | 185 | previous_tab_index = tabs.selectedTabIndex - 1 | ||
1497 | 186 | return self.switch_to_tab_by_index(previous_tab_index) | ||
1498 | 187 | |||
1499 | 188 | @autopilot_logging.log_action(logger.info) | ||
1500 | 189 | def switch_to_tab(self, object_name): | ||
1501 | 190 | """Open a tab. | ||
1502 | 191 | |||
1503 | 192 | :parameter object_name: The QML objectName property of the tab. | ||
1504 | 193 | :return: The newly opened tab. | ||
1505 | 194 | :raise ToolkitEmulatorException: If there is no tab with that object | ||
1506 | 195 | name. | ||
1507 | 196 | |||
1508 | 197 | """ | ||
1509 | 198 | tabs = self.get_tabs() | ||
1510 | 199 | for index, tab in enumerate(tabs.select_many('Tab')): | ||
1511 | 200 | if tab.objectName == object_name: | ||
1512 | 201 | return self.switch_to_tab_by_index(tab.index) | ||
1513 | 202 | raise ToolkitEmulatorException( | ||
1514 | 203 | 'Tab with objectName "{0}" not found.'.format(object_name)) | ||
1515 | 204 | |||
1516 | 205 | def get_action_selection_popover(self, object_name): | ||
1517 | 206 | """Return an ActionSelectionPopover emulator. | ||
1518 | 207 | |||
1519 | 208 | :parameter object_name: The QML objectName property of the popover. | ||
1520 | 209 | |||
1521 | 210 | """ | ||
1522 | 211 | return self.select_single( | ||
1523 | 212 | ActionSelectionPopover, objectName=object_name) | ||
1524 | 213 | |||
1525 | 214 | @autopilot_logging.log_action(logger.info) | ||
1526 | 215 | def go_back(self): | ||
1527 | 216 | """Go to the previous page.""" | ||
1528 | 217 | toolbar = self.open_toolbar() | ||
1529 | 218 | toolbar.click_back_button() | ||
1530 | 219 | |||
1531 | 220 | |||
1532 | 221 | class Header(UbuntuUIToolkitEmulatorBase): | ||
1533 | 222 | """Header Autopilot emulator.""" | ||
1534 | 223 | |||
1535 | 224 | def __init__(self, *args): | ||
1536 | 225 | super(Header, self).__init__(*args) | ||
1537 | 226 | self.pointing_device = get_pointing_device() | ||
1538 | 227 | |||
1539 | 228 | def _get_animating(self): | ||
1540 | 229 | tab_bar_style = self.select_single('TabBarStyle') | ||
1541 | 230 | return tab_bar_style.animating | ||
1542 | 231 | |||
1543 | 232 | @autopilot_logging.log_action(logger.info) | ||
1544 | 233 | def switch_to_next_tab(self): | ||
1545 | 234 | """Open the next tab. | ||
1546 | 235 | |||
1547 | 236 | :raise ToolkitEmulatorException: If the main view has no tabs. | ||
1548 | 237 | |||
1549 | 238 | """ | ||
1550 | 239 | try: | ||
1551 | 240 | tab_bar = self.select_single(TabBar) | ||
1552 | 241 | except dbus.StateNotFoundError: | ||
1553 | 242 | raise ToolkitEmulatorException(_NO_TABS_ERROR) | ||
1554 | 243 | tab_bar.switch_to_next_tab() | ||
1555 | 244 | self._get_animating().wait_for(False) | ||
1556 | 245 | |||
1557 | 246 | |||
1558 | 247 | class Toolbar(UbuntuUIToolkitEmulatorBase): | ||
1559 | 248 | """Toolbar Autopilot emulator.""" | ||
1560 | 249 | |||
1561 | 250 | @autopilot_logging.log_action(logger.info) | ||
1562 | 251 | def open(self): | ||
1563 | 252 | """Open the toolbar if it's not already opened. | ||
1564 | 253 | |||
1565 | 254 | :return: The toolbar. | ||
1566 | 255 | |||
1567 | 256 | """ | ||
1568 | 257 | self.animating.wait_for(False) | ||
1569 | 258 | if not self.opened: | ||
1570 | 259 | self._drag_to_open() | ||
1571 | 260 | self.opened.wait_for(True) | ||
1572 | 261 | self.animating.wait_for(False) | ||
1573 | 262 | |||
1574 | 263 | return self | ||
1575 | 264 | |||
1576 | 265 | def _drag_to_open(self): | ||
1577 | 266 | x, y, _, _ = self.globalRect | ||
1578 | 267 | line_x = x + self.width * 0.50 | ||
1579 | 268 | start_y = y + self.height - 1 | ||
1580 | 269 | stop_y = y | ||
1581 | 270 | |||
1582 | 271 | self.pointing_device.drag(line_x, start_y, line_x, stop_y) | ||
1583 | 272 | |||
1584 | 273 | @autopilot_logging.log_action(logger.info) | ||
1585 | 274 | def close(self): | ||
1586 | 275 | """Close the toolbar if it's opened.""" | ||
1587 | 276 | self.animating.wait_for(False) | ||
1588 | 277 | if self.opened: | ||
1589 | 278 | self._drag_to_close() | ||
1590 | 279 | self.opened.wait_for(False) | ||
1591 | 280 | self.animating.wait_for(False) | ||
1592 | 281 | |||
1593 | 282 | def _drag_to_close(self): | ||
1594 | 283 | x, y, _, _ = self.globalRect | ||
1595 | 284 | line_x = x + self.width * 0.50 | ||
1596 | 285 | start_y = y | ||
1597 | 286 | stop_y = y + self.height - 1 | ||
1598 | 287 | |||
1599 | 288 | self.pointing_device.drag(line_x, start_y, line_x, stop_y) | ||
1600 | 289 | |||
1601 | 290 | @autopilot_logging.log_action(logger.info) | ||
1602 | 291 | def click_button(self, object_name): | ||
1603 | 292 | """Click a button of the toolbar. | ||
1604 | 293 | |||
1605 | 294 | The toolbar should be opened before clicking the button, or an | ||
1606 | 295 | exception will be raised. If the toolbar is closed for some reason | ||
1607 | 296 | (e.g., timer finishes) after moving the mouse cursor and before | ||
1608 | 297 | clicking the button, it is re-opened automatically by this function. | ||
1609 | 298 | |||
1610 | 299 | :parameter object_name: The QML objectName property of the button. | ||
1611 | 300 | :raise ToolkitEmulatorException: If there is no button with that object | ||
1612 | 301 | name. | ||
1613 | 302 | |||
1614 | 303 | """ | ||
1615 | 304 | # ensure the toolbar is open | ||
1616 | 305 | if not self.opened: | ||
1617 | 306 | raise ToolkitEmulatorException( | ||
1618 | 307 | 'Toolbar must be opened before calling click_button().') | ||
1619 | 308 | try: | ||
1620 | 309 | button = self._get_button(object_name) | ||
1621 | 310 | except dbus.StateNotFoundError: | ||
1622 | 311 | raise ToolkitEmulatorException( | ||
1623 | 312 | 'Button with objectName "{0}" not found.'.format(object_name)) | ||
1624 | 313 | self.pointing_device.move_to_object(button) | ||
1625 | 314 | # ensure the toolbar is still open (may have closed due to timeout) | ||
1626 | 315 | self.open() | ||
1627 | 316 | # click the button | ||
1628 | 317 | self.pointing_device.click_object(button) | ||
1629 | 318 | |||
1630 | 319 | def _get_button(self, object_name): | ||
1631 | 320 | return self.select_single('ActionItem', objectName=object_name) | ||
1632 | 321 | |||
1633 | 322 | @autopilot_logging.log_action(logger.info) | ||
1634 | 323 | def click_back_button(self): | ||
1635 | 324 | """Click the back button of the toolbar.""" | ||
1636 | 325 | self.click_button('back_toolbar_button') | ||
1637 | 326 | |||
1638 | 327 | |||
1639 | 328 | class Tabs(UbuntuUIToolkitEmulatorBase): | ||
1640 | 329 | """Tabs Autopilot emulator.""" | ||
1641 | 330 | |||
1642 | 331 | def get_current_tab(self): | ||
1643 | 332 | """Return the currently selected tab.""" | ||
1644 | 333 | return self._get_tab(self.selectedTabIndex) | ||
1645 | 334 | |||
1646 | 335 | def _get_tab(self, index): | ||
1647 | 336 | tabs = self._get_tabs() | ||
1648 | 337 | for tab in tabs: | ||
1649 | 338 | if tab.index == index: | ||
1650 | 339 | return tab | ||
1651 | 340 | else: | ||
1652 | 341 | raise ToolkitEmulatorException( | ||
1653 | 342 | 'There is no tab with index {0}.'.format(index)) | ||
1654 | 343 | |||
1655 | 344 | def _get_tabs(self): | ||
1656 | 345 | return self.select_many('Tab') | ||
1657 | 346 | |||
1658 | 347 | def get_number_of_tabs(self): | ||
1659 | 348 | """Return the number of tabs.""" | ||
1660 | 349 | return len(self._get_tabs()) | ||
1661 | 350 | |||
1662 | 351 | |||
1663 | 352 | class TabBar(UbuntuUIToolkitEmulatorBase): | ||
1664 | 353 | """TabBar Autopilot emulator.""" | ||
1665 | 354 | |||
1666 | 355 | @autopilot_logging.log_action(logger.info) | ||
1667 | 356 | def switch_to_next_tab(self): | ||
1668 | 357 | """Open the next tab.""" | ||
1669 | 358 | self._activate_tab_bar() | ||
1670 | 359 | logger.debug('Click the next tab bar button.') | ||
1671 | 360 | self.pointing_device.click_object(self._get_next_tab_button()) | ||
1672 | 361 | |||
1673 | 362 | def _activate_tab_bar(self): | ||
1674 | 363 | # First move to the tab bar to avoid timing issues when we find it in | ||
1675 | 364 | # selection mode but it's deselected while we move to it. | ||
1676 | 365 | self.pointing_device.move_to_object(self) | ||
1677 | 366 | if self.selectionMode: | ||
1678 | 367 | logger.debug('Already in selection mode.') | ||
1679 | 368 | else: | ||
1680 | 369 | # Click the tab bar to switch to selection mode. | ||
1681 | 370 | logger.debug('Click the tab bar to enable selection mode.') | ||
1682 | 371 | self.pointing_device.click_object(self) | ||
1683 | 372 | |||
1684 | 373 | def _get_next_tab_button(self): | ||
1685 | 374 | current_index = self._get_selected_button_index() | ||
1686 | 375 | next_index = (current_index + 1) % self._get_number_of_tab_buttons() | ||
1687 | 376 | return self._get_tab_button(next_index) | ||
1688 | 377 | |||
1689 | 378 | def _get_selected_button_index(self): | ||
1690 | 379 | return self.select_single('QQuickPathView').selectedButtonIndex | ||
1691 | 380 | |||
1692 | 381 | def _get_number_of_tab_buttons(self): | ||
1693 | 382 | return len(self._get_tab_buttons()) | ||
1694 | 383 | |||
1695 | 384 | def _get_tab_buttons(self): | ||
1696 | 385 | return self.select_many('AbstractButton') | ||
1697 | 386 | |||
1698 | 387 | def _get_tab_button(self, index): | ||
1699 | 388 | buttons = self._get_tab_buttons() | ||
1700 | 389 | for button in buttons: | ||
1701 | 390 | if button.buttonIndex == index: | ||
1702 | 391 | return button | ||
1703 | 392 | raise ToolkitEmulatorException( | ||
1704 | 393 | 'There is no tab button with index {0}.'.format(index)) | ||
1705 | 394 | |||
1706 | 395 | |||
1707 | 396 | class ActionSelectionPopover(UbuntuUIToolkitEmulatorBase): | ||
1708 | 397 | """ActionSelectionPopover Autopilot emulator.""" | ||
1709 | 398 | |||
1710 | 399 | def click_button_by_text(self, text): | ||
1711 | 400 | """Click a button on the popover. | ||
1712 | 401 | |||
1713 | 402 | XXX We are receiving the text because there's no way to set the | ||
1714 | 403 | objectName on the action. This is reported at | ||
1715 | 404 | https://bugs.launchpad.net/ubuntu-ui-toolkit/+bug/1205144 | ||
1716 | 405 | --elopio - 2013-07-25 | ||
1717 | 406 | |||
1718 | 407 | :parameter text: The text of the button. | ||
1719 | 408 | :raise ToolkitEmulatorException: If the popover is not open. | ||
1720 | 409 | |||
1721 | 410 | """ | ||
1722 | 411 | if not self.visible: | ||
1723 | 412 | raise ToolkitEmulatorException('The popover is not open.') | ||
1724 | 413 | button = self._get_button(text) | ||
1725 | 414 | if button is None: | ||
1726 | 415 | raise ToolkitEmulatorException( | ||
1727 | 416 | 'Button with text "{0}" not found.'.format(text)) | ||
1728 | 417 | self.pointing_device.click_object(button) | ||
1729 | 418 | if self.autoClose: | ||
1730 | 419 | try: | ||
1731 | 420 | self.visible.wait_for(False) | ||
1732 | 421 | except dbus.StateNotFoundError: | ||
1733 | 422 | # The popover was removed from the tree. | ||
1734 | 423 | pass | ||
1735 | 424 | |||
1736 | 425 | def _get_button(self, text): | ||
1737 | 426 | buttons = self.select_many('Empty') | ||
1738 | 427 | for button in buttons: | ||
1739 | 428 | if button.text == text: | ||
1740 | 429 | return button | ||
1741 | 430 | |||
1742 | 431 | |||
1743 | 432 | class CheckBox(UbuntuUIToolkitEmulatorBase): | ||
1744 | 433 | """CheckBox Autopilot emulator.""" | ||
1745 | 434 | |||
1746 | 435 | @autopilot_logging.log_action(logger.info) | ||
1747 | 436 | def check(self, timeout=10): | ||
1748 | 437 | """Check a CheckBox, if its not already checked. | ||
1749 | 438 | |||
1750 | 439 | :parameter timeout: number of seconds to wait for the CheckBox to be | ||
1751 | 440 | checked. Default is 10. | ||
1752 | 441 | |||
1753 | 442 | """ | ||
1754 | 443 | if not self.checked: | ||
1755 | 444 | self.change_state(timeout) | ||
1756 | 445 | |||
1757 | 446 | @autopilot_logging.log_action(logger.info) | ||
1758 | 447 | def uncheck(self, timeout=10): | ||
1759 | 448 | """Uncheck a CheckBox, if its not already unchecked. | ||
1760 | 449 | |||
1761 | 450 | :parameter timeout: number of seconds to wait for the CheckBox to be | ||
1762 | 451 | unchecked. Default is 10. | ||
1763 | 452 | |||
1764 | 453 | """ | ||
1765 | 454 | if self.checked: | ||
1766 | 455 | self.change_state(timeout) | ||
1767 | 456 | |||
1768 | 457 | @autopilot_logging.log_action(logger.info) | ||
1769 | 458 | def change_state(self, timeout=10): | ||
1770 | 459 | """Change the state of a CheckBox. | ||
1771 | 460 | |||
1772 | 461 | If it is checked, it will be unchecked. If it is unchecked, it will be | ||
1773 | 462 | checked. | ||
1774 | 463 | |||
1775 | 464 | :parameter time_out: number of seconds to wait for the CheckBox state | ||
1776 | 465 | to change. Default is 10. | ||
1777 | 466 | |||
1778 | 467 | """ | ||
1779 | 468 | original_state = self.checked | ||
1780 | 469 | self.pointing_device.click_object(self) | ||
1781 | 470 | self.checked.wait_for(not original_state, timeout) | ||
1782 | 471 | |||
1783 | 472 | |||
1784 | 473 | class TextField(UbuntuUIToolkitEmulatorBase): | ||
1785 | 474 | """TextField Autopilot emulator.""" | ||
1786 | 475 | |||
1787 | 476 | def __init__(self, *args): | ||
1788 | 477 | super(TextField, self).__init__(*args) | ||
1789 | 478 | self.keyboard = get_keyboard() | ||
1790 | 479 | |||
1791 | 480 | def write(self, text, clear=True): | ||
1792 | 481 | """Write into the text field. | ||
1793 | 482 | |||
1794 | 483 | :parameter text: The text to write. | ||
1795 | 484 | :parameter clear: If True, the text field will be cleared before | ||
1796 | 485 | writing the text. If False, the text will be appended at the end | ||
1797 | 486 | of the text field. Default is True. | ||
1798 | 487 | |||
1799 | 488 | """ | ||
1800 | 489 | with self.keyboard.focused_type(self): | ||
1801 | 490 | self.focus.wait_for(True) | ||
1802 | 491 | if clear: | ||
1803 | 492 | self.clear() | ||
1804 | 493 | else: | ||
1805 | 494 | if not self.is_empty(): | ||
1806 | 495 | self.keyboard.press_and_release('End') | ||
1807 | 496 | self.keyboard.type(text) | ||
1808 | 497 | |||
1809 | 498 | def clear(self): | ||
1810 | 499 | """Clear the text field.""" | ||
1811 | 500 | if not self.is_empty(): | ||
1812 | 501 | if self.hasClearButton: | ||
1813 | 502 | self._click_clear_button() | ||
1814 | 503 | else: | ||
1815 | 504 | self._clear_with_keys() | ||
1816 | 505 | self.text.wait_for('') | ||
1817 | 506 | |||
1818 | 507 | def is_empty(self): | ||
1819 | 508 | """Return True if the text field is empty. False otherwise.""" | ||
1820 | 509 | return self.text == '' | ||
1821 | 510 | |||
1822 | 511 | def _click_clear_button(self): | ||
1823 | 512 | clear_button = self.select_single( | ||
1824 | 513 | 'AbstractButton', objectName='clear_button') | ||
1825 | 514 | if not clear_button.visible: | ||
1826 | 515 | self.pointing_device.click_object(self) | ||
1827 | 516 | self.pointing_device.click_object(clear_button) | ||
1828 | 517 | |||
1829 | 518 | def _clear_with_keys(self): | ||
1830 | 519 | if platform.model() == 'Desktop': | ||
1831 | 520 | self._select_all() | ||
1832 | 521 | else: | ||
1833 | 522 | # Touch tap currently doesn't have a press_duration parameter, so | ||
1834 | 523 | # we can't show the popover. Reported as bug http://pad.lv/1268782 | ||
1835 | 524 | # --elopio - 2014-01-13 | ||
1836 | 525 | self.keyboard.press_and_release('End') | ||
1837 | 526 | while not self.is_empty(): | ||
1838 | 527 | # We delete with backspace because the on-screen keyboard has that | ||
1839 | 528 | # key. | ||
1840 | 529 | self.keyboard.press_and_release('BackSpace') | ||
1841 | 530 | |||
1842 | 531 | def _select_all(self): | ||
1843 | 532 | self.pointing_device.click_object(self, press_duration=1) | ||
1844 | 533 | root = self.get_root_instance() | ||
1845 | 534 | main_view = root.select_single(MainView) | ||
1846 | 535 | popover = main_view.get_action_selection_popover('text_input_popover') | ||
1847 | 536 | popover.click_button_by_text('Select All') | ||
1848 | 537 | |||
1849 | 538 | |||
1850 | 539 | class Flickable(UbuntuUIToolkitEmulatorBase): | ||
1851 | 540 | |||
1852 | 541 | @autopilot_logging.log_action(logger.info) | ||
1853 | 542 | def swipe_child_into_view(self, child): | ||
1854 | 543 | """Make the child visible. | ||
1855 | 544 | |||
1856 | 545 | Currently it works only when the object needs to be swiped vertically. | ||
1857 | 546 | TODO implement horizontal swiping. --elopio - 2014-03-21 | ||
1858 | 547 | |||
1859 | 548 | """ | ||
1860 | 549 | containers = self._get_containers() | ||
1861 | 550 | if not self._is_child_visible(child, containers): | ||
1862 | 551 | self._swipe_non_visible_child_into_view(child, containers) | ||
1863 | 552 | else: | ||
1864 | 553 | logger.debug('The element is already visible.') | ||
1865 | 554 | |||
1866 | 555 | def _get_containers(self): | ||
1867 | 556 | """Return a list with the containers to take into account when swiping. | ||
1868 | 557 | |||
1869 | 558 | The list includes this flickable and the top-most container. | ||
1870 | 559 | TODO add additional flickables that are between this and the top | ||
1871 | 560 | container. --elopio - 2014-03-22 | ||
1872 | 561 | |||
1873 | 562 | """ | ||
1874 | 563 | containers = [self._get_top_container(), self] | ||
1875 | 564 | return containers | ||
1876 | 565 | |||
1877 | 566 | def _get_top_container(self): | ||
1878 | 567 | """Return the top-most container with a globalRect.""" | ||
1879 | 568 | root = self.get_root_instance() | ||
1880 | 569 | containers = [root] | ||
1881 | 570 | while len(containers) == 1: | ||
1882 | 571 | try: | ||
1883 | 572 | containers[0].globalRect | ||
1884 | 573 | return containers[0] | ||
1885 | 574 | except AttributeError: | ||
1886 | 575 | containers = containers[0].get_children() | ||
1887 | 576 | |||
1888 | 577 | raise ToolkitEmulatorException("Couldn't find the top-most container.") | ||
1889 | 578 | |||
1890 | 579 | def _is_child_visible(self, child, containers): | ||
1891 | 580 | """Check if the center of the child is visible. | ||
1892 | 581 | |||
1893 | 582 | :return: True if the center of the child is visible, False otherwise. | ||
1894 | 583 | |||
1895 | 584 | """ | ||
1896 | 585 | object_center = child.globalRect.y + child.globalRect.height // 2 | ||
1897 | 586 | visible_top = _get_visible_container_top(containers) | ||
1898 | 587 | visible_bottom = _get_visible_container_bottom(containers) | ||
1899 | 588 | return (object_center >= visible_top and | ||
1900 | 589 | object_center <= visible_bottom) | ||
1901 | 590 | |||
1902 | 591 | @autopilot_logging.log_action(logger.info) | ||
1903 | 592 | def _swipe_non_visible_child_into_view(self, child, containers): | ||
1904 | 593 | while not self._is_child_visible(child, containers): | ||
1905 | 594 | # Check the direction of the swipe based on the position of the | ||
1906 | 595 | # child relative to the immediate flickable container. | ||
1907 | 596 | if child.globalRect.y < self.globalRect.y: | ||
1908 | 597 | self._swipe_to_show_more_above(containers) | ||
1909 | 598 | else: | ||
1910 | 599 | self._swipe_to_show_more_below(containers) | ||
1911 | 600 | |||
1912 | 601 | @autopilot_logging.log_action(logger.info) | ||
1913 | 602 | def _swipe_to_show_more_above(self, containers): | ||
1914 | 603 | if self.atYBeginning: | ||
1915 | 604 | raise ToolkitEmulatorException( | ||
1916 | 605 | "Can't swipe more, we are already at the top of the " | ||
1917 | 606 | "container.") | ||
1918 | 607 | else: | ||
1919 | 608 | self._swipe_to_show_more('above', containers) | ||
1920 | 609 | |||
1921 | 610 | @autopilot_logging.log_action(logger.info) | ||
1922 | 611 | def _swipe_to_show_more_below(self, containers): | ||
1923 | 612 | if self.atYEnd: | ||
1924 | 613 | raise ToolkitEmulatorException( | ||
1925 | 614 | "Can't swipe more, we are already at the bottom of the " | ||
1926 | 615 | "container.") | ||
1927 | 616 | else: | ||
1928 | 617 | self._swipe_to_show_more('below', containers) | ||
1929 | 618 | |||
1930 | 619 | def _swipe_to_show_more(self, direction, containers): | ||
1931 | 620 | start_x = stop_x = self.globalRect.x + (self.globalRect.width // 2) | ||
1932 | 621 | # Start and stop just a little under the top and a little over the | ||
1933 | 622 | # bottom. | ||
1934 | 623 | top = _get_visible_container_top(containers) + 5 | ||
1935 | 624 | bottom = _get_visible_container_bottom(containers) - 5 | ||
1936 | 625 | if direction == 'below': | ||
1937 | 626 | start_y = bottom | ||
1938 | 627 | stop_y = top | ||
1939 | 628 | elif direction == 'above': | ||
1940 | 629 | start_y = top | ||
1941 | 630 | stop_y = bottom | ||
1942 | 631 | else: | ||
1943 | 632 | raise ToolkitEmulatorException( | ||
1944 | 633 | 'Invalid direction {}.'.format(direction)) | ||
1945 | 634 | self._slow_drag(start_x, stop_x, start_y, stop_y) | ||
1946 | 635 | self.dragging.wait_for(False) | ||
1947 | 636 | self.moving.wait_for(False) | ||
1948 | 637 | |||
1949 | 638 | def _slow_drag(self, start_x, stop_x, start_y, stop_y): | ||
1950 | 639 | # If we drag too fast, we end up scrolling more than what we | ||
1951 | 640 | # should, sometimes missing the element we are looking for. | ||
1952 | 641 | self.pointing_device.drag(start_x, start_y, stop_x, stop_y, rate=5) | ||
1953 | 642 | |||
1954 | 643 | @autopilot_logging.log_action(logger.info) | ||
1955 | 644 | def _scroll_to_top(self): | ||
1956 | 645 | if not self.atYBeginning: | ||
1957 | 646 | containers = self._get_containers() | ||
1958 | 647 | while not self.atYBeginning: | ||
1959 | 648 | self._swipe_to_show_more_above(containers) | ||
1960 | 649 | |||
1961 | 650 | |||
1962 | 651 | class QQuickListView(Flickable): | ||
1963 | 652 | |||
1964 | 653 | @autopilot_logging.log_action(logger.info) | ||
1965 | 654 | def click_element(self, object_name): | ||
1966 | 655 | """Click an element from the list. | ||
1967 | 656 | |||
1968 | 657 | It swipes the element into view if it's center is not visible. | ||
1969 | 658 | |||
1970 | 659 | :parameter objectName: The objectName property of the element to click. | ||
1971 | 660 | |||
1972 | 661 | """ | ||
1973 | 662 | try: | ||
1974 | 663 | element = self.select_single(objectName=object_name) | ||
1975 | 664 | except dbus.StateNotFoundError: | ||
1976 | 665 | # The element might be on a part of the list that hasn't been | ||
1977 | 666 | # created yet. We have to search for it scrolling the entire list. | ||
1978 | 667 | element = self._find_element(object_name) | ||
1979 | 668 | self.swipe_child_into_view(element) | ||
1980 | 669 | self.pointing_device.click_object(element) | ||
1981 | 670 | |||
1982 | 671 | @autopilot_logging.log_action(logger.info) | ||
1983 | 672 | def _find_element(self, object_name): | ||
1984 | 673 | self._scroll_to_top() | ||
1985 | 674 | while not self.atYEnd: | ||
1986 | 675 | containers = self._get_containers() | ||
1987 | 676 | self._swipe_to_show_more_below(containers) | ||
1988 | 677 | try: | ||
1989 | 678 | return self.select_single(objectName=object_name) | ||
1990 | 679 | except dbus.StateNotFoundError: | ||
1991 | 680 | pass | ||
1992 | 681 | raise ToolkitEmulatorException( | ||
1993 | 682 | 'List element with objectName "{}" not found.'.format(object_name)) | ||
1994 | 683 | |||
1995 | 684 | def _is_element_clickable(self, object_name): | ||
1996 | 685 | child = self.select_single(objectName=object_name) | ||
1997 | 686 | containers = self._get_containers() | ||
1998 | 687 | return self._is_child_visible(child, containers) | ||
1999 | 688 | |||
2000 | 689 | |||
2001 | 690 | class Empty(UbuntuUIToolkitEmulatorBase): | ||
2002 | 691 | """Base class to emulate swipe to delete.""" | ||
2003 | 692 | |||
2004 | 693 | def exists(self): | ||
2005 | 694 | try: | ||
2006 | 695 | return self.implicitHeight > 0 | ||
2007 | 696 | except dbus.StateNotFoundError: | ||
2008 | 697 | return False | ||
2009 | 698 | |||
2010 | 699 | def _get_confirm_button(self): | ||
2011 | 700 | return self.select_single( | ||
2012 | 701 | 'QQuickItem', objectName='confirmRemovalDialog') | ||
2013 | 702 | |||
2014 | 703 | @autopilot_logging.log_action(logger.info) | ||
2015 | 704 | def swipe_to_delete(self, direction='right'): | ||
2016 | 705 | """Swipe the item in a specific direction.""" | ||
2017 | 706 | if self.removable: | ||
2018 | 707 | self._drag_pointing_device_to_delete(direction) | ||
2019 | 708 | if self.confirmRemoval: | ||
2020 | 709 | self.waitingConfirmationForRemoval.wait_for(True) | ||
2021 | 710 | else: | ||
2022 | 711 | self._wait_until_deleted() | ||
2023 | 712 | else: | ||
2024 | 713 | raise ToolkitEmulatorException( | ||
2025 | 714 | 'The item "{0}" is not removable'.format(self.objectName)) | ||
2026 | 715 | |||
2027 | 716 | def _drag_pointing_device_to_delete(self, direction): | ||
2028 | 717 | x, y, w, h = self.globalRect | ||
2029 | 718 | tx = x + (w // 8) | ||
2030 | 719 | ty = y + (h // 2) | ||
2031 | 720 | |||
2032 | 721 | if direction == 'right': | ||
2033 | 722 | self.pointing_device.drag(tx, ty, w, ty) | ||
2034 | 723 | elif direction == 'left': | ||
2035 | 724 | self.pointing_device.drag(w - (w*0.1), ty, x, ty) | ||
2036 | 725 | else: | ||
2037 | 726 | raise ToolkitEmulatorException( | ||
2038 | 727 | 'Invalid direction "{0}" used on swipe to delete function' | ||
2039 | 728 | .format(direction)) | ||
2040 | 729 | |||
2041 | 730 | def _wait_until_deleted(self): | ||
2042 | 731 | try: | ||
2043 | 732 | # The item was hidden. | ||
2044 | 733 | self.implicitHeight.wait_for(0) | ||
2045 | 734 | except dbus.StateNotFoundError: | ||
2046 | 735 | # The item was destroyed. | ||
2047 | 736 | pass | ||
2048 | 737 | |||
2049 | 738 | @autopilot_logging.log_action(logger.info) | ||
2050 | 739 | def confirm_removal(self): | ||
2051 | 740 | """Comfirm item removal if this was already swiped.""" | ||
2052 | 741 | if self.waitingConfirmationForRemoval: | ||
2053 | 742 | deleteButton = self._get_confirm_button() | ||
2054 | 743 | self.pointing_device.click_object(deleteButton) | ||
2055 | 744 | self._wait_until_deleted() | ||
2056 | 745 | else: | ||
2057 | 746 | raise ToolkitEmulatorException( | ||
2058 | 747 | 'The item "{0}" is not waiting for removal confirmation'. | ||
2059 | 748 | format(self.objectName)) | ||
2060 | 749 | |||
2061 | 750 | |||
2062 | 751 | class Base(Empty): | ||
2063 | 752 | pass | ||
2064 | 753 | |||
2065 | 754 | |||
2066 | 755 | class Standard(Empty): | ||
2067 | 756 | pass | ||
2068 | 757 | |||
2069 | 758 | |||
2070 | 759 | class ItemSelector(Empty): | ||
2071 | 760 | pass | ||
2072 | 761 | |||
2073 | 762 | |||
2074 | 763 | class SingleControl(Empty): | ||
2075 | 764 | pass | ||
2076 | 765 | |||
2077 | 766 | |||
2078 | 767 | class MultiValue(Base): | ||
2079 | 768 | pass | ||
2080 | 769 | |||
2081 | 770 | |||
2082 | 771 | class SingleValue(Base): | ||
2083 | 772 | pass | ||
2084 | 773 | |||
2085 | 774 | |||
2086 | 775 | class Subtitled(Base): | ||
2087 | 776 | pass | ||
2088 | 777 | |||
2089 | 778 | |||
2090 | 779 | class ComposerSheet(UbuntuUIToolkitEmulatorBase): | ||
2091 | 780 | """ComposerSheet Autopilot emulator.""" | ||
2092 | 781 | |||
2093 | 782 | def __init__(self, *args): | ||
2094 | 783 | super(ComposerSheet, self).__init__(*args) | ||
2095 | 784 | |||
2096 | 785 | @autopilot_logging.log_action(logger.info) | ||
2097 | 786 | def confirm(self): | ||
2098 | 787 | """Confirm the composer sheet.""" | ||
2099 | 788 | button = self.select_single('Button', objectName='confirmButton') | ||
2100 | 789 | self.pointing_device.click_object(button) | ||
2101 | 790 | self.wait_until_destroyed() | ||
2102 | 791 | |||
2103 | 792 | @autopilot_logging.log_action(logger.info) | ||
2104 | 793 | def cancel(self): | ||
2105 | 794 | """Cancel the composer sheet.""" | ||
2106 | 795 | button = self.select_single('Button', objectName='cancelButton') | ||
2107 | 796 | self.pointing_device.click_object(button) | ||
2108 | 797 | self.wait_until_destroyed() | ||
2175 | 798 | 83 | ||
2176 | === added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_checkbox.py' | |||
2177 | --- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_checkbox.py 1970-01-01 00:00:00 +0000 | |||
2178 | +++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_checkbox.py 2014-04-17 01:29:24 +0000 | |||
2179 | @@ -0,0 +1,65 @@ | |||
2180 | 1 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- | ||
2181 | 2 | # | ||
2182 | 3 | # Copyright (C) 2012, 2013, 2014 Canonical Ltd. | ||
2183 | 4 | # | ||
2184 | 5 | # This program is free software; you can redistribute it and/or modify | ||
2185 | 6 | # it under the terms of the GNU Lesser General Public License as published by | ||
2186 | 7 | # the Free Software Foundation; version 3. | ||
2187 | 8 | # | ||
2188 | 9 | # This program is distributed in the hope that it will be useful, | ||
2189 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
2190 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
2191 | 12 | # GNU Lesser General Public License for more details. | ||
2192 | 13 | # | ||
2193 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
2194 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
2195 | 16 | |||
2196 | 17 | import logging | ||
2197 | 18 | |||
2198 | 19 | from autopilot import logging as autopilot_logging | ||
2199 | 20 | |||
2200 | 21 | from ubuntuuitoolkit._custom_proxy_objects import _common | ||
2201 | 22 | |||
2202 | 23 | |||
2203 | 24 | logger = logging.getLogger(__name__) | ||
2204 | 25 | |||
2205 | 26 | |||
2206 | 27 | class CheckBox(_common.UbuntuUIToolkitCustomProxyObjectBase): | ||
2207 | 28 | """CheckBox Autopilot emulator.""" | ||
2208 | 29 | |||
2209 | 30 | @autopilot_logging.log_action(logger.info) | ||
2210 | 31 | def check(self, timeout=10): | ||
2211 | 32 | """Check a CheckBox, if its not already checked. | ||
2212 | 33 | |||
2213 | 34 | :parameter timeout: number of seconds to wait for the CheckBox to be | ||
2214 | 35 | checked. Default is 10. | ||
2215 | 36 | |||
2216 | 37 | """ | ||
2217 | 38 | if not self.checked: | ||
2218 | 39 | self.change_state(timeout) | ||
2219 | 40 | |||
2220 | 41 | @autopilot_logging.log_action(logger.info) | ||
2221 | 42 | def uncheck(self, timeout=10): | ||
2222 | 43 | """Uncheck a CheckBox, if its not already unchecked. | ||
2223 | 44 | |||
2224 | 45 | :parameter timeout: number of seconds to wait for the CheckBox to be | ||
2225 | 46 | unchecked. Default is 10. | ||
2226 | 47 | |||
2227 | 48 | """ | ||
2228 | 49 | if self.checked: | ||
2229 | 50 | self.change_state(timeout) | ||
2230 | 51 | |||
2231 | 52 | @autopilot_logging.log_action(logger.info) | ||
2232 | 53 | def change_state(self, timeout=10): | ||
2233 | 54 | """Change the state of a CheckBox. | ||
2234 | 55 | |||
2235 | 56 | If it is checked, it will be unchecked. If it is unchecked, it will be | ||
2236 | 57 | checked. | ||
2237 | 58 | |||
2238 | 59 | :parameter time_out: number of seconds to wait for the CheckBox state | ||
2239 | 60 | to change. Default is 10. | ||
2240 | 61 | |||
2241 | 62 | """ | ||
2242 | 63 | original_state = self.checked | ||
2243 | 64 | self.pointing_device.click_object(self) | ||
2244 | 65 | self.checked.wait_for(not original_state, timeout) | ||
2245 | 0 | 66 | ||
2246 | === added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_common.py' | |||
2247 | --- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_common.py 1970-01-01 00:00:00 +0000 | |||
2248 | +++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_common.py 2014-04-17 01:29:24 +0000 | |||
2249 | @@ -0,0 +1,69 @@ | |||
2250 | 1 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- | ||
2251 | 2 | # | ||
2252 | 3 | # Copyright (C) 2012, 2013, 2014 Canonical Ltd. | ||
2253 | 4 | # | ||
2254 | 5 | # This program is free software; you can redistribute it and/or modify | ||
2255 | 6 | # it under the terms of the GNU Lesser General Public License as published by | ||
2256 | 7 | # the Free Software Foundation; version 3. | ||
2257 | 8 | # | ||
2258 | 9 | # This program is distributed in the hope that it will be useful, | ||
2259 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
2260 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
2261 | 12 | # GNU Lesser General Public License for more details. | ||
2262 | 13 | # | ||
2263 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
2264 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
2265 | 16 | |||
2266 | 17 | """Common helpers for Ubuntu UI Toolkit Autopilot custom proxy objects.""" | ||
2267 | 18 | |||
2268 | 19 | from distutils import version | ||
2269 | 20 | |||
2270 | 21 | import autopilot | ||
2271 | 22 | from autopilot import platform, input | ||
2272 | 23 | from autopilot.introspection import dbus | ||
2273 | 24 | |||
2274 | 25 | |||
2275 | 26 | class ToolkitException(Exception): | ||
2276 | 27 | """Exception raised when there is an error with the emulator.""" | ||
2277 | 28 | |||
2278 | 29 | |||
2279 | 30 | def get_pointing_device(): | ||
2280 | 31 | """Return the pointing device depending on the platform. | ||
2281 | 32 | |||
2282 | 33 | If the platform is `Desktop`, the pointing device will be a `Mouse`. | ||
2283 | 34 | If not, the pointing device will be `Touch`. | ||
2284 | 35 | |||
2285 | 36 | """ | ||
2286 | 37 | if platform.model() == 'Desktop': | ||
2287 | 38 | input_device_class = input.Mouse | ||
2288 | 39 | else: | ||
2289 | 40 | input_device_class = input.Touch | ||
2290 | 41 | return input.Pointer(device=input_device_class.create()) | ||
2291 | 42 | |||
2292 | 43 | |||
2293 | 44 | def get_keyboard(): | ||
2294 | 45 | """Return the keyboard device.""" | ||
2295 | 46 | # TODO return the OSK if we are on the phone. --elopio - 2014-01-13 | ||
2296 | 47 | return input.Keyboard.create() | ||
2297 | 48 | |||
2298 | 49 | |||
2299 | 50 | def check_autopilot_version(): | ||
2300 | 51 | """Check that the Autopilot installed version matches the one required. | ||
2301 | 52 | |||
2302 | 53 | :raise ToolkitException: If the installed Autopilot version does't | ||
2303 | 54 | match the required by the emulators. | ||
2304 | 55 | |||
2305 | 56 | """ | ||
2306 | 57 | installed_version = version.LooseVersion(autopilot.version) | ||
2307 | 58 | if installed_version < version.LooseVersion('1.4'): | ||
2308 | 59 | raise ToolkitException( | ||
2309 | 60 | 'The emulators need Autopilot 1.4 or higher.') | ||
2310 | 61 | |||
2311 | 62 | |||
2312 | 63 | class UbuntuUIToolkitCustomProxyObjectBase(dbus.CustomEmulatorBase): | ||
2313 | 64 | """A base class for all the Ubuntu UI Toolkit emulators.""" | ||
2314 | 65 | |||
2315 | 66 | def __init__(self, *args): | ||
2316 | 67 | check_autopilot_version() | ||
2317 | 68 | super(UbuntuUIToolkitCustomProxyObjectBase, self).__init__(*args) | ||
2318 | 69 | self.pointing_device = get_pointing_device() | ||
2319 | 0 | 70 | ||
2320 | === added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_flickable.py' | |||
2321 | --- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_flickable.py 1970-01-01 00:00:00 +0000 | |||
2322 | +++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_flickable.py 2014-04-17 01:29:24 +0000 | |||
2323 | @@ -0,0 +1,150 @@ | |||
2324 | 1 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- | ||
2325 | 2 | # | ||
2326 | 3 | # Copyright (C) 2012, 2013, 2014 Canonical Ltd. | ||
2327 | 4 | # | ||
2328 | 5 | # This program is free software; you can redistribute it and/or modify | ||
2329 | 6 | # it under the terms of the GNU Lesser General Public License as published by | ||
2330 | 7 | # the Free Software Foundation; version 3. | ||
2331 | 8 | # | ||
2332 | 9 | # This program is distributed in the hope that it will be useful, | ||
2333 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
2334 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
2335 | 12 | # GNU Lesser General Public License for more details. | ||
2336 | 13 | # | ||
2337 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
2338 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
2339 | 16 | |||
2340 | 17 | import logging | ||
2341 | 18 | |||
2342 | 19 | from autopilot import logging as autopilot_logging | ||
2343 | 20 | |||
2344 | 21 | from ubuntuuitoolkit._custom_proxy_objects import _common | ||
2345 | 22 | |||
2346 | 23 | |||
2347 | 24 | logger = logging.getLogger(__name__) | ||
2348 | 25 | |||
2349 | 26 | |||
2350 | 27 | # Containers helpers. | ||
2351 | 28 | |||
2352 | 29 | def _get_visible_container_top(containers): | ||
2353 | 30 | containers_top = [container.globalRect.y for container in containers] | ||
2354 | 31 | return max(containers_top) | ||
2355 | 32 | |||
2356 | 33 | |||
2357 | 34 | def _get_visible_container_bottom(containers): | ||
2358 | 35 | containers_bottom = [ | ||
2359 | 36 | container.globalRect.y + container.globalRect.height | ||
2360 | 37 | for container in containers if container.globalRect.height > 0] | ||
2361 | 38 | return min(containers_bottom) | ||
2362 | 39 | |||
2363 | 40 | |||
2364 | 41 | class Flickable(_common.UbuntuUIToolkitCustomProxyObjectBase): | ||
2365 | 42 | |||
2366 | 43 | @autopilot_logging.log_action(logger.info) | ||
2367 | 44 | def swipe_child_into_view(self, child): | ||
2368 | 45 | """Make the child visible. | ||
2369 | 46 | |||
2370 | 47 | Currently it works only when the object needs to be swiped vertically. | ||
2371 | 48 | TODO implement horizontal swiping. --elopio - 2014-03-21 | ||
2372 | 49 | |||
2373 | 50 | """ | ||
2374 | 51 | containers = self._get_containers() | ||
2375 | 52 | if not self._is_child_visible(child, containers): | ||
2376 | 53 | self._swipe_non_visible_child_into_view(child, containers) | ||
2377 | 54 | else: | ||
2378 | 55 | logger.debug('The element is already visible.') | ||
2379 | 56 | |||
2380 | 57 | def _get_containers(self): | ||
2381 | 58 | """Return a list with the containers to take into account when swiping. | ||
2382 | 59 | |||
2383 | 60 | The list includes this flickable and the top-most container. | ||
2384 | 61 | TODO add additional flickables that are between this and the top | ||
2385 | 62 | container. --elopio - 2014-03-22 | ||
2386 | 63 | |||
2387 | 64 | """ | ||
2388 | 65 | containers = [self._get_top_container(), self] | ||
2389 | 66 | return containers | ||
2390 | 67 | |||
2391 | 68 | def _get_top_container(self): | ||
2392 | 69 | """Return the top-most container with a globalRect.""" | ||
2393 | 70 | root = self.get_root_instance() | ||
2394 | 71 | containers = [root] | ||
2395 | 72 | while len(containers) == 1: | ||
2396 | 73 | try: | ||
2397 | 74 | containers[0].globalRect | ||
2398 | 75 | return containers[0] | ||
2399 | 76 | except AttributeError: | ||
2400 | 77 | containers = containers[0].get_children() | ||
2401 | 78 | |||
2402 | 79 | raise _common.ToolkitException("Couldn't find the top-most container.") | ||
2403 | 80 | |||
2404 | 81 | def _is_child_visible(self, child, containers): | ||
2405 | 82 | """Check if the center of the child is visible. | ||
2406 | 83 | |||
2407 | 84 | :return: True if the center of the child is visible, False otherwise. | ||
2408 | 85 | |||
2409 | 86 | """ | ||
2410 | 87 | object_center = child.globalRect.y + child.globalRect.height // 2 | ||
2411 | 88 | visible_top = _get_visible_container_top(containers) | ||
2412 | 89 | visible_bottom = _get_visible_container_bottom(containers) | ||
2413 | 90 | return (object_center >= visible_top and | ||
2414 | 91 | object_center <= visible_bottom) | ||
2415 | 92 | |||
2416 | 93 | @autopilot_logging.log_action(logger.info) | ||
2417 | 94 | def _swipe_non_visible_child_into_view(self, child, containers): | ||
2418 | 95 | while not self._is_child_visible(child, containers): | ||
2419 | 96 | # Check the direction of the swipe based on the position of the | ||
2420 | 97 | # child relative to the immediate flickable container. | ||
2421 | 98 | if child.globalRect.y < self.globalRect.y: | ||
2422 | 99 | self._swipe_to_show_more_above(containers) | ||
2423 | 100 | else: | ||
2424 | 101 | self._swipe_to_show_more_below(containers) | ||
2425 | 102 | |||
2426 | 103 | @autopilot_logging.log_action(logger.info) | ||
2427 | 104 | def _swipe_to_show_more_above(self, containers): | ||
2428 | 105 | if self.atYBeginning: | ||
2429 | 106 | raise _common.ToolkitException( | ||
2430 | 107 | "Can't swipe more, we are already at the top of the " | ||
2431 | 108 | "container.") | ||
2432 | 109 | else: | ||
2433 | 110 | self._swipe_to_show_more('above', containers) | ||
2434 | 111 | |||
2435 | 112 | @autopilot_logging.log_action(logger.info) | ||
2436 | 113 | def _swipe_to_show_more_below(self, containers): | ||
2437 | 114 | if self.atYEnd: | ||
2438 | 115 | raise _common.ToolkitException( | ||
2439 | 116 | "Can't swipe more, we are already at the bottom of the " | ||
2440 | 117 | "container.") | ||
2441 | 118 | else: | ||
2442 | 119 | self._swipe_to_show_more('below', containers) | ||
2443 | 120 | |||
2444 | 121 | def _swipe_to_show_more(self, direction, containers): | ||
2445 | 122 | start_x = stop_x = self.globalRect.x + (self.globalRect.width // 2) | ||
2446 | 123 | # Start and stop just a little under the top and a little over the | ||
2447 | 124 | # bottom. | ||
2448 | 125 | top = _get_visible_container_top(containers) + 5 | ||
2449 | 126 | bottom = _get_visible_container_bottom(containers) - 5 | ||
2450 | 127 | if direction == 'below': | ||
2451 | 128 | start_y = bottom | ||
2452 | 129 | stop_y = top | ||
2453 | 130 | elif direction == 'above': | ||
2454 | 131 | start_y = top | ||
2455 | 132 | stop_y = bottom | ||
2456 | 133 | else: | ||
2457 | 134 | raise _common.ToolkitException( | ||
2458 | 135 | 'Invalid direction {}.'.format(direction)) | ||
2459 | 136 | self._slow_drag(start_x, stop_x, start_y, stop_y) | ||
2460 | 137 | self.dragging.wait_for(False) | ||
2461 | 138 | self.moving.wait_for(False) | ||
2462 | 139 | |||
2463 | 140 | def _slow_drag(self, start_x, stop_x, start_y, stop_y): | ||
2464 | 141 | # If we drag too fast, we end up scrolling more than what we | ||
2465 | 142 | # should, sometimes missing the element we are looking for. | ||
2466 | 143 | self.pointing_device.drag(start_x, start_y, stop_x, stop_y, rate=5) | ||
2467 | 144 | |||
2468 | 145 | @autopilot_logging.log_action(logger.info) | ||
2469 | 146 | def _scroll_to_top(self): | ||
2470 | 147 | if not self.atYBeginning: | ||
2471 | 148 | containers = self._get_containers() | ||
2472 | 149 | while not self.atYBeginning: | ||
2473 | 150 | self._swipe_to_show_more_above(containers) | ||
2474 | 0 | 151 | ||
2475 | === added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_header.py' | |||
2476 | --- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_header.py 1970-01-01 00:00:00 +0000 | |||
2477 | +++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_header.py 2014-04-17 01:29:24 +0000 | |||
2478 | @@ -0,0 +1,57 @@ | |||
2479 | 1 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- | ||
2480 | 2 | # | ||
2481 | 3 | # Copyright (C) 2012, 2013, 2014 Canonical Ltd. | ||
2482 | 4 | # | ||
2483 | 5 | # This program is free software; you can redistribute it and/or modify | ||
2484 | 6 | # it under the terms of the GNU Lesser General Public License as published by | ||
2485 | 7 | # the Free Software Foundation; version 3. | ||
2486 | 8 | # | ||
2487 | 9 | # This program is distributed in the hope that it will be useful, | ||
2488 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
2489 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
2490 | 12 | # GNU Lesser General Public License for more details. | ||
2491 | 13 | # | ||
2492 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
2493 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
2494 | 16 | |||
2495 | 17 | import logging | ||
2496 | 18 | |||
2497 | 19 | from autopilot import logging as autopilot_logging | ||
2498 | 20 | from autopilot.introspection import dbus | ||
2499 | 21 | |||
2500 | 22 | from ubuntuuitoolkit._custom_proxy_objects import ( | ||
2501 | 23 | _common, | ||
2502 | 24 | _tabbar | ||
2503 | 25 | ) | ||
2504 | 26 | |||
2505 | 27 | |||
2506 | 28 | _NO_TABS_ERROR = 'The MainView has no Tabs.' | ||
2507 | 29 | |||
2508 | 30 | |||
2509 | 31 | logger = logging.getLogger(__name__) | ||
2510 | 32 | |||
2511 | 33 | |||
2512 | 34 | class Header(_common.UbuntuUIToolkitCustomProxyObjectBase): | ||
2513 | 35 | """Header Autopilot emulator.""" | ||
2514 | 36 | |||
2515 | 37 | def __init__(self, *args): | ||
2516 | 38 | super(Header, self).__init__(*args) | ||
2517 | 39 | self.pointing_device = _common.get_pointing_device() | ||
2518 | 40 | |||
2519 | 41 | def _get_animating(self): | ||
2520 | 42 | tab_bar_style = self.select_single('TabBarStyle') | ||
2521 | 43 | return tab_bar_style.animating | ||
2522 | 44 | |||
2523 | 45 | @autopilot_logging.log_action(logger.info) | ||
2524 | 46 | def switch_to_next_tab(self): | ||
2525 | 47 | """Open the next tab. | ||
2526 | 48 | |||
2527 | 49 | :raise ToolkitException: If the main view has no tabs. | ||
2528 | 50 | |||
2529 | 51 | """ | ||
2530 | 52 | try: | ||
2531 | 53 | tab_bar = self.select_single(_tabbar.TabBar) | ||
2532 | 54 | except dbus.StateNotFoundError: | ||
2533 | 55 | raise _common.ToolkitException(_NO_TABS_ERROR) | ||
2534 | 56 | tab_bar.switch_to_next_tab() | ||
2535 | 57 | self._get_animating().wait_for(False) | ||
2536 | 0 | 58 | ||
2537 | === added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_listitems.py' | |||
2538 | --- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_listitems.py 1970-01-01 00:00:00 +0000 | |||
2539 | +++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_listitems.py 2014-04-17 01:29:24 +0000 | |||
2540 | @@ -0,0 +1,114 @@ | |||
2541 | 1 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- | ||
2542 | 2 | # | ||
2543 | 3 | # Copyright (C) 2012, 2013, 2014 Canonical Ltd. | ||
2544 | 4 | # | ||
2545 | 5 | # This program is free software; you can redistribute it and/or modify | ||
2546 | 6 | # it under the terms of the GNU Lesser General Public License as published by | ||
2547 | 7 | # the Free Software Foundation; version 3. | ||
2548 | 8 | # | ||
2549 | 9 | # This program is distributed in the hope that it will be useful, | ||
2550 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
2551 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
2552 | 12 | # GNU Lesser General Public License for more details. | ||
2553 | 13 | # | ||
2554 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
2555 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
2556 | 16 | |||
2557 | 17 | import logging | ||
2558 | 18 | |||
2559 | 19 | from autopilot import logging as autopilot_logging | ||
2560 | 20 | from autopilot.introspection import dbus | ||
2561 | 21 | |||
2562 | 22 | from ubuntuuitoolkit._custom_proxy_objects import _common | ||
2563 | 23 | |||
2564 | 24 | |||
2565 | 25 | logger = logging.getLogger(__name__) | ||
2566 | 26 | |||
2567 | 27 | |||
2568 | 28 | class Empty(_common.UbuntuUIToolkitCustomProxyObjectBase): | ||
2569 | 29 | """Base class to emulate swipe to delete.""" | ||
2570 | 30 | |||
2571 | 31 | def exists(self): | ||
2572 | 32 | try: | ||
2573 | 33 | return self.implicitHeight > 0 | ||
2574 | 34 | except dbus.StateNotFoundError: | ||
2575 | 35 | return False | ||
2576 | 36 | |||
2577 | 37 | def _get_confirm_button(self): | ||
2578 | 38 | return self.select_single( | ||
2579 | 39 | 'QQuickItem', objectName='confirmRemovalDialog') | ||
2580 | 40 | |||
2581 | 41 | @autopilot_logging.log_action(logger.info) | ||
2582 | 42 | def swipe_to_delete(self, direction='right'): | ||
2583 | 43 | """Swipe the item in a specific direction.""" | ||
2584 | 44 | if self.removable: | ||
2585 | 45 | self._drag_pointing_device_to_delete(direction) | ||
2586 | 46 | if self.confirmRemoval: | ||
2587 | 47 | self.waitingConfirmationForRemoval.wait_for(True) | ||
2588 | 48 | else: | ||
2589 | 49 | self._wait_until_deleted() | ||
2590 | 50 | else: | ||
2591 | 51 | raise _common.ToolkitException( | ||
2592 | 52 | 'The item "{0}" is not removable'.format(self.objectName)) | ||
2593 | 53 | |||
2594 | 54 | def _drag_pointing_device_to_delete(self, direction): | ||
2595 | 55 | x, y, w, h = self.globalRect | ||
2596 | 56 | tx = x + (w // 8) | ||
2597 | 57 | ty = y + (h // 2) | ||
2598 | 58 | |||
2599 | 59 | if direction == 'right': | ||
2600 | 60 | self.pointing_device.drag(tx, ty, w, ty) | ||
2601 | 61 | elif direction == 'left': | ||
2602 | 62 | self.pointing_device.drag(w - (w*0.1), ty, x, ty) | ||
2603 | 63 | else: | ||
2604 | 64 | raise _common.ToolkitException( | ||
2605 | 65 | 'Invalid direction "{0}" used on swipe to delete function' | ||
2606 | 66 | .format(direction)) | ||
2607 | 67 | |||
2608 | 68 | def _wait_until_deleted(self): | ||
2609 | 69 | try: | ||
2610 | 70 | # The item was hidden. | ||
2611 | 71 | self.implicitHeight.wait_for(0) | ||
2612 | 72 | except dbus.StateNotFoundError: | ||
2613 | 73 | # The item was destroyed. | ||
2614 | 74 | pass | ||
2615 | 75 | |||
2616 | 76 | @autopilot_logging.log_action(logger.info) | ||
2617 | 77 | def confirm_removal(self): | ||
2618 | 78 | """Comfirm item removal if this was already swiped.""" | ||
2619 | 79 | if self.waitingConfirmationForRemoval: | ||
2620 | 80 | deleteButton = self._get_confirm_button() | ||
2621 | 81 | self.pointing_device.click_object(deleteButton) | ||
2622 | 82 | self._wait_until_deleted() | ||
2623 | 83 | else: | ||
2624 | 84 | raise _common.ToolkitException( | ||
2625 | 85 | 'The item "{0}" is not waiting for removal confirmation'. | ||
2626 | 86 | format(self.objectName)) | ||
2627 | 87 | |||
2628 | 88 | |||
2629 | 89 | class Base(Empty): | ||
2630 | 90 | pass | ||
2631 | 91 | |||
2632 | 92 | |||
2633 | 93 | class Standard(Empty): | ||
2634 | 94 | pass | ||
2635 | 95 | |||
2636 | 96 | |||
2637 | 97 | class ItemSelector(Empty): | ||
2638 | 98 | pass | ||
2639 | 99 | |||
2640 | 100 | |||
2641 | 101 | class SingleControl(Empty): | ||
2642 | 102 | pass | ||
2643 | 103 | |||
2644 | 104 | |||
2645 | 105 | class MultiValue(Base): | ||
2646 | 106 | pass | ||
2647 | 107 | |||
2648 | 108 | |||
2649 | 109 | class SingleValue(Base): | ||
2650 | 110 | pass | ||
2651 | 111 | |||
2652 | 112 | |||
2653 | 113 | class Subtitled(Base): | ||
2654 | 114 | pass | ||
2655 | 0 | 115 | ||
2656 | === added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_mainview.py' | |||
2657 | --- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_mainview.py 1970-01-01 00:00:00 +0000 | |||
2658 | +++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_mainview.py 2014-04-17 01:29:24 +0000 | |||
2659 | @@ -0,0 +1,162 @@ | |||
2660 | 1 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- | ||
2661 | 2 | # | ||
2662 | 3 | # Copyright (C) 2012, 2013, 2014 Canonical Ltd. | ||
2663 | 4 | # | ||
2664 | 5 | # This program is free software; you can redistribute it and/or modify | ||
2665 | 6 | # it under the terms of the GNU Lesser General Public License as published by | ||
2666 | 7 | # the Free Software Foundation; version 3. | ||
2667 | 8 | # | ||
2668 | 9 | # This program is distributed in the hope that it will be useful, | ||
2669 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
2670 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
2671 | 12 | # GNU Lesser General Public License for more details. | ||
2672 | 13 | # | ||
2673 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
2674 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
2675 | 16 | |||
2676 | 17 | """Ubuntu UI Toolkit Autopilot custom proxy objects.""" | ||
2677 | 18 | |||
2678 | 19 | import logging | ||
2679 | 20 | |||
2680 | 21 | from autopilot import logging as autopilot_logging | ||
2681 | 22 | from autopilot.introspection import dbus | ||
2682 | 23 | |||
2683 | 24 | from ubuntuuitoolkit._custom_proxy_objects import ( | ||
2684 | 25 | _common, | ||
2685 | 26 | _popups, | ||
2686 | 27 | _tabs, | ||
2687 | 28 | _toolbar, | ||
2688 | 29 | ) | ||
2689 | 30 | |||
2690 | 31 | |||
2691 | 32 | _NO_TABS_ERROR = 'The MainView has no Tabs.' | ||
2692 | 33 | |||
2693 | 34 | |||
2694 | 35 | logger = logging.getLogger(__name__) | ||
2695 | 36 | |||
2696 | 37 | |||
2697 | 38 | class MainView(_common.UbuntuUIToolkitCustomProxyObjectBase): | ||
2698 | 39 | """MainView Autopilot emulator.""" | ||
2699 | 40 | |||
2700 | 41 | def get_header(self): | ||
2701 | 42 | """Return the Header emulator of the MainView.""" | ||
2702 | 43 | try: | ||
2703 | 44 | return self.select_single('Header', objectName='MainView_Header') | ||
2704 | 45 | except dbus.StateNotFoundError: | ||
2705 | 46 | raise _common.ToolkitException('The main view has no header.') | ||
2706 | 47 | |||
2707 | 48 | def get_toolbar(self): | ||
2708 | 49 | """Return the Toolbar emulator of the MainView.""" | ||
2709 | 50 | return self.select_single(_toolbar.Toolbar) | ||
2710 | 51 | |||
2711 | 52 | @autopilot_logging.log_action(logger.info) | ||
2712 | 53 | def open_toolbar(self): | ||
2713 | 54 | """Open the toolbar if it's not already opened. | ||
2714 | 55 | |||
2715 | 56 | :return: The toolbar. | ||
2716 | 57 | |||
2717 | 58 | """ | ||
2718 | 59 | return self.get_toolbar().open() | ||
2719 | 60 | |||
2720 | 61 | @autopilot_logging.log_action(logger.info) | ||
2721 | 62 | def close_toolbar(self): | ||
2722 | 63 | """Close the toolbar if it's opened.""" | ||
2723 | 64 | self.get_toolbar().close() | ||
2724 | 65 | |||
2725 | 66 | def get_tabs(self): | ||
2726 | 67 | """Return the Tabs emulator of the MainView. | ||
2727 | 68 | |||
2728 | 69 | :raise ToolkitException: If the main view has no tabs. | ||
2729 | 70 | |||
2730 | 71 | """ | ||
2731 | 72 | try: | ||
2732 | 73 | return self.select_single(_tabs.Tabs) | ||
2733 | 74 | except dbus.StateNotFoundError: | ||
2734 | 75 | raise _common.ToolkitException(_NO_TABS_ERROR) | ||
2735 | 76 | |||
2736 | 77 | @autopilot_logging.log_action(logger.info) | ||
2737 | 78 | def switch_to_next_tab(self): | ||
2738 | 79 | """Open the next tab. | ||
2739 | 80 | |||
2740 | 81 | :return: The newly opened tab. | ||
2741 | 82 | |||
2742 | 83 | """ | ||
2743 | 84 | logger.debug('Switch to next tab.') | ||
2744 | 85 | self.get_header().switch_to_next_tab() | ||
2745 | 86 | current_tab = self.get_tabs().get_current_tab() | ||
2746 | 87 | current_tab.visible.wait_for(True) | ||
2747 | 88 | return current_tab | ||
2748 | 89 | |||
2749 | 90 | @autopilot_logging.log_action(logger.info) | ||
2750 | 91 | def switch_to_tab_by_index(self, index): | ||
2751 | 92 | """Open a tab. | ||
2752 | 93 | |||
2753 | 94 | :parameter index: The index of the tab to open. | ||
2754 | 95 | :return: The newly opened tab. | ||
2755 | 96 | :raise ToolkitException: If the tab index is out of range. | ||
2756 | 97 | |||
2757 | 98 | """ | ||
2758 | 99 | logger.debug('Switch to tab with index {0}.'.format(index)) | ||
2759 | 100 | tabs = self.get_tabs() | ||
2760 | 101 | number_of_tabs = tabs.get_number_of_tabs() | ||
2761 | 102 | if index >= number_of_tabs: | ||
2762 | 103 | raise _common.ToolkitException('Tab index out of range.') | ||
2763 | 104 | current_tab = tabs.get_current_tab() | ||
2764 | 105 | number_of_switches = 0 | ||
2765 | 106 | while not tabs.selectedTabIndex == index: | ||
2766 | 107 | logger.debug( | ||
2767 | 108 | 'Current tab index: {0}.'.format(tabs.selectedTabIndex)) | ||
2768 | 109 | if number_of_switches >= number_of_tabs - 1: | ||
2769 | 110 | # This prevents a loop. But if this error is ever raised, it's | ||
2770 | 111 | # likely there's a bug on the emulator or on the QML Tab. | ||
2771 | 112 | raise _common.ToolkitException( | ||
2772 | 113 | 'The tab with index {0} was not selected.'.format(index)) | ||
2773 | 114 | current_tab = self.switch_to_next_tab() | ||
2774 | 115 | number_of_switches += 1 | ||
2775 | 116 | return current_tab | ||
2776 | 117 | |||
2777 | 118 | @autopilot_logging.log_action(logger.info) | ||
2778 | 119 | def switch_to_previous_tab(self): | ||
2779 | 120 | """Open the previous tab. | ||
2780 | 121 | |||
2781 | 122 | :return: The newly opened tab. | ||
2782 | 123 | |||
2783 | 124 | """ | ||
2784 | 125 | tabs = self.get_tabs() | ||
2785 | 126 | if tabs.selectedTabIndex == 0: | ||
2786 | 127 | previous_tab_index = tabs.get_number_of_tabs() - 1 | ||
2787 | 128 | else: | ||
2788 | 129 | previous_tab_index = tabs.selectedTabIndex - 1 | ||
2789 | 130 | return self.switch_to_tab_by_index(previous_tab_index) | ||
2790 | 131 | |||
2791 | 132 | @autopilot_logging.log_action(logger.info) | ||
2792 | 133 | def switch_to_tab(self, object_name): | ||
2793 | 134 | """Open a tab. | ||
2794 | 135 | |||
2795 | 136 | :parameter object_name: The QML objectName property of the tab. | ||
2796 | 137 | :return: The newly opened tab. | ||
2797 | 138 | :raise ToolkitException: If there is no tab with that object | ||
2798 | 139 | name. | ||
2799 | 140 | |||
2800 | 141 | """ | ||
2801 | 142 | tabs = self.get_tabs() | ||
2802 | 143 | for index, tab in enumerate(tabs.select_many('Tab')): | ||
2803 | 144 | if tab.objectName == object_name: | ||
2804 | 145 | return self.switch_to_tab_by_index(tab.index) | ||
2805 | 146 | raise _common.ToolkitException( | ||
2806 | 147 | 'Tab with objectName "{0}" not found.'.format(object_name)) | ||
2807 | 148 | |||
2808 | 149 | def get_action_selection_popover(self, object_name): | ||
2809 | 150 | """Return an ActionSelectionPopover emulator. | ||
2810 | 151 | |||
2811 | 152 | :parameter object_name: The QML objectName property of the popover. | ||
2812 | 153 | |||
2813 | 154 | """ | ||
2814 | 155 | return self.select_single( | ||
2815 | 156 | _popups.ActionSelectionPopover, objectName=object_name) | ||
2816 | 157 | |||
2817 | 158 | @autopilot_logging.log_action(logger.info) | ||
2818 | 159 | def go_back(self): | ||
2819 | 160 | """Go to the previous page.""" | ||
2820 | 161 | toolbar = self.open_toolbar() | ||
2821 | 162 | toolbar.click_back_button() | ||
2822 | 0 | 163 | ||
2823 | === added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_popups.py' | |||
2824 | --- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_popups.py 1970-01-01 00:00:00 +0000 | |||
2825 | +++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_popups.py 2014-04-17 01:29:24 +0000 | |||
2826 | @@ -0,0 +1,82 @@ | |||
2827 | 1 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- | ||
2828 | 2 | # | ||
2829 | 3 | # Copyright (C) 2012, 2013, 2014 Canonical Ltd. | ||
2830 | 4 | # | ||
2831 | 5 | # This program is free software; you can redistribute it and/or modify | ||
2832 | 6 | # it under the terms of the GNU Lesser General Public License as published by | ||
2833 | 7 | # the Free Software Foundation; version 3. | ||
2834 | 8 | # | ||
2835 | 9 | # This program is distributed in the hope that it will be useful, | ||
2836 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
2837 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
2838 | 12 | # GNU Lesser General Public License for more details. | ||
2839 | 13 | # | ||
2840 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
2841 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
2842 | 16 | |||
2843 | 17 | import logging | ||
2844 | 18 | |||
2845 | 19 | from autopilot import logging as autopilot_logging | ||
2846 | 20 | from autopilot.introspection import dbus | ||
2847 | 21 | |||
2848 | 22 | from ubuntuuitoolkit._custom_proxy_objects import _common | ||
2849 | 23 | |||
2850 | 24 | |||
2851 | 25 | logger = logging.getLogger(__name__) | ||
2852 | 26 | |||
2853 | 27 | |||
2854 | 28 | class ActionSelectionPopover(_common.UbuntuUIToolkitCustomProxyObjectBase): | ||
2855 | 29 | """ActionSelectionPopover Autopilot emulator.""" | ||
2856 | 30 | |||
2857 | 31 | def click_button_by_text(self, text): | ||
2858 | 32 | """Click a button on the popover. | ||
2859 | 33 | |||
2860 | 34 | XXX We are receiving the text because there's no way to set the | ||
2861 | 35 | objectName on the action. This is reported at | ||
2862 | 36 | https://bugs.launchpad.net/ubuntu-ui-toolkit/+bug/1205144 | ||
2863 | 37 | --elopio - 2013-07-25 | ||
2864 | 38 | |||
2865 | 39 | :parameter text: The text of the button. | ||
2866 | 40 | :raise ToolkitException: If the popover is not open. | ||
2867 | 41 | |||
2868 | 42 | """ | ||
2869 | 43 | if not self.visible: | ||
2870 | 44 | raise _common.ToolkitException('The popover is not open.') | ||
2871 | 45 | button = self._get_button(text) | ||
2872 | 46 | if button is None: | ||
2873 | 47 | raise _common.ToolkitException( | ||
2874 | 48 | 'Button with text "{0}" not found.'.format(text)) | ||
2875 | 49 | self.pointing_device.click_object(button) | ||
2876 | 50 | if self.autoClose: | ||
2877 | 51 | try: | ||
2878 | 52 | self.visible.wait_for(False) | ||
2879 | 53 | except dbus.StateNotFoundError: | ||
2880 | 54 | # The popover was removed from the tree. | ||
2881 | 55 | pass | ||
2882 | 56 | |||
2883 | 57 | def _get_button(self, text): | ||
2884 | 58 | buttons = self.select_many('Empty') | ||
2885 | 59 | for button in buttons: | ||
2886 | 60 | if button.text == text: | ||
2887 | 61 | return button | ||
2888 | 62 | |||
2889 | 63 | |||
2890 | 64 | class ComposerSheet(_common.UbuntuUIToolkitCustomProxyObjectBase): | ||
2891 | 65 | """ComposerSheet Autopilot emulator.""" | ||
2892 | 66 | |||
2893 | 67 | def __init__(self, *args): | ||
2894 | 68 | super(ComposerSheet, self).__init__(*args) | ||
2895 | 69 | |||
2896 | 70 | @autopilot_logging.log_action(logger.info) | ||
2897 | 71 | def confirm(self): | ||
2898 | 72 | """Confirm the composer sheet.""" | ||
2899 | 73 | button = self.select_single('Button', objectName='confirmButton') | ||
2900 | 74 | self.pointing_device.click_object(button) | ||
2901 | 75 | self.wait_until_destroyed() | ||
2902 | 76 | |||
2903 | 77 | @autopilot_logging.log_action(logger.info) | ||
2904 | 78 | def cancel(self): | ||
2905 | 79 | """Cancel the composer sheet.""" | ||
2906 | 80 | button = self.select_single('Button', objectName='cancelButton') | ||
2907 | 81 | self.pointing_device.click_object(button) | ||
2908 | 82 | self.wait_until_destroyed() | ||
2909 | 0 | 83 | ||
2910 | === added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_qquicklistview.py' | |||
2911 | --- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_qquicklistview.py 1970-01-01 00:00:00 +0000 | |||
2912 | +++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_qquicklistview.py 2014-04-17 01:29:24 +0000 | |||
2913 | @@ -0,0 +1,65 @@ | |||
2914 | 1 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- | ||
2915 | 2 | # | ||
2916 | 3 | # Copyright (C) 2012, 2013, 2014 Canonical Ltd. | ||
2917 | 4 | # | ||
2918 | 5 | # This program is free software; you can redistribute it and/or modify | ||
2919 | 6 | # it under the terms of the GNU Lesser General Public License as published by | ||
2920 | 7 | # the Free Software Foundation; version 3. | ||
2921 | 8 | # | ||
2922 | 9 | # This program is distributed in the hope that it will be useful, | ||
2923 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
2924 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
2925 | 12 | # GNU Lesser General Public License for more details. | ||
2926 | 13 | # | ||
2927 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
2928 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
2929 | 16 | |||
2930 | 17 | import logging | ||
2931 | 18 | |||
2932 | 19 | from autopilot import logging as autopilot_logging | ||
2933 | 20 | from autopilot.introspection import dbus | ||
2934 | 21 | |||
2935 | 22 | from ubuntuuitoolkit._custom_proxy_objects import _flickable | ||
2936 | 23 | from ubuntuuitoolkit._custom_proxy_objects import _common | ||
2937 | 24 | |||
2938 | 25 | |||
2939 | 26 | logger = logging.getLogger(__name__) | ||
2940 | 27 | |||
2941 | 28 | |||
2942 | 29 | class QQuickListView(_flickable.Flickable): | ||
2943 | 30 | |||
2944 | 31 | @autopilot_logging.log_action(logger.info) | ||
2945 | 32 | def click_element(self, object_name): | ||
2946 | 33 | """Click an element from the list. | ||
2947 | 34 | |||
2948 | 35 | It swipes the element into view if it's center is not visible. | ||
2949 | 36 | |||
2950 | 37 | :parameter objectName: The objectName property of the element to click. | ||
2951 | 38 | |||
2952 | 39 | """ | ||
2953 | 40 | try: | ||
2954 | 41 | element = self.select_single(objectName=object_name) | ||
2955 | 42 | except dbus.StateNotFoundError: | ||
2956 | 43 | # The element might be on a part of the list that hasn't been | ||
2957 | 44 | # created yet. We have to search for it scrolling the entire list. | ||
2958 | 45 | element = self._find_element(object_name) | ||
2959 | 46 | self.swipe_child_into_view(element) | ||
2960 | 47 | self.pointing_device.click_object(element) | ||
2961 | 48 | |||
2962 | 49 | @autopilot_logging.log_action(logger.info) | ||
2963 | 50 | def _find_element(self, object_name): | ||
2964 | 51 | self._scroll_to_top() | ||
2965 | 52 | while not self.atYEnd: | ||
2966 | 53 | containers = self._get_containers() | ||
2967 | 54 | self._swipe_to_show_more_below(containers) | ||
2968 | 55 | try: | ||
2969 | 56 | return self.select_single(objectName=object_name) | ||
2970 | 57 | except dbus.StateNotFoundError: | ||
2971 | 58 | pass | ||
2972 | 59 | raise _common.ToolkitException( | ||
2973 | 60 | 'List element with objectName "{}" not found.'.format(object_name)) | ||
2974 | 61 | |||
2975 | 62 | def _is_element_clickable(self, object_name): | ||
2976 | 63 | child = self.select_single(objectName=object_name) | ||
2977 | 64 | containers = self._get_containers() | ||
2978 | 65 | return self._is_child_visible(child, containers) | ||
2979 | 0 | 66 | ||
2980 | === added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_tabbar.py' | |||
2981 | --- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_tabbar.py 1970-01-01 00:00:00 +0000 | |||
2982 | +++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_tabbar.py 2014-04-17 01:29:24 +0000 | |||
2983 | @@ -0,0 +1,68 @@ | |||
2984 | 1 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- | ||
2985 | 2 | # | ||
2986 | 3 | # Copyright (C) 2012, 2013, 2014 Canonical Ltd. | ||
2987 | 4 | # | ||
2988 | 5 | # This program is free software; you can redistribute it and/or modify | ||
2989 | 6 | # it under the terms of the GNU Lesser General Public License as published by | ||
2990 | 7 | # the Free Software Foundation; version 3. | ||
2991 | 8 | # | ||
2992 | 9 | # This program is distributed in the hope that it will be useful, | ||
2993 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
2994 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
2995 | 12 | # GNU Lesser General Public License for more details. | ||
2996 | 13 | # | ||
2997 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
2998 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
2999 | 16 | |||
3000 | 17 | import logging | ||
3001 | 18 | |||
3002 | 19 | from autopilot import logging as autopilot_logging | ||
3003 | 20 | |||
3004 | 21 | from ubuntuuitoolkit._custom_proxy_objects import _common | ||
3005 | 22 | |||
3006 | 23 | |||
3007 | 24 | logger = logging.getLogger(__name__) | ||
3008 | 25 | |||
3009 | 26 | |||
3010 | 27 | class TabBar(_common.UbuntuUIToolkitCustomProxyObjectBase): | ||
3011 | 28 | """TabBar Autopilot emulator.""" | ||
3012 | 29 | |||
3013 | 30 | @autopilot_logging.log_action(logger.info) | ||
3014 | 31 | def switch_to_next_tab(self): | ||
3015 | 32 | """Open the next tab.""" | ||
3016 | 33 | self._activate_tab_bar() | ||
3017 | 34 | logger.debug('Click the next tab bar button.') | ||
3018 | 35 | self.pointing_device.click_object(self._get_next_tab_button()) | ||
3019 | 36 | |||
3020 | 37 | def _activate_tab_bar(self): | ||
3021 | 38 | # First move to the tab bar to avoid timing issues when we find it in | ||
3022 | 39 | # selection mode but it's deselected while we move to it. | ||
3023 | 40 | self.pointing_device.move_to_object(self) | ||
3024 | 41 | if self.selectionMode: | ||
3025 | 42 | logger.debug('Already in selection mode.') | ||
3026 | 43 | else: | ||
3027 | 44 | # Click the tab bar to switch to selection mode. | ||
3028 | 45 | logger.debug('Click the tab bar to enable selection mode.') | ||
3029 | 46 | self.pointing_device.click_object(self) | ||
3030 | 47 | |||
3031 | 48 | def _get_next_tab_button(self): | ||
3032 | 49 | current_index = self._get_selected_button_index() | ||
3033 | 50 | next_index = (current_index + 1) % self._get_number_of_tab_buttons() | ||
3034 | 51 | return self._get_tab_button(next_index) | ||
3035 | 52 | |||
3036 | 53 | def _get_selected_button_index(self): | ||
3037 | 54 | return self.select_single('QQuickPathView').selectedButtonIndex | ||
3038 | 55 | |||
3039 | 56 | def _get_number_of_tab_buttons(self): | ||
3040 | 57 | return len(self._get_tab_buttons()) | ||
3041 | 58 | |||
3042 | 59 | def _get_tab_buttons(self): | ||
3043 | 60 | return self.select_many('AbstractButton') | ||
3044 | 61 | |||
3045 | 62 | def _get_tab_button(self, index): | ||
3046 | 63 | buttons = self._get_tab_buttons() | ||
3047 | 64 | for button in buttons: | ||
3048 | 65 | if button.buttonIndex == index: | ||
3049 | 66 | return button | ||
3050 | 67 | raise _common.ToolkitException( | ||
3051 | 68 | 'There is no tab button with index {0}.'.format(index)) | ||
3052 | 0 | 69 | ||
3053 | === added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_tabs.py' | |||
3054 | --- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_tabs.py 1970-01-01 00:00:00 +0000 | |||
3055 | +++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_tabs.py 2014-04-17 01:29:24 +0000 | |||
3056 | @@ -0,0 +1,41 @@ | |||
3057 | 1 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- | ||
3058 | 2 | # | ||
3059 | 3 | # Copyright (C) 2012, 2013, 2014 Canonical Ltd. | ||
3060 | 4 | # | ||
3061 | 5 | # This program is free software; you can redistribute it and/or modify | ||
3062 | 6 | # it under the terms of the GNU Lesser General Public License as published by | ||
3063 | 7 | # the Free Software Foundation; version 3. | ||
3064 | 8 | # | ||
3065 | 9 | # This program is distributed in the hope that it will be useful, | ||
3066 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
3067 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
3068 | 12 | # GNU Lesser General Public License for more details. | ||
3069 | 13 | # | ||
3070 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
3071 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
3072 | 16 | |||
3073 | 17 | from ubuntuuitoolkit._custom_proxy_objects import _common | ||
3074 | 18 | |||
3075 | 19 | |||
3076 | 20 | class Tabs(_common.UbuntuUIToolkitCustomProxyObjectBase): | ||
3077 | 21 | """Tabs Autopilot emulator.""" | ||
3078 | 22 | |||
3079 | 23 | def get_current_tab(self): | ||
3080 | 24 | """Return the currently selected tab.""" | ||
3081 | 25 | return self._get_tab(self.selectedTabIndex) | ||
3082 | 26 | |||
3083 | 27 | def _get_tab(self, index): | ||
3084 | 28 | tabs = self._get_tabs() | ||
3085 | 29 | for tab in tabs: | ||
3086 | 30 | if tab.index == index: | ||
3087 | 31 | return tab | ||
3088 | 32 | else: | ||
3089 | 33 | raise _common.ToolkitException( | ||
3090 | 34 | 'There is no tab with index {0}.'.format(index)) | ||
3091 | 35 | |||
3092 | 36 | def _get_tabs(self): | ||
3093 | 37 | return self.select_many('Tab') | ||
3094 | 38 | |||
3095 | 39 | def get_number_of_tabs(self): | ||
3096 | 40 | """Return the number of tabs.""" | ||
3097 | 41 | return len(self._get_tabs()) | ||
3098 | 0 | 42 | ||
3099 | === added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_textfield.py' | |||
3100 | --- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_textfield.py 1970-01-01 00:00:00 +0000 | |||
3101 | +++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_textfield.py 2014-04-17 01:29:24 +0000 | |||
3102 | @@ -0,0 +1,88 @@ | |||
3103 | 1 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- | ||
3104 | 2 | # | ||
3105 | 3 | # Copyright (C) 2012, 2013, 2014 Canonical Ltd. | ||
3106 | 4 | # | ||
3107 | 5 | # This program is free software; you can redistribute it and/or modify | ||
3108 | 6 | # it under the terms of the GNU Lesser General Public License as published by | ||
3109 | 7 | # the Free Software Foundation; version 3. | ||
3110 | 8 | # | ||
3111 | 9 | # This program is distributed in the hope that it will be useful, | ||
3112 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
3113 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
3114 | 12 | # GNU Lesser General Public License for more details. | ||
3115 | 13 | # | ||
3116 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
3117 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
3118 | 16 | |||
3119 | 17 | from autopilot import platform | ||
3120 | 18 | |||
3121 | 19 | from ubuntuuitoolkit._custom_proxy_objects import ( | ||
3122 | 20 | _common, | ||
3123 | 21 | _mainview | ||
3124 | 22 | ) | ||
3125 | 23 | |||
3126 | 24 | |||
3127 | 25 | class TextField(_common.UbuntuUIToolkitCustomProxyObjectBase): | ||
3128 | 26 | """TextField Autopilot emulator.""" | ||
3129 | 27 | |||
3130 | 28 | def __init__(self, *args): | ||
3131 | 29 | super(TextField, self).__init__(*args) | ||
3132 | 30 | self.keyboard = _common.get_keyboard() | ||
3133 | 31 | |||
3134 | 32 | def write(self, text, clear=True): | ||
3135 | 33 | """Write into the text field. | ||
3136 | 34 | |||
3137 | 35 | :parameter text: The text to write. | ||
3138 | 36 | :parameter clear: If True, the text field will be cleared before | ||
3139 | 37 | writing the text. If False, the text will be appended at the end | ||
3140 | 38 | of the text field. Default is True. | ||
3141 | 39 | |||
3142 | 40 | """ | ||
3143 | 41 | with self.keyboard.focused_type(self): | ||
3144 | 42 | self.focus.wait_for(True) | ||
3145 | 43 | if clear: | ||
3146 | 44 | self.clear() | ||
3147 | 45 | else: | ||
3148 | 46 | if not self.is_empty(): | ||
3149 | 47 | self.keyboard.press_and_release('End') | ||
3150 | 48 | self.keyboard.type(text) | ||
3151 | 49 | |||
3152 | 50 | def clear(self): | ||
3153 | 51 | """Clear the text field.""" | ||
3154 | 52 | if not self.is_empty(): | ||
3155 | 53 | if self.hasClearButton: | ||
3156 | 54 | self._click_clear_button() | ||
3157 | 55 | else: | ||
3158 | 56 | self._clear_with_keys() | ||
3159 | 57 | self.text.wait_for('') | ||
3160 | 58 | |||
3161 | 59 | def is_empty(self): | ||
3162 | 60 | """Return True if the text field is empty. False otherwise.""" | ||
3163 | 61 | return self.text == '' | ||
3164 | 62 | |||
3165 | 63 | def _click_clear_button(self): | ||
3166 | 64 | clear_button = self.select_single( | ||
3167 | 65 | 'AbstractButton', objectName='clear_button') | ||
3168 | 66 | if not clear_button.visible: | ||
3169 | 67 | self.pointing_device.click_object(self) | ||
3170 | 68 | self.pointing_device.click_object(clear_button) | ||
3171 | 69 | |||
3172 | 70 | def _clear_with_keys(self): | ||
3173 | 71 | if platform.model() == 'Desktop': | ||
3174 | 72 | self._select_all() | ||
3175 | 73 | else: | ||
3176 | 74 | # Touch tap currently doesn't have a press_duration parameter, so | ||
3177 | 75 | # we can't show the popover. Reported as bug http://pad.lv/1268782 | ||
3178 | 76 | # --elopio - 2014-01-13 | ||
3179 | 77 | self.keyboard.press_and_release('End') | ||
3180 | 78 | while not self.is_empty(): | ||
3181 | 79 | # We delete with backspace because the on-screen keyboard has that | ||
3182 | 80 | # key. | ||
3183 | 81 | self.keyboard.press_and_release('BackSpace') | ||
3184 | 82 | |||
3185 | 83 | def _select_all(self): | ||
3186 | 84 | self.pointing_device.click_object(self, press_duration=1) | ||
3187 | 85 | root = self.get_root_instance() | ||
3188 | 86 | main_view = root.select_single(_mainview.MainView) | ||
3189 | 87 | popover = main_view.get_action_selection_popover('text_input_popover') | ||
3190 | 88 | popover.click_button_by_text('Select All') | ||
3191 | 0 | 89 | ||
3192 | === added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_toolbar.py' | |||
3193 | --- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_toolbar.py 1970-01-01 00:00:00 +0000 | |||
3194 | +++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_toolbar.py 2014-04-17 01:29:24 +0000 | |||
3195 | @@ -0,0 +1,106 @@ | |||
3196 | 1 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- | ||
3197 | 2 | # | ||
3198 | 3 | # Copyright (C) 2012, 2013, 2014 Canonical Ltd. | ||
3199 | 4 | # | ||
3200 | 5 | # This program is free software; you can redistribute it and/or modify | ||
3201 | 6 | # it under the terms of the GNU Lesser General Public License as published by | ||
3202 | 7 | # the Free Software Foundation; version 3. | ||
3203 | 8 | # | ||
3204 | 9 | # This program is distributed in the hope that it will be useful, | ||
3205 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
3206 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
3207 | 12 | # GNU Lesser General Public License for more details. | ||
3208 | 13 | # | ||
3209 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
3210 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
3211 | 16 | |||
3212 | 17 | import logging | ||
3213 | 18 | |||
3214 | 19 | from autopilot import logging as autopilot_logging | ||
3215 | 20 | from autopilot.introspection import dbus | ||
3216 | 21 | |||
3217 | 22 | from ubuntuuitoolkit._custom_proxy_objects import _common | ||
3218 | 23 | |||
3219 | 24 | |||
3220 | 25 | logger = logging.getLogger(__name__) | ||
3221 | 26 | |||
3222 | 27 | |||
3223 | 28 | class Toolbar(_common.UbuntuUIToolkitCustomProxyObjectBase): | ||
3224 | 29 | """Toolbar Autopilot emulator.""" | ||
3225 | 30 | |||
3226 | 31 | @autopilot_logging.log_action(logger.info) | ||
3227 | 32 | def open(self): | ||
3228 | 33 | """Open the toolbar if it's not already opened. | ||
3229 | 34 | |||
3230 | 35 | :return: The toolbar. | ||
3231 | 36 | |||
3232 | 37 | """ | ||
3233 | 38 | self.animating.wait_for(False) | ||
3234 | 39 | if not self.opened: | ||
3235 | 40 | self._drag_to_open() | ||
3236 | 41 | self.opened.wait_for(True) | ||
3237 | 42 | self.animating.wait_for(False) | ||
3238 | 43 | |||
3239 | 44 | return self | ||
3240 | 45 | |||
3241 | 46 | def _drag_to_open(self): | ||
3242 | 47 | x, y, _, _ = self.globalRect | ||
3243 | 48 | line_x = x + self.width * 0.50 | ||
3244 | 49 | start_y = y + self.height - 1 | ||
3245 | 50 | stop_y = y | ||
3246 | 51 | |||
3247 | 52 | self.pointing_device.drag(line_x, start_y, line_x, stop_y) | ||
3248 | 53 | |||
3249 | 54 | @autopilot_logging.log_action(logger.info) | ||
3250 | 55 | def close(self): | ||
3251 | 56 | """Close the toolbar if it's opened.""" | ||
3252 | 57 | self.animating.wait_for(False) | ||
3253 | 58 | if self.opened: | ||
3254 | 59 | self._drag_to_close() | ||
3255 | 60 | self.opened.wait_for(False) | ||
3256 | 61 | self.animating.wait_for(False) | ||
3257 | 62 | |||
3258 | 63 | def _drag_to_close(self): | ||
3259 | 64 | x, y, _, _ = self.globalRect | ||
3260 | 65 | line_x = x + self.width * 0.50 | ||
3261 | 66 | start_y = y | ||
3262 | 67 | stop_y = y + self.height - 1 | ||
3263 | 68 | |||
3264 | 69 | self.pointing_device.drag(line_x, start_y, line_x, stop_y) | ||
3265 | 70 | |||
3266 | 71 | @autopilot_logging.log_action(logger.info) | ||
3267 | 72 | def click_button(self, object_name): | ||
3268 | 73 | """Click a button of the toolbar. | ||
3269 | 74 | |||
3270 | 75 | The toolbar should be opened before clicking the button, or an | ||
3271 | 76 | exception will be raised. If the toolbar is closed for some reason | ||
3272 | 77 | (e.g., timer finishes) after moving the mouse cursor and before | ||
3273 | 78 | clicking the button, it is re-opened automatically by this function. | ||
3274 | 79 | |||
3275 | 80 | :parameter object_name: The QML objectName property of the button. | ||
3276 | 81 | :raise ToolkitException: If there is no button with that object | ||
3277 | 82 | name. | ||
3278 | 83 | |||
3279 | 84 | """ | ||
3280 | 85 | # ensure the toolbar is open | ||
3281 | 86 | if not self.opened: | ||
3282 | 87 | raise _common.ToolkitException( | ||
3283 | 88 | 'Toolbar must be opened before calling click_button().') | ||
3284 | 89 | try: | ||
3285 | 90 | button = self._get_button(object_name) | ||
3286 | 91 | except dbus.StateNotFoundError: | ||
3287 | 92 | raise _common.ToolkitException( | ||
3288 | 93 | 'Button with objectName "{0}" not found.'.format(object_name)) | ||
3289 | 94 | self.pointing_device.move_to_object(button) | ||
3290 | 95 | # ensure the toolbar is still open (may have closed due to timeout) | ||
3291 | 96 | self.open() | ||
3292 | 97 | # click the button | ||
3293 | 98 | self.pointing_device.click_object(button) | ||
3294 | 99 | |||
3295 | 100 | def _get_button(self, object_name): | ||
3296 | 101 | return self.select_single('ActionItem', objectName=object_name) | ||
3297 | 102 | |||
3298 | 103 | @autopilot_logging.log_action(logger.info) | ||
3299 | 104 | def click_back_button(self): | ||
3300 | 105 | """Click the back button of the toolbar.""" | ||
3301 | 106 | self.click_button('back_toolbar_button') | ||
3302 | 0 | 107 | ||
3303 | === added file 'tests/autopilot/ubuntuuitoolkit/emulators.py' | |||
3304 | --- tests/autopilot/ubuntuuitoolkit/emulators.py 1970-01-01 00:00:00 +0000 | |||
3305 | +++ tests/autopilot/ubuntuuitoolkit/emulators.py 2014-04-17 01:29:24 +0000 | |||
3306 | @@ -0,0 +1,85 @@ | |||
3307 | 1 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- | ||
3308 | 2 | # | ||
3309 | 3 | # Copyright (C) 2012, 2013, 2014 Canonical Ltd. | ||
3310 | 4 | # | ||
3311 | 5 | # This program is free software; you can redistribute it and/or modify | ||
3312 | 6 | # it under the terms of the GNU Lesser General Public License as published by | ||
3313 | 7 | # the Free Software Foundation; version 3. | ||
3314 | 8 | # | ||
3315 | 9 | # This program is distributed in the hope that it will be useful, | ||
3316 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
3317 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
3318 | 12 | # GNU Lesser General Public License for more details. | ||
3319 | 13 | # | ||
3320 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
3321 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
3322 | 16 | |||
3323 | 17 | """Emulators was the old name for the custom proxy objects.""" | ||
3324 | 18 | |||
3325 | 19 | import logging | ||
3326 | 20 | |||
3327 | 21 | |||
3328 | 22 | logger = logging.getLogger(__name__) | ||
3329 | 23 | |||
3330 | 24 | |||
3331 | 25 | logger.warning( | ||
3332 | 26 | 'The ubuntuuitoolkit.emulators module is deprecated. Import the autopilot ' | ||
3333 | 27 | 'helpers from the top-level ubuntuuitoolkit module.') | ||
3334 | 28 | |||
3335 | 29 | |||
3336 | 30 | __all__ = [ | ||
3337 | 31 | 'check_autopilot_version', | ||
3338 | 32 | 'get_keyboard', | ||
3339 | 33 | 'get_pointing_device', | ||
3340 | 34 | 'ActionSelectionPopover', | ||
3341 | 35 | 'Base', | ||
3342 | 36 | 'CheckBox', | ||
3343 | 37 | 'ComposerSheet', | ||
3344 | 38 | 'Empty', | ||
3345 | 39 | 'Flickable', | ||
3346 | 40 | 'Header', | ||
3347 | 41 | 'ItemSelector', | ||
3348 | 42 | 'MainView', | ||
3349 | 43 | 'MultiValue', | ||
3350 | 44 | 'OptionSelector', | ||
3351 | 45 | 'QQuickListView', | ||
3352 | 46 | 'SingleControl', | ||
3353 | 47 | 'SingleValue', | ||
3354 | 48 | 'Standard', | ||
3355 | 49 | 'Subtitled', | ||
3356 | 50 | 'TabBar', | ||
3357 | 51 | 'Tabs', | ||
3358 | 52 | 'TextField', | ||
3359 | 53 | 'Toolbar', | ||
3360 | 54 | 'ToolkitEmulatorException', | ||
3361 | 55 | 'UbuntuUIToolkitEmulatorBase', | ||
3362 | 56 | ] | ||
3363 | 57 | |||
3364 | 58 | |||
3365 | 59 | from ubuntuuitoolkit import ( | ||
3366 | 60 | check_autopilot_version, | ||
3367 | 61 | get_keyboard, | ||
3368 | 62 | get_pointing_device, | ||
3369 | 63 | ActionSelectionPopover, | ||
3370 | 64 | Base, | ||
3371 | 65 | CheckBox, | ||
3372 | 66 | ComposerSheet, | ||
3373 | 67 | Empty, | ||
3374 | 68 | Flickable, | ||
3375 | 69 | Header, | ||
3376 | 70 | ItemSelector, | ||
3377 | 71 | MainView, | ||
3378 | 72 | MultiValue, | ||
3379 | 73 | OptionSelector, | ||
3380 | 74 | QQuickListView, | ||
3381 | 75 | SingleControl, | ||
3382 | 76 | SingleValue, | ||
3383 | 77 | Standard, | ||
3384 | 78 | Subtitled, | ||
3385 | 79 | TabBar, | ||
3386 | 80 | Tabs, | ||
3387 | 81 | TextField, | ||
3388 | 82 | Toolbar, | ||
3389 | 83 | ToolkitException as ToolkitEmulatorException, | ||
3390 | 84 | UbuntuUIToolkitCustomProxyObjectBase as UbuntuUIToolkitEmulatorBase, | ||
3391 | 85 | ) | ||
3392 | 0 | 86 | ||
3393 | === added directory 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects' | |||
3394 | === added file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/__init__.py' | |||
3395 | === added file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_checkbox.py' | |||
3396 | --- tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_checkbox.py 1970-01-01 00:00:00 +0000 | |||
3397 | +++ tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_checkbox.py 2014-04-17 01:29:24 +0000 | |||
3398 | @@ -0,0 +1,136 @@ | |||
3399 | 1 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- | ||
3400 | 2 | # | ||
3401 | 3 | # Copyright (C) 2013, 2014 Canonical Ltd. | ||
3402 | 4 | # | ||
3403 | 5 | # This program is free software; you can redistribute it and/or modify | ||
3404 | 6 | # it under the terms of the GNU Lesser General Public License as published by | ||
3405 | 7 | # the Free Software Foundation; version 3. | ||
3406 | 8 | # | ||
3407 | 9 | # This program is distributed in the hope that it will be useful, | ||
3408 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
3409 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
3410 | 12 | # GNU Lesser General Public License for more details. | ||
3411 | 13 | # | ||
3412 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
3413 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
3414 | 16 | |||
3415 | 17 | import time | ||
3416 | 18 | |||
3417 | 19 | from autopilot import input | ||
3418 | 20 | from testtools.matchers import GreaterThan, LessThan | ||
3419 | 21 | try: | ||
3420 | 22 | from unittest import mock | ||
3421 | 23 | except ImportError: | ||
3422 | 24 | import mock | ||
3423 | 25 | |||
3424 | 26 | import ubuntuuitoolkit | ||
3425 | 27 | from ubuntuuitoolkit import tests | ||
3426 | 28 | |||
3427 | 29 | |||
3428 | 30 | TEST_QML_WITH_CHECKBOX = (""" | ||
3429 | 31 | import QtQuick 2.0 | ||
3430 | 32 | import Ubuntu.Components 0.1 | ||
3431 | 33 | |||
3432 | 34 | MainView { | ||
3433 | 35 | width: units.gu(48) | ||
3434 | 36 | height: units.gu(60) | ||
3435 | 37 | |||
3436 | 38 | Item { | ||
3437 | 39 | CheckBox { | ||
3438 | 40 | checked: false | ||
3439 | 41 | objectName: "test_checkbox" | ||
3440 | 42 | } | ||
3441 | 43 | } | ||
3442 | 44 | } | ||
3443 | 45 | """) | ||
3444 | 46 | |||
3445 | 47 | |||
3446 | 48 | TEST_QML_WITH_SWITCH = (""" | ||
3447 | 49 | import QtQuick 2.0 | ||
3448 | 50 | import Ubuntu.Components 0.1 | ||
3449 | 51 | |||
3450 | 52 | MainView { | ||
3451 | 53 | width: units.gu(48) | ||
3452 | 54 | height: units.gu(60) | ||
3453 | 55 | |||
3454 | 56 | Item { | ||
3455 | 57 | Switch { | ||
3456 | 58 | checked: false | ||
3457 | 59 | objectName: "test_switch" | ||
3458 | 60 | } | ||
3459 | 61 | } | ||
3460 | 62 | } | ||
3461 | 63 | """) | ||
3462 | 64 | |||
3463 | 65 | |||
3464 | 66 | class ToggleTestCase(tests.QMLStringAppTestCase): | ||
3465 | 67 | |||
3466 | 68 | scenarios = [ | ||
3467 | 69 | ('checkbox', dict( | ||
3468 | 70 | test_qml=TEST_QML_WITH_CHECKBOX, objectName='test_checkbox')), | ||
3469 | 71 | ('switch', dict( | ||
3470 | 72 | test_qml=TEST_QML_WITH_SWITCH, objectName='test_switch')) | ||
3471 | 73 | ] | ||
3472 | 74 | |||
3473 | 75 | def setUp(self): | ||
3474 | 76 | super(ToggleTestCase, self).setUp() | ||
3475 | 77 | self.toggle = self.main_view.select_single( | ||
3476 | 78 | ubuntuuitoolkit.CheckBox, objectName=self.objectName) | ||
3477 | 79 | self.assertFalse(self.toggle.checked) | ||
3478 | 80 | |||
3479 | 81 | def test_toggle_custom_proxy_object(self): | ||
3480 | 82 | self.assertIsInstance(self.toggle, ubuntuuitoolkit.CheckBox) | ||
3481 | 83 | |||
3482 | 84 | def test_check_toggle(self): | ||
3483 | 85 | self.toggle.check() | ||
3484 | 86 | self.assertTrue(self.toggle.checked) | ||
3485 | 87 | |||
3486 | 88 | def test_check_toggle_already_checked(self): | ||
3487 | 89 | self.toggle.check() | ||
3488 | 90 | with mock.patch.object(input.Pointer, 'click_object') as mock_click: | ||
3489 | 91 | self.toggle.check() | ||
3490 | 92 | self.assertFalse(mock_click.called) | ||
3491 | 93 | |||
3492 | 94 | def test_uncheck_toggle(self): | ||
3493 | 95 | self.toggle.check() | ||
3494 | 96 | self.toggle.uncheck() | ||
3495 | 97 | self.assertFalse(self.toggle.checked) | ||
3496 | 98 | |||
3497 | 99 | def test_uncheck_toggle_already_unchecked(self): | ||
3498 | 100 | with mock.patch.object(input.Pointer, 'click_object') as mock_click: | ||
3499 | 101 | self.toggle.uncheck() | ||
3500 | 102 | self.assertFalse(mock_click.called) | ||
3501 | 103 | |||
3502 | 104 | def test_change_state_from_checked(self): | ||
3503 | 105 | self.toggle.check() | ||
3504 | 106 | self.toggle.change_state() | ||
3505 | 107 | self.assertFalse(self.toggle.checked) | ||
3506 | 108 | |||
3507 | 109 | def test_change_state_from_unchecked(self): | ||
3508 | 110 | self.toggle.change_state() | ||
3509 | 111 | self.assertTrue(self.toggle.checked) | ||
3510 | 112 | |||
3511 | 113 | def test_check_with_timeout(self): | ||
3512 | 114 | with mock.patch.object( | ||
3513 | 115 | ubuntuuitoolkit.CheckBox, 'change_state') as mock_change: | ||
3514 | 116 | self.toggle.check(timeout=1) | ||
3515 | 117 | |||
3516 | 118 | mock_change.assert_called_once_with(1) | ||
3517 | 119 | |||
3518 | 120 | def test_uncheck_with_timeout(self): | ||
3519 | 121 | self.toggle.check() | ||
3520 | 122 | with mock.patch.object( | ||
3521 | 123 | ubuntuuitoolkit.CheckBox, 'change_state') as mock_change: | ||
3522 | 124 | self.toggle.uncheck(timeout=1) | ||
3523 | 125 | |||
3524 | 126 | mock_change.assert_called_once_with(1) | ||
3525 | 127 | |||
3526 | 128 | def test_change_state_with_timeout(self): | ||
3527 | 129 | with mock.patch.object(self.toggle, 'pointing_device'): | ||
3528 | 130 | # mock the pointing device so the checkbox is not clicked. | ||
3529 | 131 | timestamp_before_call = time.time() | ||
3530 | 132 | self.assertRaises(AssertionError, self.toggle.change_state, 1) | ||
3531 | 133 | |||
3532 | 134 | waiting_time = time.time() - timestamp_before_call | ||
3533 | 135 | self.assertThat(waiting_time, GreaterThan(1)) | ||
3534 | 136 | self.assertThat(waiting_time, LessThan(2)) | ||
3535 | 0 | 137 | ||
3536 | === added file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_common.py' | |||
3537 | --- tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_common.py 1970-01-01 00:00:00 +0000 | |||
3538 | +++ tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_common.py 2014-04-17 01:29:24 +0000 | |||
3539 | @@ -0,0 +1,68 @@ | |||
3540 | 1 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- | ||
3541 | 2 | # | ||
3542 | 3 | # Copyright (C) 2013, 2014 Canonical Ltd. | ||
3543 | 4 | # | ||
3544 | 5 | # This program is free software; you can redistribute it and/or modify | ||
3545 | 6 | # it under the terms of the GNU Lesser General Public License as published by | ||
3546 | 7 | # the Free Software Foundation; version 3. | ||
3547 | 8 | # | ||
3548 | 9 | # This program is distributed in the hope that it will be useful, | ||
3549 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
3550 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
3551 | 12 | # GNU Lesser General Public License for more details. | ||
3552 | 13 | # | ||
3553 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
3554 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
3555 | 16 | |||
3556 | 17 | import unittest | ||
3557 | 18 | |||
3558 | 19 | import autopilot | ||
3559 | 20 | from autopilot import platform, input | ||
3560 | 21 | try: | ||
3561 | 22 | from unittest import mock | ||
3562 | 23 | except ImportError: | ||
3563 | 24 | import mock | ||
3564 | 25 | |||
3565 | 26 | import ubuntuuitoolkit | ||
3566 | 27 | from ubuntuuitoolkit import tests | ||
3567 | 28 | |||
3568 | 29 | |||
3569 | 30 | class CheckAutopilotVersionTestCase(unittest.TestCase): | ||
3570 | 31 | |||
3571 | 32 | def test_lower_version_should_raise_exception(self): | ||
3572 | 33 | with mock.patch.object(autopilot, 'version', '1.3'): | ||
3573 | 34 | self.assertRaises( | ||
3574 | 35 | ubuntuuitoolkit.ToolkitException, | ||
3575 | 36 | ubuntuuitoolkit.check_autopilot_version) | ||
3576 | 37 | |||
3577 | 38 | def test_required_version_should_succeed(self): | ||
3578 | 39 | with mock.patch.object(autopilot, 'version', '1.4'): | ||
3579 | 40 | ubuntuuitoolkit.check_autopilot_version() | ||
3580 | 41 | |||
3581 | 42 | def test_higher_version_should_succeed(self): | ||
3582 | 43 | with mock.patch.object(autopilot, 'version', '1.5'): | ||
3583 | 44 | ubuntuuitoolkit.check_autopilot_version() | ||
3584 | 45 | |||
3585 | 46 | |||
3586 | 47 | class UbuntuUIToolkitCustomProxyObjectBaseTestCase(tests.QMLStringAppTestCase): | ||
3587 | 48 | |||
3588 | 49 | def test_pointing_device(self): | ||
3589 | 50 | self.assertIsInstance(self.app.pointing_device, input.Pointer) | ||
3590 | 51 | |||
3591 | 52 | @unittest.skipIf(platform.model() != 'Desktop', 'Desktop only') | ||
3592 | 53 | def test_pointing_device_in_desktop(self): | ||
3593 | 54 | self.assertIsInstance(self.app.pointing_device._device, input.Mouse) | ||
3594 | 55 | |||
3595 | 56 | @unittest.skipIf(platform.model() == 'Desktop', 'Phablet only') | ||
3596 | 57 | def test_pointing_device_in_phablet(self): | ||
3597 | 58 | self.assertIsInstance(self.app.pointing_device._device, input.Touch) | ||
3598 | 59 | |||
3599 | 60 | def test_custom_proxy_objects_should_check_version_on_init(self): | ||
3600 | 61 | check_name = ( | ||
3601 | 62 | 'ubuntuuitoolkit._custom_proxy_objects._common.' | ||
3602 | 63 | 'check_autopilot_version') | ||
3603 | 64 | with mock.patch(check_name, autospec=True) as mock_check: | ||
3604 | 65 | # Instantiate any custom proxy object. | ||
3605 | 66 | self.main_view | ||
3606 | 67 | |||
3607 | 68 | mock_check.assert_called_once_with() | ||
3608 | 0 | 69 | ||
3609 | === added file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_header.py' | |||
3610 | --- tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_header.py 1970-01-01 00:00:00 +0000 | |||
3611 | +++ tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_header.py 2014-04-17 01:29:24 +0000 | |||
3612 | @@ -0,0 +1,41 @@ | |||
3613 | 1 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- | ||
3614 | 2 | # | ||
3615 | 3 | # Copyright (C) 2013, 2014 Canonical Ltd. | ||
3616 | 4 | # | ||
3617 | 5 | # This program is free software; you can redistribute it and/or modify | ||
3618 | 6 | # it under the terms of the GNU Lesser General Public License as published by | ||
3619 | 7 | # the Free Software Foundation; version 3. | ||
3620 | 8 | # | ||
3621 | 9 | # This program is distributed in the hope that it will be useful, | ||
3622 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
3623 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
3624 | 12 | # GNU Lesser General Public License for more details. | ||
3625 | 13 | # | ||
3626 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
3627 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
3628 | 16 | |||
3629 | 17 | import ubuntuuitoolkit | ||
3630 | 18 | from ubuntuuitoolkit import tests | ||
3631 | 19 | |||
3632 | 20 | |||
3633 | 21 | class PageTestCase(tests.QMLStringAppTestCase): | ||
3634 | 22 | |||
3635 | 23 | test_qml = (""" | ||
3636 | 24 | import QtQuick 2.0 | ||
3637 | 25 | import Ubuntu.Components 0.1 | ||
3638 | 26 | |||
3639 | 27 | MainView { | ||
3640 | 28 | width: units.gu(48) | ||
3641 | 29 | height: units.gu(60) | ||
3642 | 30 | |||
3643 | 31 | Page { | ||
3644 | 32 | title: "Test title" | ||
3645 | 33 | } | ||
3646 | 34 | } | ||
3647 | 35 | """) | ||
3648 | 36 | |||
3649 | 37 | def test_header_custom_proxy_object(self): | ||
3650 | 38 | header = self.main_view.get_header() | ||
3651 | 39 | self.assertIsInstance(header, ubuntuuitoolkit.Header) | ||
3652 | 40 | self.assertTrue(header.visible) | ||
3653 | 41 | self.assertEqual(header.title, "Test title") | ||
3654 | 0 | 42 | ||
3655 | === added file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_listitems.py' | |||
3656 | --- tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_listitems.py 1970-01-01 00:00:00 +0000 | |||
3657 | +++ tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_listitems.py 2014-04-17 01:29:24 +0000 | |||
3658 | @@ -0,0 +1,170 @@ | |||
3659 | 1 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- | ||
3660 | 2 | # | ||
3661 | 3 | # Copyright (C) 2013, 2014 Canonical Ltd. | ||
3662 | 4 | # | ||
3663 | 5 | # This program is free software; you can redistribute it and/or modify | ||
3664 | 6 | # it under the terms of the GNU Lesser General Public License as published by | ||
3665 | 7 | # the Free Software Foundation; version 3. | ||
3666 | 8 | # | ||
3667 | 9 | # This program is distributed in the hope that it will be useful, | ||
3668 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
3669 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
3670 | 12 | # GNU Lesser General Public License for more details. | ||
3671 | 13 | # | ||
3672 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
3673 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
3674 | 16 | |||
3675 | 17 | import ubuntuuitoolkit | ||
3676 | 18 | from ubuntuuitoolkit import tests | ||
3677 | 19 | |||
3678 | 20 | |||
3679 | 21 | class SwipeToDeleteTestCase(tests.QMLStringAppTestCase): | ||
3680 | 22 | |||
3681 | 23 | test_qml = (""" | ||
3682 | 24 | import QtQuick 2.0 | ||
3683 | 25 | import Ubuntu.Components 0.1 | ||
3684 | 26 | import Ubuntu.Components.ListItems 0.1 | ||
3685 | 27 | |||
3686 | 28 | |||
3687 | 29 | MainView { | ||
3688 | 30 | width: units.gu(48) | ||
3689 | 31 | height: units.gu(60) | ||
3690 | 32 | |||
3691 | 33 | Page { | ||
3692 | 34 | |||
3693 | 35 | ListModel { | ||
3694 | 36 | id: testModel | ||
3695 | 37 | |||
3696 | 38 | ListElement { | ||
3697 | 39 | name: "listitem_destroyed_on_remove_with_confirm" | ||
3698 | 40 | label: "Item destroyed on remove with confirmation" | ||
3699 | 41 | confirm: true | ||
3700 | 42 | } | ||
3701 | 43 | ListElement { | ||
3702 | 44 | name: "listitem_destroyed_on_remove_without_confirm" | ||
3703 | 45 | label: "Item destroyed on remove without confirmation" | ||
3704 | 46 | confirm: false | ||
3705 | 47 | } | ||
3706 | 48 | } | ||
3707 | 49 | |||
3708 | 50 | Column { | ||
3709 | 51 | anchors { fill: parent } | ||
3710 | 52 | |||
3711 | 53 | Standard { | ||
3712 | 54 | objectName: "listitem_standard" | ||
3713 | 55 | confirmRemoval: true | ||
3714 | 56 | removable: true | ||
3715 | 57 | text: 'Slide to remove' | ||
3716 | 58 | } | ||
3717 | 59 | |||
3718 | 60 | Empty { | ||
3719 | 61 | objectName: "listitem_empty" | ||
3720 | 62 | } | ||
3721 | 63 | |||
3722 | 64 | Standard { | ||
3723 | 65 | objectName: "listitem_without_confirm" | ||
3724 | 66 | confirmRemoval: false | ||
3725 | 67 | removable: true | ||
3726 | 68 | text: "Item without delete confirmation" | ||
3727 | 69 | } | ||
3728 | 70 | |||
3729 | 71 | ListView { | ||
3730 | 72 | anchors { left: parent.left; right: parent.right } | ||
3731 | 73 | height: childrenRect.height | ||
3732 | 74 | model: testModel | ||
3733 | 75 | |||
3734 | 76 | delegate: Standard { | ||
3735 | 77 | removable: true | ||
3736 | 78 | confirmRemoval: confirm | ||
3737 | 79 | onItemRemoved: testModel.remove(index) | ||
3738 | 80 | text: label | ||
3739 | 81 | objectName: name | ||
3740 | 82 | } | ||
3741 | 83 | } | ||
3742 | 84 | } | ||
3743 | 85 | } | ||
3744 | 86 | } | ||
3745 | 87 | """) | ||
3746 | 88 | |||
3747 | 89 | def setUp(self): | ||
3748 | 90 | super(SwipeToDeleteTestCase, self).setUp() | ||
3749 | 91 | self._item = self.main_view.select_single( | ||
3750 | 92 | ubuntuuitoolkit.Standard, objectName='listitem_standard') | ||
3751 | 93 | self.assertTrue(self._item.exists()) | ||
3752 | 94 | |||
3753 | 95 | def test_supported_class(self): | ||
3754 | 96 | self.assertTrue(issubclass( | ||
3755 | 97 | ubuntuuitoolkit.Base, ubuntuuitoolkit.Empty)) | ||
3756 | 98 | self.assertTrue(issubclass( | ||
3757 | 99 | ubuntuuitoolkit.ItemSelector, ubuntuuitoolkit.Empty)) | ||
3758 | 100 | self.assertTrue(issubclass( | ||
3759 | 101 | ubuntuuitoolkit.Standard, ubuntuuitoolkit.Empty)) | ||
3760 | 102 | self.assertTrue(issubclass( | ||
3761 | 103 | ubuntuuitoolkit.SingleControl, ubuntuuitoolkit.Empty)) | ||
3762 | 104 | self.assertTrue(issubclass( | ||
3763 | 105 | ubuntuuitoolkit.MultiValue, ubuntuuitoolkit.Base)) | ||
3764 | 106 | self.assertTrue(issubclass( | ||
3765 | 107 | ubuntuuitoolkit.SingleValue, ubuntuuitoolkit.Base)) | ||
3766 | 108 | self.assertTrue(issubclass( | ||
3767 | 109 | ubuntuuitoolkit.Subtitled, ubuntuuitoolkit.Base)) | ||
3768 | 110 | |||
3769 | 111 | def test_standard_custom_proxy_object(self): | ||
3770 | 112 | self.assertIsInstance(self._item, ubuntuuitoolkit.Standard) | ||
3771 | 113 | |||
3772 | 114 | def test_swipe_item(self): | ||
3773 | 115 | self._item.swipe_to_delete() | ||
3774 | 116 | self.assertTrue(self._item.waitingConfirmationForRemoval) | ||
3775 | 117 | |||
3776 | 118 | def test_swipe_item_to_right(self): | ||
3777 | 119 | self._item.swipe_to_delete('right') | ||
3778 | 120 | self.assertTrue(self._item.waitingConfirmationForRemoval) | ||
3779 | 121 | |||
3780 | 122 | def test_swipe_item_to_left(self): | ||
3781 | 123 | self._item.swipe_to_delete('left') | ||
3782 | 124 | self.assertTrue(self._item.waitingConfirmationForRemoval) | ||
3783 | 125 | |||
3784 | 126 | def test_swipe_item_to_wrong_direction(self): | ||
3785 | 127 | self.assertRaises( | ||
3786 | 128 | ubuntuuitoolkit.ToolkitException, | ||
3787 | 129 | self._item.swipe_to_delete, 'up') | ||
3788 | 130 | |||
3789 | 131 | def test_delete_item_moving_right(self): | ||
3790 | 132 | self._item.swipe_to_delete('right') | ||
3791 | 133 | self._item.confirm_removal() | ||
3792 | 134 | self.assertFalse(self._item.exists()) | ||
3793 | 135 | |||
3794 | 136 | def test_delete_item_moving_left(self): | ||
3795 | 137 | self._item.swipe_to_delete('left') | ||
3796 | 138 | self._item.confirm_removal() | ||
3797 | 139 | self.assertFalse(self._item.exists()) | ||
3798 | 140 | |||
3799 | 141 | def test_delete_non_removable_item(self): | ||
3800 | 142 | self._item = self.main_view.select_single( | ||
3801 | 143 | ubuntuuitoolkit.Empty, objectName='listitem_empty') | ||
3802 | 144 | self.assertRaises( | ||
3803 | 145 | ubuntuuitoolkit.ToolkitException, self._item.swipe_to_delete) | ||
3804 | 146 | |||
3805 | 147 | def test_confirm_removal_when_item_was_not_swiped(self): | ||
3806 | 148 | self.assertRaises( | ||
3807 | 149 | ubuntuuitoolkit.ToolkitException, self._item.confirm_removal) | ||
3808 | 150 | |||
3809 | 151 | def test_delete_item_without_confirm(self): | ||
3810 | 152 | item = self.main_view.select_single( | ||
3811 | 153 | ubuntuuitoolkit.Standard, objectName='listitem_without_confirm') | ||
3812 | 154 | item.swipe_to_delete() | ||
3813 | 155 | self.assertFalse(item.exists()) | ||
3814 | 156 | |||
3815 | 157 | def test_delete_item_with_confirmation_that_will_be_destroyed(self): | ||
3816 | 158 | item = self.main_view.select_single( | ||
3817 | 159 | ubuntuuitoolkit.Standard, | ||
3818 | 160 | objectName='listitem_destroyed_on_remove_with_confirm') | ||
3819 | 161 | item.swipe_to_delete() | ||
3820 | 162 | item.confirm_removal() | ||
3821 | 163 | self.assertFalse(item.exists()) | ||
3822 | 164 | |||
3823 | 165 | def test_delete_item_without_confirmation_that_will_be_destroyed(self): | ||
3824 | 166 | item = self.main_view.select_single( | ||
3825 | 167 | ubuntuuitoolkit.Standard, | ||
3826 | 168 | objectName='listitem_destroyed_on_remove_without_confirm') | ||
3827 | 169 | item.swipe_to_delete() | ||
3828 | 170 | self.assertFalse(item.exists()) | ||
3829 | 0 | 171 | ||
3830 | === added file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_main_view.py' | |||
3831 | --- tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_main_view.py 1970-01-01 00:00:00 +0000 | |||
3832 | +++ tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_main_view.py 2014-04-17 01:29:24 +0000 | |||
3833 | @@ -0,0 +1,132 @@ | |||
3834 | 1 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- | ||
3835 | 2 | # | ||
3836 | 3 | # Copyright (C) 2013, 2014 Canonical Ltd. | ||
3837 | 4 | # | ||
3838 | 5 | # This program is free software; you can redistribute it and/or modify | ||
3839 | 6 | # it under the terms of the GNU Lesser General Public License as published by | ||
3840 | 7 | # the Free Software Foundation; version 3. | ||
3841 | 8 | # | ||
3842 | 9 | # This program is distributed in the hope that it will be useful, | ||
3843 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
3844 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
3845 | 12 | # GNU Lesser General Public License for more details. | ||
3846 | 13 | # | ||
3847 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
3848 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
3849 | 16 | |||
3850 | 17 | try: | ||
3851 | 18 | from unittest import mock | ||
3852 | 19 | except ImportError: | ||
3853 | 20 | import mock | ||
3854 | 21 | |||
3855 | 22 | import ubuntuuitoolkit | ||
3856 | 23 | from ubuntuuitoolkit import tests | ||
3857 | 24 | |||
3858 | 25 | |||
3859 | 26 | class MainViewTestCase(tests.QMLStringAppTestCase): | ||
3860 | 27 | |||
3861 | 28 | test_qml = (""" | ||
3862 | 29 | import QtQuick 2.0 | ||
3863 | 30 | import Ubuntu.Components 0.1 | ||
3864 | 31 | |||
3865 | 32 | MainView { | ||
3866 | 33 | width: units.gu(48) | ||
3867 | 34 | height: units.gu(60) | ||
3868 | 35 | } | ||
3869 | 36 | """) | ||
3870 | 37 | |||
3871 | 38 | def test_main_view_custom_proxy_object(self): | ||
3872 | 39 | self.assertIsInstance(self.main_view, ubuntuuitoolkit.MainView) | ||
3873 | 40 | |||
3874 | 41 | def test_get_header_without_header(self): | ||
3875 | 42 | header = self.main_view.get_header() | ||
3876 | 43 | self.assertFalse(header.visible) | ||
3877 | 44 | |||
3878 | 45 | def test_toolbar_custom_proxy_object(self): | ||
3879 | 46 | toolbar = self.main_view.get_toolbar() | ||
3880 | 47 | self.assertIsInstance(toolbar, ubuntuuitoolkit.Toolbar) | ||
3881 | 48 | |||
3882 | 49 | def test_open_toolbar(self): | ||
3883 | 50 | with mock.patch.object(ubuntuuitoolkit.Toolbar, 'open') as mock_open: | ||
3884 | 51 | self.main_view.open_toolbar() | ||
3885 | 52 | |||
3886 | 53 | mock_open.assert_called_once_with() | ||
3887 | 54 | |||
3888 | 55 | def test_close_toolbar(self): | ||
3889 | 56 | with mock.patch.object(ubuntuuitoolkit.Toolbar, 'close') as mock_close: | ||
3890 | 57 | self.main_view.close_toolbar() | ||
3891 | 58 | |||
3892 | 59 | mock_close.assert_called_once_with() | ||
3893 | 60 | |||
3894 | 61 | def test_open_toolbar_returns_the_toolbar(self): | ||
3895 | 62 | toolbar = self.main_view.open_toolbar() | ||
3896 | 63 | self.assertIsInstance(toolbar, ubuntuuitoolkit.Toolbar) | ||
3897 | 64 | |||
3898 | 65 | def test_get_tabs_without_tabs(self): | ||
3899 | 66 | error = self.assertRaises( | ||
3900 | 67 | ubuntuuitoolkit.ToolkitException, self.main_view.get_tabs) | ||
3901 | 68 | self.assertEqual( | ||
3902 | 69 | str(error), 'The MainView has no Tabs.') | ||
3903 | 70 | |||
3904 | 71 | def test_switch_to_next_tab_without_tabs(self): | ||
3905 | 72 | header = self.main_view.get_header() | ||
3906 | 73 | error = self.assertRaises( | ||
3907 | 74 | ubuntuuitoolkit.ToolkitException, | ||
3908 | 75 | header.switch_to_next_tab) | ||
3909 | 76 | self.assertEqual( | ||
3910 | 77 | str(error), 'The MainView has no Tabs.') | ||
3911 | 78 | |||
3912 | 79 | |||
3913 | 80 | class GoBackTestCase(tests.QMLStringAppTestCase): | ||
3914 | 81 | |||
3915 | 82 | test_qml = (""" | ||
3916 | 83 | import QtQuick 2.0 | ||
3917 | 84 | import Ubuntu.Components 0.1 | ||
3918 | 85 | |||
3919 | 86 | MainView { | ||
3920 | 87 | width: units.gu(48) | ||
3921 | 88 | height: units.gu(60) | ||
3922 | 89 | |||
3923 | 90 | PageStack { | ||
3924 | 91 | id: pageStack | ||
3925 | 92 | Component.onCompleted: push(page0) | ||
3926 | 93 | |||
3927 | 94 | Page { | ||
3928 | 95 | id: page0 | ||
3929 | 96 | title: "Page 0" | ||
3930 | 97 | visible: false | ||
3931 | 98 | |||
3932 | 99 | Button { | ||
3933 | 100 | objectName: "go_to_page1" | ||
3934 | 101 | text: "Go to page 1" | ||
3935 | 102 | onClicked: pageStack.push(page1) | ||
3936 | 103 | } | ||
3937 | 104 | } | ||
3938 | 105 | |||
3939 | 106 | Page { | ||
3940 | 107 | id: page1 | ||
3941 | 108 | title: "Page 1" | ||
3942 | 109 | visible: false | ||
3943 | 110 | } | ||
3944 | 111 | } | ||
3945 | 112 | } | ||
3946 | 113 | """) | ||
3947 | 114 | |||
3948 | 115 | def setUp(self): | ||
3949 | 116 | super(GoBackTestCase, self).setUp() | ||
3950 | 117 | self.header = self.main_view.get_header() | ||
3951 | 118 | self.assertEqual(self.header.title, 'Page 0') | ||
3952 | 119 | |||
3953 | 120 | def test_open_page(self): | ||
3954 | 121 | self._go_to_page1() | ||
3955 | 122 | self.assertEqual(self.header.title, 'Page 1') | ||
3956 | 123 | |||
3957 | 124 | def _go_to_page1(self): | ||
3958 | 125 | button = self.main_view.select_single( | ||
3959 | 126 | 'Button', objectName='go_to_page1') | ||
3960 | 127 | self.pointing_device.click_object(button) | ||
3961 | 128 | |||
3962 | 129 | def test_go_back(self): | ||
3963 | 130 | self._go_to_page1() | ||
3964 | 131 | self.main_view.go_back() | ||
3965 | 132 | self.assertEqual(self.header.title, 'Page 0') | ||
3966 | 0 | 133 | ||
3967 | === added file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_popups.py' | |||
3968 | --- tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_popups.py 1970-01-01 00:00:00 +0000 | |||
3969 | +++ tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_popups.py 2014-04-17 01:29:24 +0000 | |||
3970 | @@ -0,0 +1,172 @@ | |||
3971 | 1 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- | ||
3972 | 2 | # | ||
3973 | 3 | # Copyright (C) 2013, 2014 Canonical Ltd. | ||
3974 | 4 | # | ||
3975 | 5 | # This program is free software; you can redistribute it and/or modify | ||
3976 | 6 | # it under the terms of the GNU Lesser General Public License as published by | ||
3977 | 7 | # the Free Software Foundation; version 3. | ||
3978 | 8 | # | ||
3979 | 9 | # This program is distributed in the hope that it will be useful, | ||
3980 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
3981 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
3982 | 12 | # GNU Lesser General Public License for more details. | ||
3983 | 13 | # | ||
3984 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
3985 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
3986 | 16 | |||
3987 | 17 | from autopilot.introspection import dbus | ||
3988 | 18 | |||
3989 | 19 | import ubuntuuitoolkit | ||
3990 | 20 | from ubuntuuitoolkit import tests | ||
3991 | 21 | |||
3992 | 22 | |||
3993 | 23 | class ActionSelectionPopoverTestCase(tests.QMLStringAppTestCase): | ||
3994 | 24 | |||
3995 | 25 | test_qml = (""" | ||
3996 | 26 | import QtQuick 2.0 | ||
3997 | 27 | import Ubuntu.Components 0.1 | ||
3998 | 28 | import Ubuntu.Components.Popups 0.1 | ||
3999 | 29 | |||
4000 | 30 | MainView { | ||
4001 | 31 | width: units.gu(48) | ||
4002 | 32 | height: units.gu(60) | ||
4003 | 33 | |||
4004 | 34 | Button { | ||
4005 | 35 | objectName: "open_popover" | ||
4006 | 36 | text: "Open Popover" | ||
4007 | 37 | onClicked: testActionsPopover.show(); | ||
4008 | 38 | } | ||
4009 | 39 | |||
4010 | 40 | Label { | ||
4011 | 41 | id: "label" | ||
4012 | 42 | objectName: "clicked_label" | ||
4013 | 43 | anchors.centerIn: parent | ||
4014 | 44 | text: "Button not clicked." | ||
4015 | 45 | } | ||
4016 | 46 | |||
4017 | 47 | ActionSelectionPopover { | ||
4018 | 48 | objectName: "test_actions_popover" | ||
4019 | 49 | id: testActionsPopover | ||
4020 | 50 | actions: ActionList { | ||
4021 | 51 | Action { | ||
4022 | 52 | text: "Action one" | ||
4023 | 53 | onTriggered: label.text = "Button clicked." | ||
4024 | 54 | } | ||
4025 | 55 | } | ||
4026 | 56 | } | ||
4027 | 57 | } | ||
4028 | 58 | """) | ||
4029 | 59 | |||
4030 | 60 | def test_action_selection_popover_custom_proxy_object(self): | ||
4031 | 61 | popover = self.main_view.get_action_selection_popover( | ||
4032 | 62 | 'test_actions_popover') | ||
4033 | 63 | self.assertIsInstance(popover, ubuntuuitoolkit.ActionSelectionPopover) | ||
4034 | 64 | |||
4035 | 65 | def test_click_action_select_popover_button(self): | ||
4036 | 66 | label = self.app.select_single('Label', objectName='clicked_label') | ||
4037 | 67 | self.assertNotEqual(label.text, 'Button clicked.') | ||
4038 | 68 | self._open_popover() | ||
4039 | 69 | popover = self.main_view.get_action_selection_popover( | ||
4040 | 70 | 'test_actions_popover') | ||
4041 | 71 | popover.click_button_by_text('Action one') | ||
4042 | 72 | self.assertEqual(label.text, 'Button clicked.') | ||
4043 | 73 | |||
4044 | 74 | def _open_popover(self): | ||
4045 | 75 | open_button = self.main_view.select_single( | ||
4046 | 76 | 'Button', objectName='open_popover') | ||
4047 | 77 | self.pointing_device.click_object(open_button) | ||
4048 | 78 | |||
4049 | 79 | def test_click_unexisting_button(self): | ||
4050 | 80 | self._open_popover() | ||
4051 | 81 | popover = self.main_view.get_action_selection_popover( | ||
4052 | 82 | 'test_actions_popover') | ||
4053 | 83 | error = self.assertRaises( | ||
4054 | 84 | ubuntuuitoolkit.ToolkitException, popover.click_button_by_text, | ||
4055 | 85 | 'unexisting') | ||
4056 | 86 | self.assertEqual( | ||
4057 | 87 | str(error), 'Button with text "unexisting" not found.') | ||
4058 | 88 | |||
4059 | 89 | def test_click_button_with_closed_popover(self): | ||
4060 | 90 | popover = self.main_view.get_action_selection_popover( | ||
4061 | 91 | 'test_actions_popover') | ||
4062 | 92 | error = self.assertRaises( | ||
4063 | 93 | ubuntuuitoolkit.ToolkitException, popover.click_button_by_text, | ||
4064 | 94 | 'Action one') | ||
4065 | 95 | self.assertEqual( | ||
4066 | 96 | str(error), 'The popover is not open.') | ||
4067 | 97 | |||
4068 | 98 | |||
4069 | 99 | class ComposerSheetTestCase(tests.QMLStringAppTestCase): | ||
4070 | 100 | |||
4071 | 101 | test_qml = (""" | ||
4072 | 102 | import QtQuick 2.0 | ||
4073 | 103 | import Ubuntu.Components 0.1 | ||
4074 | 104 | import Ubuntu.Components.Popups 0.1 | ||
4075 | 105 | |||
4076 | 106 | MainView { | ||
4077 | 107 | width: units.gu(48) | ||
4078 | 108 | height: units.gu(60) | ||
4079 | 109 | |||
4080 | 110 | Button { | ||
4081 | 111 | objectName: "openComposerSheetButton" | ||
4082 | 112 | text: "Open Composer Sheet" | ||
4083 | 113 | onClicked: PopupUtils.open(testComposerSheet); | ||
4084 | 114 | } | ||
4085 | 115 | |||
4086 | 116 | Label { | ||
4087 | 117 | id: "label" | ||
4088 | 118 | objectName: "actionLabel" | ||
4089 | 119 | anchors.centerIn: parent | ||
4090 | 120 | text: "No action taken." | ||
4091 | 121 | } | ||
4092 | 122 | |||
4093 | 123 | Component { | ||
4094 | 124 | id: testComposerSheet | ||
4095 | 125 | ComposerSheet { | ||
4096 | 126 | id: sheet | ||
4097 | 127 | objectName: "testComposerSheet" | ||
4098 | 128 | onCancelClicked: { | ||
4099 | 129 | label.text = "Cancel selected." | ||
4100 | 130 | } | ||
4101 | 131 | onConfirmClicked: { | ||
4102 | 132 | label.text = "Confirm selected." | ||
4103 | 133 | } | ||
4104 | 134 | } | ||
4105 | 135 | } | ||
4106 | 136 | } | ||
4107 | 137 | """) | ||
4108 | 138 | |||
4109 | 139 | def setUp(self): | ||
4110 | 140 | super(ComposerSheetTestCase, self).setUp() | ||
4111 | 141 | self.label = self.main_view.select_single( | ||
4112 | 142 | 'Label', objectName='actionLabel') | ||
4113 | 143 | self.assertEqual(self.label.text, 'No action taken.') | ||
4114 | 144 | self._open_composer_sheet() | ||
4115 | 145 | self.composer_sheet = self._select_composer_sheet() | ||
4116 | 146 | |||
4117 | 147 | def _open_composer_sheet(self): | ||
4118 | 148 | button = self.main_view.select_single( | ||
4119 | 149 | 'Button', objectName='openComposerSheetButton') | ||
4120 | 150 | self.pointing_device.click_object(button) | ||
4121 | 151 | |||
4122 | 152 | def _select_composer_sheet(self): | ||
4123 | 153 | return self.main_view.select_single( | ||
4124 | 154 | ubuntuuitoolkit.ComposerSheet, objectName='testComposerSheet') | ||
4125 | 155 | |||
4126 | 156 | def test_select_composer_sheet_custom_proxy_object(self): | ||
4127 | 157 | self.assertIsInstance( | ||
4128 | 158 | self.composer_sheet, ubuntuuitoolkit.ComposerSheet) | ||
4129 | 159 | |||
4130 | 160 | def test_confirm_composer_sheet(self): | ||
4131 | 161 | self.composer_sheet.confirm() | ||
4132 | 162 | self.assertEqual(self.label.text, 'Confirm selected.') | ||
4133 | 163 | self._assert_composer_sheet_is_closed() | ||
4134 | 164 | |||
4135 | 165 | def _assert_composer_sheet_is_closed(self): | ||
4136 | 166 | self.assertRaises( | ||
4137 | 167 | dbus.StateNotFoundError, self._select_composer_sheet) | ||
4138 | 168 | |||
4139 | 169 | def test_cancel_composer_sheet(self): | ||
4140 | 170 | self.composer_sheet.cancel() | ||
4141 | 171 | self.assertEqual(self.label.text, 'Cancel selected.') | ||
4142 | 172 | self._assert_composer_sheet_is_closed() | ||
4143 | 0 | 173 | ||
4144 | === added file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_qquicklistview.py' | |||
4145 | --- tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_qquicklistview.py 1970-01-01 00:00:00 +0000 | |||
4146 | +++ tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_qquicklistview.py 2014-04-17 01:29:24 +0000 | |||
4147 | @@ -0,0 +1,193 @@ | |||
4148 | 1 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- | ||
4149 | 2 | # | ||
4150 | 3 | # Copyright (C) 2013, 2014 Canonical Ltd. | ||
4151 | 4 | # | ||
4152 | 5 | # This program is free software; you can redistribute it and/or modify | ||
4153 | 6 | # it under the terms of the GNU Lesser General Public License as published by | ||
4154 | 7 | # the Free Software Foundation; version 3. | ||
4155 | 8 | # | ||
4156 | 9 | # This program is distributed in the hope that it will be useful, | ||
4157 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
4158 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
4159 | 12 | # GNU Lesser General Public License for more details. | ||
4160 | 13 | # | ||
4161 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
4162 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
4163 | 16 | |||
4164 | 17 | from autopilot.introspection import dbus | ||
4165 | 18 | |||
4166 | 19 | import ubuntuuitoolkit | ||
4167 | 20 | from ubuntuuitoolkit import tests | ||
4168 | 21 | |||
4169 | 22 | |||
4170 | 23 | class QQuickListViewTestCase(tests.QMLStringAppTestCase): | ||
4171 | 24 | |||
4172 | 25 | test_qml = (""" | ||
4173 | 26 | import QtQuick 2.0 | ||
4174 | 27 | import Ubuntu.Components 0.1 | ||
4175 | 28 | import Ubuntu.Components.ListItems 0.1 as ListItem | ||
4176 | 29 | |||
4177 | 30 | MainView { | ||
4178 | 31 | width: units.gu(48) | ||
4179 | 32 | height: units.gu(20) | ||
4180 | 33 | |||
4181 | 34 | Page { | ||
4182 | 35 | |||
4183 | 36 | Column { | ||
4184 | 37 | id: column | ||
4185 | 38 | width: units.gu(48) | ||
4186 | 39 | height: units.gu(20) | ||
4187 | 40 | |||
4188 | 41 | Label { | ||
4189 | 42 | id: clickedLabel | ||
4190 | 43 | objectName: "clickedLabel" | ||
4191 | 44 | text: "No element clicked." | ||
4192 | 45 | } | ||
4193 | 46 | |||
4194 | 47 | ListView { | ||
4195 | 48 | id: testListView | ||
4196 | 49 | objectName: "testListView" | ||
4197 | 50 | anchors.left: parent.left | ||
4198 | 51 | anchors.right: parent.right | ||
4199 | 52 | height: column.height - clickedLabel.paintedHeight | ||
4200 | 53 | clip: true | ||
4201 | 54 | model: 20 | ||
4202 | 55 | |||
4203 | 56 | delegate: ListItem.Standard { | ||
4204 | 57 | objectName: "testListElement%1".arg(index) | ||
4205 | 58 | text: "test list element %1".arg(index) | ||
4206 | 59 | onClicked: clickedLabel.text = objectName | ||
4207 | 60 | height: units.gu(5) | ||
4208 | 61 | } | ||
4209 | 62 | } | ||
4210 | 63 | } | ||
4211 | 64 | } | ||
4212 | 65 | } | ||
4213 | 66 | """) | ||
4214 | 67 | |||
4215 | 68 | def setUp(self): | ||
4216 | 69 | super(QQuickListViewTestCase, self).setUp() | ||
4217 | 70 | self.list_view = self.main_view.select_single( | ||
4218 | 71 | ubuntuuitoolkit.QQuickListView, objectName='testListView') | ||
4219 | 72 | self.label = self.main_view.select_single( | ||
4220 | 73 | 'Label', objectName='clickedLabel') | ||
4221 | 74 | self.assertEqual(self.label.text, 'No element clicked.') | ||
4222 | 75 | |||
4223 | 76 | def test_qquicklistview_custom_proxy_object(self): | ||
4224 | 77 | self.assertIsInstance(self.list_view, ubuntuuitoolkit.QQuickListView) | ||
4225 | 78 | |||
4226 | 79 | def test_click_element(self): | ||
4227 | 80 | self.list_view.click_element('testListElement0') | ||
4228 | 81 | self.assertEqual(self.label.text, 'testListElement0') | ||
4229 | 82 | |||
4230 | 83 | def test_click_element_outside_view_below(self): | ||
4231 | 84 | # Click the first element out of view to make sure we are not scrolling | ||
4232 | 85 | # to the bottom at once. | ||
4233 | 86 | self.assertFalse( | ||
4234 | 87 | self.list_view._is_element_clickable('testListElement5')) | ||
4235 | 88 | |||
4236 | 89 | self.list_view.click_element('testListElement5') | ||
4237 | 90 | self.assertEqual(self.label.text, 'testListElement5') | ||
4238 | 91 | |||
4239 | 92 | def test_click_element_outside_view_above(self): | ||
4240 | 93 | self.list_view.click_element('testListElement9') | ||
4241 | 94 | |||
4242 | 95 | # Click the first element out of view to make sure we are not scrolling | ||
4243 | 96 | # to the top at once. | ||
4244 | 97 | self.assertFalse( | ||
4245 | 98 | self.list_view._is_element_clickable('testListElement4')) | ||
4246 | 99 | |||
4247 | 100 | self.list_view.click_element('testListElement4') | ||
4248 | 101 | self.assertEqual(self.label.text, 'testListElement4') | ||
4249 | 102 | |||
4250 | 103 | def test_click_element_not_created_at_start(self): | ||
4251 | 104 | objectName = 'testListElement19' | ||
4252 | 105 | self.assertRaises( | ||
4253 | 106 | dbus.StateNotFoundError, | ||
4254 | 107 | self.list_view.select_single, | ||
4255 | 108 | objectName=objectName) | ||
4256 | 109 | self.list_view.click_element(objectName) | ||
4257 | 110 | |||
4258 | 111 | def test_click_unexisting_element(self): | ||
4259 | 112 | error = self.assertRaises( | ||
4260 | 113 | ubuntuuitoolkit.ToolkitException, | ||
4261 | 114 | self.list_view.click_element, | ||
4262 | 115 | 'unexisting') | ||
4263 | 116 | self.assertEqual( | ||
4264 | 117 | str(error), 'List element with objectName "unexisting" not found.') | ||
4265 | 118 | |||
4266 | 119 | |||
4267 | 120 | class QQuickListViewOutOfViewTestCase(tests.QMLStringAppTestCase): | ||
4268 | 121 | """Test that we can make elements visible when the list is out of view.""" | ||
4269 | 122 | |||
4270 | 123 | test_qml = (""" | ||
4271 | 124 | import QtQuick 2.0 | ||
4272 | 125 | import Ubuntu.Components 0.1 | ||
4273 | 126 | import Ubuntu.Components.ListItems 0.1 as ListItem | ||
4274 | 127 | |||
4275 | 128 | MainView { | ||
4276 | 129 | width: units.gu(48) | ||
4277 | 130 | height: units.gu(20) | ||
4278 | 131 | |||
4279 | 132 | Page { | ||
4280 | 133 | |||
4281 | 134 | Flickable { | ||
4282 | 135 | |||
4283 | 136 | Column { | ||
4284 | 137 | id: column | ||
4285 | 138 | width: units.gu(48) | ||
4286 | 139 | // The column height is greater than the main view height, so | ||
4287 | 140 | // the bottom of the list is out of view. | ||
4288 | 141 | height: units.gu(40) | ||
4289 | 142 | |||
4290 | 143 | Label { | ||
4291 | 144 | id: clickedLabel | ||
4292 | 145 | objectName: "clickedLabel" | ||
4293 | 146 | text: "No element clicked." | ||
4294 | 147 | } | ||
4295 | 148 | |||
4296 | 149 | ListView { | ||
4297 | 150 | id: testListView | ||
4298 | 151 | objectName: "testListView" | ||
4299 | 152 | anchors.left: parent.left | ||
4300 | 153 | anchors.right: parent.right | ||
4301 | 154 | height: column.height - clickedLabel.paintedHeight | ||
4302 | 155 | clip: true | ||
4303 | 156 | model: 20 | ||
4304 | 157 | |||
4305 | 158 | delegate: ListItem.Standard { | ||
4306 | 159 | objectName: "testListElement%1".arg(index) | ||
4307 | 160 | text: "test list element %1".arg(index) | ||
4308 | 161 | onClicked: clickedLabel.text = objectName | ||
4309 | 162 | height: units.gu(5) | ||
4310 | 163 | } | ||
4311 | 164 | } | ||
4312 | 165 | } | ||
4313 | 166 | } | ||
4314 | 167 | } | ||
4315 | 168 | } | ||
4316 | 169 | """) | ||
4317 | 170 | |||
4318 | 171 | def setUp(self): | ||
4319 | 172 | super(QQuickListViewOutOfViewTestCase, self).setUp() | ||
4320 | 173 | self.list_view = self.main_view.select_single( | ||
4321 | 174 | ubuntuuitoolkit.QQuickListView, objectName='testListView') | ||
4322 | 175 | self.label = self.main_view.select_single( | ||
4323 | 176 | 'Label', objectName='clickedLabel') | ||
4324 | 177 | self.assertEqual(self.label.text, 'No element clicked.') | ||
4325 | 178 | |||
4326 | 179 | def test_click_element_outside_view_below(self): | ||
4327 | 180 | """Test that we can click an element that's out of view below. | ||
4328 | 181 | |||
4329 | 182 | The list is also out of view, so we must scroll from the bottom of the | ||
4330 | 183 | main view. | ||
4331 | 184 | |||
4332 | 185 | """ | ||
4333 | 186 | # Test for http://pad.lv/1275060. | ||
4334 | 187 | # Click the first element out of view to make sure we are not scrolling | ||
4335 | 188 | # to the bottom at once. | ||
4336 | 189 | self.assertFalse( | ||
4337 | 190 | self.list_view._is_element_clickable('testListElement9')) | ||
4338 | 191 | |||
4339 | 192 | self.list_view.click_element('testListElement9') | ||
4340 | 193 | self.assertEqual(self.label.text, 'testListElement9') | ||
4341 | 0 | 194 | ||
4342 | === added file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_tabs.py' | |||
4343 | --- tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_tabs.py 1970-01-01 00:00:00 +0000 | |||
4344 | +++ tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_tabs.py 2014-04-17 01:29:24 +0000 | |||
4345 | @@ -0,0 +1,149 @@ | |||
4346 | 1 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- | ||
4347 | 2 | # | ||
4348 | 3 | # Copyright (C) 2013, 2014 Canonical Ltd. | ||
4349 | 4 | # | ||
4350 | 5 | # This program is free software; you can redistribute it and/or modify | ||
4351 | 6 | # it under the terms of the GNU Lesser General Public License as published by | ||
4352 | 7 | # the Free Software Foundation; version 3. | ||
4353 | 8 | # | ||
4354 | 9 | # This program is distributed in the hope that it will be useful, | ||
4355 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
4356 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
4357 | 12 | # GNU Lesser General Public License for more details. | ||
4358 | 13 | # | ||
4359 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
4360 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
4361 | 16 | |||
4362 | 17 | try: | ||
4363 | 18 | from unittest import mock | ||
4364 | 19 | except ImportError: | ||
4365 | 20 | import mock | ||
4366 | 21 | |||
4367 | 22 | import ubuntuuitoolkit | ||
4368 | 23 | from ubuntuuitoolkit import tests | ||
4369 | 24 | |||
4370 | 25 | |||
4371 | 26 | class TabsTestCase(tests.QMLStringAppTestCase): | ||
4372 | 27 | |||
4373 | 28 | test_qml = (""" | ||
4374 | 29 | import QtQuick 2.0 | ||
4375 | 30 | import Ubuntu.Components 0.1 | ||
4376 | 31 | import Ubuntu.Components.ListItems 0.1 as ListItem | ||
4377 | 32 | |||
4378 | 33 | MainView { | ||
4379 | 34 | width: units.gu(70) | ||
4380 | 35 | height: units.gu(60) | ||
4381 | 36 | |||
4382 | 37 | Tabs { | ||
4383 | 38 | id: tabs | ||
4384 | 39 | Tab { | ||
4385 | 40 | objectName: "tab1" | ||
4386 | 41 | title: "Tab1" | ||
4387 | 42 | Page { | ||
4388 | 43 | tools: ToolbarItems { | ||
4389 | 44 | ToolbarButton { | ||
4390 | 45 | text: "Test1" | ||
4391 | 46 | } | ||
4392 | 47 | } | ||
4393 | 48 | } | ||
4394 | 49 | } | ||
4395 | 50 | Tab { | ||
4396 | 51 | objectName: "tab2" | ||
4397 | 52 | title: "Tab2" | ||
4398 | 53 | Page { | ||
4399 | 54 | tools: ToolbarItems { | ||
4400 | 55 | ToolbarButton { | ||
4401 | 56 | text: "Test2" | ||
4402 | 57 | } | ||
4403 | 58 | } | ||
4404 | 59 | } | ||
4405 | 60 | } | ||
4406 | 61 | Tab { | ||
4407 | 62 | objectName: "tab3" | ||
4408 | 63 | title: "Tab3" | ||
4409 | 64 | Page { | ||
4410 | 65 | tools: ToolbarItems { | ||
4411 | 66 | ToolbarButton { | ||
4412 | 67 | text: "Test3" | ||
4413 | 68 | } | ||
4414 | 69 | } | ||
4415 | 70 | } | ||
4416 | 71 | } | ||
4417 | 72 | } | ||
4418 | 73 | } | ||
4419 | 74 | """) | ||
4420 | 75 | |||
4421 | 76 | def test_tabs_custom_proxy_object(self): | ||
4422 | 77 | self.assertIsInstance(self.main_view.get_tabs(), ubuntuuitoolkit.Tabs) | ||
4423 | 78 | |||
4424 | 79 | def test_get_current_tab(self): | ||
4425 | 80 | tabs = self.main_view.get_tabs() | ||
4426 | 81 | self.assertEqual(tabs.get_current_tab().title, 'Tab1') | ||
4427 | 82 | |||
4428 | 83 | def test_switch_to_next_tab_from_header(self): | ||
4429 | 84 | header = self.main_view.get_header() | ||
4430 | 85 | header.switch_to_next_tab() | ||
4431 | 86 | current_tab = self.main_view.get_tabs().get_current_tab() | ||
4432 | 87 | self.assertEqual(current_tab.title, 'Tab2') | ||
4433 | 88 | |||
4434 | 89 | def test_switch_to_next_tab_from_main_view(self): | ||
4435 | 90 | current_tab = self.main_view.switch_to_next_tab() | ||
4436 | 91 | self.assertEqual(current_tab.title, 'Tab2') | ||
4437 | 92 | |||
4438 | 93 | def test_switch_to_next_tab_from_last(self): | ||
4439 | 94 | last_tab_index = self.main_view.get_tabs().get_number_of_tabs() - 1 | ||
4440 | 95 | self.main_view.switch_to_tab_by_index(last_tab_index) | ||
4441 | 96 | current_tab = self.main_view.switch_to_next_tab() | ||
4442 | 97 | self.assertEqual(current_tab.title, 'Tab1') | ||
4443 | 98 | |||
4444 | 99 | def test_switch_to_tab_by_index(self): | ||
4445 | 100 | current_tab = self.main_view.switch_to_tab_by_index(2) | ||
4446 | 101 | self.assertEqual(current_tab.title, 'Tab3') | ||
4447 | 102 | current_tab = self.main_view.switch_to_tab_by_index(1) | ||
4448 | 103 | self.assertEqual(current_tab.title, 'Tab2') | ||
4449 | 104 | current_tab = self.main_view.switch_to_tab_by_index(0) | ||
4450 | 105 | self.assertEqual(current_tab.title, 'Tab1') | ||
4451 | 106 | |||
4452 | 107 | def test_switch_to_opened_tab_is_not_opened_again(self): | ||
4453 | 108 | with mock.patch.object( | ||
4454 | 109 | ubuntuuitoolkit.Header, 'switch_to_next_tab') as mock_switch: | ||
4455 | 110 | current_tab = self.main_view.switch_to_tab_by_index(0) | ||
4456 | 111 | |||
4457 | 112 | self.assertFalse(mock_switch.called) | ||
4458 | 113 | self.assertEqual(current_tab.title, 'Tab1') | ||
4459 | 114 | |||
4460 | 115 | def test_get_number_of_tabs(self): | ||
4461 | 116 | tabs = self.main_view.get_tabs() | ||
4462 | 117 | self.assertEqual(tabs.get_number_of_tabs(), 3) | ||
4463 | 118 | |||
4464 | 119 | def test_swith_to_tab_by_index_out_of_range(self): | ||
4465 | 120 | last_tab_index = self.main_view.get_tabs().get_number_of_tabs() - 1 | ||
4466 | 121 | error = self.assertRaises( | ||
4467 | 122 | ubuntuuitoolkit.ToolkitException, | ||
4468 | 123 | self.main_view.switch_to_tab_by_index, | ||
4469 | 124 | last_tab_index + 1) | ||
4470 | 125 | self.assertEqual(str(error), 'Tab index out of range.') | ||
4471 | 126 | |||
4472 | 127 | def test_switch_to_previous_tab_from_first(self): | ||
4473 | 128 | current_tab = self.main_view.switch_to_previous_tab() | ||
4474 | 129 | self.assertEqual(current_tab.title, 'Tab3') | ||
4475 | 130 | |||
4476 | 131 | def test_switch_to_previous_tab_not_from_first(self): | ||
4477 | 132 | self.main_view.switch_to_tab_by_index(1) | ||
4478 | 133 | current_tab = self.main_view.switch_to_previous_tab() | ||
4479 | 134 | self.assertEqual(current_tab.title, 'Tab1') | ||
4480 | 135 | |||
4481 | 136 | def test_switch_to_tab_by_object_name(self): | ||
4482 | 137 | current_tab = self.main_view.switch_to_tab('tab3') | ||
4483 | 138 | self.assertEqual(current_tab.title, 'Tab3') | ||
4484 | 139 | current_tab = self.main_view.switch_to_tab('tab2') | ||
4485 | 140 | self.assertEqual(current_tab.title, 'Tab2') | ||
4486 | 141 | current_tab = self.main_view.switch_to_tab('tab1') | ||
4487 | 142 | self.assertEqual(current_tab.title, 'Tab1') | ||
4488 | 143 | |||
4489 | 144 | def test_switch_to_unexisting_tab(self): | ||
4490 | 145 | error = self.assertRaises( | ||
4491 | 146 | ubuntuuitoolkit.ToolkitException, self.main_view.switch_to_tab, | ||
4492 | 147 | 'unexisting') | ||
4493 | 148 | self.assertEqual( | ||
4494 | 149 | str(error), 'Tab with objectName "unexisting" not found.') | ||
4495 | 0 | 150 | ||
4496 | === added file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_textfield.py' | |||
4497 | --- tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_textfield.py 1970-01-01 00:00:00 +0000 | |||
4498 | +++ tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_textfield.py 2014-04-17 01:29:24 +0000 | |||
4499 | @@ -0,0 +1,93 @@ | |||
4500 | 1 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- | ||
4501 | 2 | # | ||
4502 | 3 | # Copyright (C) 2013, 2014 Canonical Ltd. | ||
4503 | 4 | # | ||
4504 | 5 | # This program is free software; you can redistribute it and/or modify | ||
4505 | 6 | # it under the terms of the GNU Lesser General Public License as published by | ||
4506 | 7 | # the Free Software Foundation; version 3. | ||
4507 | 8 | # | ||
4508 | 9 | # This program is distributed in the hope that it will be useful, | ||
4509 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
4510 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
4511 | 12 | # GNU Lesser General Public License for more details. | ||
4512 | 13 | # | ||
4513 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
4514 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
4515 | 16 | |||
4516 | 17 | import ubuntuuitoolkit | ||
4517 | 18 | from ubuntuuitoolkit import tests | ||
4518 | 19 | |||
4519 | 20 | |||
4520 | 21 | class TextFieldTestCase(tests.QMLStringAppTestCase): | ||
4521 | 22 | |||
4522 | 23 | test_qml = (""" | ||
4523 | 24 | import QtQuick 2.0 | ||
4524 | 25 | import Ubuntu.Components 0.1 | ||
4525 | 26 | |||
4526 | 27 | MainView { | ||
4527 | 28 | width: units.gu(48) | ||
4528 | 29 | height: units.gu(60) | ||
4529 | 30 | |||
4530 | 31 | Item { | ||
4531 | 32 | TextField { | ||
4532 | 33 | id: simpleTextField | ||
4533 | 34 | objectName: "simple_text_field" | ||
4534 | 35 | } | ||
4535 | 36 | TextField { | ||
4536 | 37 | id: textFieldWithoutClearButton | ||
4537 | 38 | objectName: "text_field_without_clear_button" | ||
4538 | 39 | hasClearButton: false | ||
4539 | 40 | anchors.top: simpleTextField.bottom | ||
4540 | 41 | } | ||
4541 | 42 | } | ||
4542 | 43 | } | ||
4543 | 44 | """) | ||
4544 | 45 | |||
4545 | 46 | def setUp(self): | ||
4546 | 47 | super(TextFieldTestCase, self).setUp() | ||
4547 | 48 | self.simple_text_field = self.main_view.select_single( | ||
4548 | 49 | ubuntuuitoolkit.TextField, objectName='simple_text_field') | ||
4549 | 50 | |||
4550 | 51 | def test_text_field_custom_proxy_object(self): | ||
4551 | 52 | self.assertIsInstance( | ||
4552 | 53 | self.simple_text_field, ubuntuuitoolkit.TextField) | ||
4553 | 54 | |||
4554 | 55 | def test_write(self): | ||
4555 | 56 | self.simple_text_field.write('test') | ||
4556 | 57 | self.assertEqual(self.simple_text_field.text, 'test') | ||
4557 | 58 | |||
4558 | 59 | def test_clear_with_clear_button(self): | ||
4559 | 60 | self.simple_text_field.write('test') | ||
4560 | 61 | self.simple_text_field.clear() | ||
4561 | 62 | self.assertEqual(self.simple_text_field.text, '') | ||
4562 | 63 | |||
4563 | 64 | def test_clear_without_clear_button(self): | ||
4564 | 65 | text_field = self.main_view.select_single( | ||
4565 | 66 | ubuntuuitoolkit.TextField, | ||
4566 | 67 | objectName='text_field_without_clear_button') | ||
4567 | 68 | text_field.write('test') | ||
4568 | 69 | text_field.clear() | ||
4569 | 70 | self.assertEqual(text_field.text, '') | ||
4570 | 71 | |||
4571 | 72 | def test_clear_and_write(self): | ||
4572 | 73 | self.simple_text_field.write('test1') | ||
4573 | 74 | self.simple_text_field.write('test2') | ||
4574 | 75 | self.assertEqual(self.simple_text_field.text, 'test2') | ||
4575 | 76 | |||
4576 | 77 | def test_write_without_clear(self): | ||
4577 | 78 | self.simple_text_field.write('test1') | ||
4578 | 79 | self.simple_text_field.write('test2', clear=False) | ||
4579 | 80 | self.assertEqual(self.simple_text_field.text, 'test1test2') | ||
4580 | 81 | |||
4581 | 82 | def test_write_without_clear_writes_at_the_end(self): | ||
4582 | 83 | self.simple_text_field.write( | ||
4583 | 84 | 'long text that will fill more than half of the text field.') | ||
4584 | 85 | self.simple_text_field.write('append', clear=False) | ||
4585 | 86 | self.assertEqual( | ||
4586 | 87 | self.simple_text_field.text, | ||
4587 | 88 | 'long text that will fill more than half of the text field.append') | ||
4588 | 89 | |||
4589 | 90 | def test_is_empty(self): | ||
4590 | 91 | self.assertTrue(self.simple_text_field.is_empty()) | ||
4591 | 92 | self.simple_text_field.write('test') | ||
4592 | 93 | self.assertFalse(self.simple_text_field.is_empty()) | ||
4593 | 0 | 94 | ||
4594 | === added file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_toolbar.py' | |||
4595 | --- tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_toolbar.py 1970-01-01 00:00:00 +0000 | |||
4596 | +++ tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_toolbar.py 2014-04-17 01:29:24 +0000 | |||
4597 | @@ -0,0 +1,121 @@ | |||
4598 | 1 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- | ||
4599 | 2 | # | ||
4600 | 3 | # Copyright (C) 2013, 2014 Canonical Ltd. | ||
4601 | 4 | # | ||
4602 | 5 | # This program is free software; you can redistribute it and/or modify | ||
4603 | 6 | # it under the terms of the GNU Lesser General Public License as published by | ||
4604 | 7 | # the Free Software Foundation; version 3. | ||
4605 | 8 | # | ||
4606 | 9 | # This program is distributed in the hope that it will be useful, | ||
4607 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
4608 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
4609 | 12 | # GNU Lesser General Public License for more details. | ||
4610 | 13 | # | ||
4611 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
4612 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
4613 | 16 | |||
4614 | 17 | try: | ||
4615 | 18 | from unittest import mock | ||
4616 | 19 | except ImportError: | ||
4617 | 20 | import mock | ||
4618 | 21 | |||
4619 | 22 | import ubuntuuitoolkit | ||
4620 | 23 | from ubuntuuitoolkit import tests | ||
4621 | 24 | |||
4622 | 25 | |||
4623 | 26 | class ToolbarTestCase(tests.QMLStringAppTestCase): | ||
4624 | 27 | |||
4625 | 28 | test_qml = (""" | ||
4626 | 29 | import QtQuick 2.0 | ||
4627 | 30 | import Ubuntu.Components 0.1 | ||
4628 | 31 | |||
4629 | 32 | MainView { | ||
4630 | 33 | width: units.gu(50) | ||
4631 | 34 | height: units.gu(50) | ||
4632 | 35 | |||
4633 | 36 | // Make sure that for these tests the toolbar starts closed. | ||
4634 | 37 | Component.onCompleted: { | ||
4635 | 38 | __propagated.toolbar.close(); | ||
4636 | 39 | } | ||
4637 | 40 | |||
4638 | 41 | Page { | ||
4639 | 42 | |||
4640 | 43 | Label { | ||
4641 | 44 | id: "label" | ||
4642 | 45 | objectName: "clicked_label" | ||
4643 | 46 | anchors.centerIn: parent | ||
4644 | 47 | text: "Button not clicked." | ||
4645 | 48 | } | ||
4646 | 49 | |||
4647 | 50 | tools: ToolbarItems { | ||
4648 | 51 | ToolbarButton { | ||
4649 | 52 | objectName: "buttonName" | ||
4650 | 53 | action: Action { | ||
4651 | 54 | text: "buttonText" | ||
4652 | 55 | onTriggered: label.text = "Button clicked." | ||
4653 | 56 | } | ||
4654 | 57 | } | ||
4655 | 58 | } | ||
4656 | 59 | } | ||
4657 | 60 | } | ||
4658 | 61 | """) | ||
4659 | 62 | |||
4660 | 63 | def setUp(self): | ||
4661 | 64 | super(ToolbarTestCase, self).setUp() | ||
4662 | 65 | self.toolbar = self.main_view.get_toolbar() | ||
4663 | 66 | # toolbar may be opened or closed now, depending on whether | ||
4664 | 67 | # the application has been deactivated and resumed already | ||
4665 | 68 | |||
4666 | 69 | def test_open_toolbar(self): | ||
4667 | 70 | self.toolbar.open() | ||
4668 | 71 | self.assertTrue(self.toolbar.opened) | ||
4669 | 72 | self.assertFalse(self.toolbar.animating) | ||
4670 | 73 | |||
4671 | 74 | def test_opened_toolbar_is_not_opened_again(self): | ||
4672 | 75 | self.toolbar.open() | ||
4673 | 76 | with mock.patch.object( | ||
4674 | 77 | self.main_view.pointing_device, 'drag') as mock_drag: | ||
4675 | 78 | self.toolbar.open() | ||
4676 | 79 | |||
4677 | 80 | self.assertFalse(mock_drag.called) | ||
4678 | 81 | self.assertTrue(self.toolbar.opened) | ||
4679 | 82 | |||
4680 | 83 | def test_close_toolbar(self): | ||
4681 | 84 | self.toolbar.open() | ||
4682 | 85 | self.toolbar.close() | ||
4683 | 86 | self.assertFalse(self.toolbar.opened) | ||
4684 | 87 | self.assertFalse(self.toolbar.animating) | ||
4685 | 88 | |||
4686 | 89 | def test_closed_toolbar_is_not_closed_again(self): | ||
4687 | 90 | self.toolbar.close() | ||
4688 | 91 | with mock.patch.object( | ||
4689 | 92 | self.main_view.pointing_device, 'drag') as mock_drag: | ||
4690 | 93 | self.toolbar.close() | ||
4691 | 94 | |||
4692 | 95 | self.assertFalse(mock_drag.called) | ||
4693 | 96 | self.assertFalse(self.toolbar.opened) | ||
4694 | 97 | |||
4695 | 98 | def test_click_toolbar_button(self): | ||
4696 | 99 | self.toolbar.close() | ||
4697 | 100 | label = self.app.select_single('Label', objectName='clicked_label') | ||
4698 | 101 | self.assertNotEqual(label.text, 'Button clicked.') | ||
4699 | 102 | self.toolbar.open() | ||
4700 | 103 | self.toolbar.click_button('buttonName') | ||
4701 | 104 | self.assertEqual(label.text, 'Button clicked.') | ||
4702 | 105 | |||
4703 | 106 | def test_click_unexisting_button(self): | ||
4704 | 107 | self.main_view.open_toolbar() | ||
4705 | 108 | error = self.assertRaises( | ||
4706 | 109 | ubuntuuitoolkit.ToolkitException, self.toolbar.click_button, | ||
4707 | 110 | 'unexisting') | ||
4708 | 111 | self.assertEqual( | ||
4709 | 112 | str(error), 'Button with objectName "unexisting" not found.') | ||
4710 | 113 | |||
4711 | 114 | def test_click_button_on_closed_toolbar(self): | ||
4712 | 115 | self.toolbar.close() | ||
4713 | 116 | error = self.assertRaises( | ||
4714 | 117 | ubuntuuitoolkit.ToolkitException, self.toolbar.click_button, | ||
4715 | 118 | 'buttonName') | ||
4716 | 119 | self.assertEqual( | ||
4717 | 120 | str(error), | ||
4718 | 121 | 'Toolbar must be opened before calling click_button().') | ||
4719 | 0 | 122 | ||
4720 | === removed file 'tests/autopilot/ubuntuuitoolkit/tests/test_emulators.py' | |||
4721 | --- tests/autopilot/ubuntuuitoolkit/tests/test_emulators.py 2014-03-22 06:53:24 +0000 | |||
4722 | +++ tests/autopilot/ubuntuuitoolkit/tests/test_emulators.py 1970-01-01 00:00:00 +0000 | |||
4723 | @@ -1,1081 +0,0 @@ | |||
4724 | 1 | # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- | ||
4725 | 2 | # | ||
4726 | 3 | # Copyright (C) 2013 Canonical Ltd. | ||
4727 | 4 | # | ||
4728 | 5 | # This program is free software; you can redistribute it and/or modify | ||
4729 | 6 | # it under the terms of the GNU Lesser General Public License as published by | ||
4730 | 7 | # the Free Software Foundation; version 3. | ||
4731 | 8 | # | ||
4732 | 9 | # This program is distributed in the hope that it will be useful, | ||
4733 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
4734 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
4735 | 12 | # GNU Lesser General Public License for more details. | ||
4736 | 13 | # | ||
4737 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
4738 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
4739 | 16 | |||
4740 | 17 | import time | ||
4741 | 18 | import unittest | ||
4742 | 19 | |||
4743 | 20 | import autopilot | ||
4744 | 21 | from autopilot import input, platform | ||
4745 | 22 | from autopilot.introspection import dbus | ||
4746 | 23 | from testtools.matchers import GreaterThan, LessThan | ||
4747 | 24 | try: | ||
4748 | 25 | from unittest import mock | ||
4749 | 26 | except ImportError: | ||
4750 | 27 | import mock | ||
4751 | 28 | |||
4752 | 29 | from ubuntuuitoolkit import emulators, tests | ||
4753 | 30 | |||
4754 | 31 | |||
4755 | 32 | class CheckAutopilotVersionTestCase(unittest.TestCase): | ||
4756 | 33 | |||
4757 | 34 | def test_lower_version_should_raise_exception(self): | ||
4758 | 35 | with mock.patch.object(autopilot, 'version', '1.3'): | ||
4759 | 36 | self.assertRaises( | ||
4760 | 37 | emulators.ToolkitEmulatorException, | ||
4761 | 38 | emulators.check_autopilot_version) | ||
4762 | 39 | |||
4763 | 40 | def test_required_version_should_succeed(self): | ||
4764 | 41 | with mock.patch.object(autopilot, 'version', '1.4'): | ||
4765 | 42 | emulators.check_autopilot_version() | ||
4766 | 43 | |||
4767 | 44 | def test_higher_version_should_succeed(self): | ||
4768 | 45 | with mock.patch.object(autopilot, 'version', '1.5'): | ||
4769 | 46 | emulators.check_autopilot_version() | ||
4770 | 47 | |||
4771 | 48 | |||
4772 | 49 | class UbuntuUIToolkitEmulatorBaseTestCase(tests.QMLStringAppTestCase): | ||
4773 | 50 | |||
4774 | 51 | def test_pointing_device(self): | ||
4775 | 52 | self.assertIsInstance(self.app.pointing_device, input.Pointer) | ||
4776 | 53 | |||
4777 | 54 | @unittest.skipIf(platform.model() != 'Desktop', 'Desktop only') | ||
4778 | 55 | def test_pointing_device_in_desktop(self): | ||
4779 | 56 | self.assertIsInstance(self.app.pointing_device._device, input.Mouse) | ||
4780 | 57 | |||
4781 | 58 | @unittest.skipIf(platform.model() == 'Desktop', 'Phablet only') | ||
4782 | 59 | def test_pointing_device_in_phablet(self): | ||
4783 | 60 | self.assertIsInstance(self.app.pointing_device._device, input.Touch) | ||
4784 | 61 | |||
4785 | 62 | def test_emulators_should_check_version_on_init(self): | ||
4786 | 63 | check_name = 'ubuntuuitoolkit.emulators.check_autopilot_version' | ||
4787 | 64 | with mock.patch(check_name, autospec=True) as mock_check: | ||
4788 | 65 | # Instantiate any emulator. | ||
4789 | 66 | self.main_view | ||
4790 | 67 | |||
4791 | 68 | mock_check.assert_called_once_with() | ||
4792 | 69 | |||
4793 | 70 | |||
4794 | 71 | class MainViewTestCase(tests.QMLStringAppTestCase): | ||
4795 | 72 | |||
4796 | 73 | test_qml = (""" | ||
4797 | 74 | import QtQuick 2.0 | ||
4798 | 75 | import Ubuntu.Components 0.1 | ||
4799 | 76 | |||
4800 | 77 | MainView { | ||
4801 | 78 | width: units.gu(48) | ||
4802 | 79 | height: units.gu(60) | ||
4803 | 80 | } | ||
4804 | 81 | """) | ||
4805 | 82 | |||
4806 | 83 | def test_main_view_custom_emulator(self): | ||
4807 | 84 | self.assertIsInstance(self.main_view, emulators.MainView) | ||
4808 | 85 | |||
4809 | 86 | def test_get_header_without_header(self): | ||
4810 | 87 | header = self.main_view.get_header() | ||
4811 | 88 | self.assertFalse(header.visible) | ||
4812 | 89 | |||
4813 | 90 | def test_toolbar_custom_emulator(self): | ||
4814 | 91 | toolbar = self.main_view.get_toolbar() | ||
4815 | 92 | self.assertIsInstance(toolbar, emulators.Toolbar) | ||
4816 | 93 | |||
4817 | 94 | def test_open_toolbar(self): | ||
4818 | 95 | with mock.patch.object(emulators.Toolbar, 'open') as mock_open: | ||
4819 | 96 | self.main_view.open_toolbar() | ||
4820 | 97 | |||
4821 | 98 | mock_open.assert_called_once_with() | ||
4822 | 99 | |||
4823 | 100 | def test_close_toolbar(self): | ||
4824 | 101 | with mock.patch.object(emulators.Toolbar, 'close') as mock_close: | ||
4825 | 102 | self.main_view.close_toolbar() | ||
4826 | 103 | |||
4827 | 104 | mock_close.assert_called_once_with() | ||
4828 | 105 | |||
4829 | 106 | def test_open_toolbar_returns_the_toolbar(self): | ||
4830 | 107 | toolbar = self.main_view.open_toolbar() | ||
4831 | 108 | self.assertIsInstance(toolbar, emulators.Toolbar) | ||
4832 | 109 | |||
4833 | 110 | def test_get_tabs_without_tabs(self): | ||
4834 | 111 | error = self.assertRaises( | ||
4835 | 112 | emulators.ToolkitEmulatorException, self.main_view.get_tabs) | ||
4836 | 113 | self.assertEqual( | ||
4837 | 114 | str(error), 'The MainView has no Tabs.') | ||
4838 | 115 | |||
4839 | 116 | def test_switch_to_next_tab_without_tabs(self): | ||
4840 | 117 | header = self.main_view.get_header() | ||
4841 | 118 | error = self.assertRaises( | ||
4842 | 119 | emulators.ToolkitEmulatorException, header.switch_to_next_tab) | ||
4843 | 120 | self.assertEqual( | ||
4844 | 121 | str(error), 'The MainView has no Tabs.') | ||
4845 | 122 | |||
4846 | 123 | |||
4847 | 124 | class PageTestCase(tests.QMLStringAppTestCase): | ||
4848 | 125 | |||
4849 | 126 | test_qml = (""" | ||
4850 | 127 | import QtQuick 2.0 | ||
4851 | 128 | import Ubuntu.Components 0.1 | ||
4852 | 129 | |||
4853 | 130 | MainView { | ||
4854 | 131 | width: units.gu(48) | ||
4855 | 132 | height: units.gu(60) | ||
4856 | 133 | |||
4857 | 134 | Page { | ||
4858 | 135 | title: "Test title" | ||
4859 | 136 | } | ||
4860 | 137 | } | ||
4861 | 138 | """) | ||
4862 | 139 | |||
4863 | 140 | def test_header_custom_emulator(self): | ||
4864 | 141 | header = self.main_view.get_header() | ||
4865 | 142 | self.assertIsInstance(header, emulators.Header) | ||
4866 | 143 | self.assertTrue(header.visible) | ||
4867 | 144 | self.assertEqual(header.title, "Test title") | ||
4868 | 145 | |||
4869 | 146 | |||
4870 | 147 | class ToolbarTestCase(tests.QMLStringAppTestCase): | ||
4871 | 148 | |||
4872 | 149 | test_qml = (""" | ||
4873 | 150 | import QtQuick 2.0 | ||
4874 | 151 | import Ubuntu.Components 0.1 | ||
4875 | 152 | |||
4876 | 153 | MainView { | ||
4877 | 154 | width: units.gu(50) | ||
4878 | 155 | height: units.gu(50) | ||
4879 | 156 | |||
4880 | 157 | // Make sure that for these tests the toolbar starts closed. | ||
4881 | 158 | Component.onCompleted: { | ||
4882 | 159 | __propagated.toolbar.close(); | ||
4883 | 160 | } | ||
4884 | 161 | |||
4885 | 162 | Page { | ||
4886 | 163 | |||
4887 | 164 | Label { | ||
4888 | 165 | id: "label" | ||
4889 | 166 | objectName: "clicked_label" | ||
4890 | 167 | anchors.centerIn: parent | ||
4891 | 168 | text: "Button not clicked." | ||
4892 | 169 | } | ||
4893 | 170 | |||
4894 | 171 | tools: ToolbarItems { | ||
4895 | 172 | ToolbarButton { | ||
4896 | 173 | objectName: "buttonName" | ||
4897 | 174 | action: Action { | ||
4898 | 175 | text: "buttonText" | ||
4899 | 176 | onTriggered: label.text = "Button clicked." | ||
4900 | 177 | } | ||
4901 | 178 | } | ||
4902 | 179 | } | ||
4903 | 180 | } | ||
4904 | 181 | } | ||
4905 | 182 | """) | ||
4906 | 183 | |||
4907 | 184 | def setUp(self): | ||
4908 | 185 | super(ToolbarTestCase, self).setUp() | ||
4909 | 186 | self.toolbar = self.main_view.get_toolbar() | ||
4910 | 187 | # toolbar may be opened or closed now, depending on whether | ||
4911 | 188 | # the application has been deactivated and resumed already | ||
4912 | 189 | |||
4913 | 190 | def test_open_toolbar(self): | ||
4914 | 191 | self.toolbar.open() | ||
4915 | 192 | self.assertTrue(self.toolbar.opened) | ||
4916 | 193 | self.assertFalse(self.toolbar.animating) | ||
4917 | 194 | |||
4918 | 195 | def test_opened_toolbar_is_not_opened_again(self): | ||
4919 | 196 | self.toolbar.open() | ||
4920 | 197 | with mock.patch.object( | ||
4921 | 198 | self.main_view.pointing_device, 'drag') as mock_drag: | ||
4922 | 199 | self.toolbar.open() | ||
4923 | 200 | |||
4924 | 201 | self.assertFalse(mock_drag.called) | ||
4925 | 202 | self.assertTrue(self.toolbar.opened) | ||
4926 | 203 | |||
4927 | 204 | def test_close_toolbar(self): | ||
4928 | 205 | self.toolbar.open() | ||
4929 | 206 | self.toolbar.close() | ||
4930 | 207 | self.assertFalse(self.toolbar.opened) | ||
4931 | 208 | self.assertFalse(self.toolbar.animating) | ||
4932 | 209 | |||
4933 | 210 | def test_closed_toolbar_is_not_closed_again(self): | ||
4934 | 211 | self.toolbar.close() | ||
4935 | 212 | with mock.patch.object( | ||
4936 | 213 | self.main_view.pointing_device, 'drag') as mock_drag: | ||
4937 | 214 | self.toolbar.close() | ||
4938 | 215 | |||
4939 | 216 | self.assertFalse(mock_drag.called) | ||
4940 | 217 | self.assertFalse(self.toolbar.opened) | ||
4941 | 218 | |||
4942 | 219 | def test_click_toolbar_button(self): | ||
4943 | 220 | self.toolbar.close() | ||
4944 | 221 | label = self.app.select_single('Label', objectName='clicked_label') | ||
4945 | 222 | self.assertNotEqual(label.text, 'Button clicked.') | ||
4946 | 223 | self.toolbar.open() | ||
4947 | 224 | self.toolbar.click_button('buttonName') | ||
4948 | 225 | self.assertEqual(label.text, 'Button clicked.') | ||
4949 | 226 | |||
4950 | 227 | def test_click_unexisting_button(self): | ||
4951 | 228 | self.main_view.open_toolbar() | ||
4952 | 229 | error = self.assertRaises( | ||
4953 | 230 | emulators.ToolkitEmulatorException, self.toolbar.click_button, | ||
4954 | 231 | 'unexisting') | ||
4955 | 232 | self.assertEqual( | ||
4956 | 233 | str(error), 'Button with objectName "unexisting" not found.') | ||
4957 | 234 | |||
4958 | 235 | def test_click_button_on_closed_toolbar(self): | ||
4959 | 236 | self.toolbar.close() | ||
4960 | 237 | error = self.assertRaises( | ||
4961 | 238 | emulators.ToolkitEmulatorException, self.toolbar.click_button, | ||
4962 | 239 | 'buttonName') | ||
4963 | 240 | self.assertEqual( | ||
4964 | 241 | str(error), | ||
4965 | 242 | 'Toolbar must be opened before calling click_button().') | ||
4966 | 243 | |||
4967 | 244 | |||
4968 | 245 | class TabsTestCase(tests.QMLStringAppTestCase): | ||
4969 | 246 | |||
4970 | 247 | test_qml = (""" | ||
4971 | 248 | import QtQuick 2.0 | ||
4972 | 249 | import Ubuntu.Components 0.1 | ||
4973 | 250 | import Ubuntu.Components.ListItems 0.1 as ListItem | ||
4974 | 251 | |||
4975 | 252 | MainView { | ||
4976 | 253 | width: units.gu(70) | ||
4977 | 254 | height: units.gu(60) | ||
4978 | 255 | |||
4979 | 256 | Tabs { | ||
4980 | 257 | id: tabs | ||
4981 | 258 | Tab { | ||
4982 | 259 | objectName: "tab1" | ||
4983 | 260 | title: "Tab1" | ||
4984 | 261 | Page { | ||
4985 | 262 | tools: ToolbarItems { | ||
4986 | 263 | ToolbarButton { | ||
4987 | 264 | text: "Test1" | ||
4988 | 265 | } | ||
4989 | 266 | } | ||
4990 | 267 | } | ||
4991 | 268 | } | ||
4992 | 269 | Tab { | ||
4993 | 270 | objectName: "tab2" | ||
4994 | 271 | title: "Tab2" | ||
4995 | 272 | Page { | ||
4996 | 273 | tools: ToolbarItems { | ||
4997 | 274 | ToolbarButton { | ||
4998 | 275 | text: "Test2" | ||
4999 | 276 | } | ||
5000 | 277 | } |
FAILED: Continuous integration, rev:1010 jenkins. qa.ubuntu. com/job/ ubuntu- ui-toolkit- ci/2022/ jenkins. qa.ubuntu. com/job/ generic- deb-autopilot- trusty- touch/200/ console jenkins. qa.ubuntu. com/job/ generic- mediumtests- trusty/ 4777/console jenkins. qa.ubuntu. com/job/ ubuntu- ui-toolkit- trusty- amd64-ci/ 970/console jenkins. qa.ubuntu. com/job/ ubuntu- ui-toolkit- trusty- armhf-ci/ 970/console jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- trusty- armhf/4358/ console jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- trusty- amd64/4909/ console
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/ubuntu- ui-toolkit- ci/2022/ rebuild
http://