Merge lp:~bfiller/ubuntu-keyboard/back-to-rev-44 into lp:ubuntu-keyboard
- back-to-rev-44
- Merge into trunk
Proposed by
Bill Filler
Status: | Merged |
---|---|
Approved by: | Bill Filler |
Approved revision: | 49 |
Merged at revision: | 47 |
Proposed branch: | lp:~bfiller/ubuntu-keyboard/back-to-rev-44 |
Merge into: | lp:ubuntu-keyboard |
Diff against target: |
947 lines (+449/-238) 10 files modified
data/data.pro (+4/-1) data/schemas/com.canonical.keyboard.maliit.gschema.xml (+30/-0) debian/changelog (+8/-4) debian/ubuntu-keyboard-data.install (+1/-0) qml/KeyboardContainer.qml (+2/-0) tests/autopilot/ubuntu_keyboard/emulators/__init__.py (+6/-0) tests/autopilot/ubuntu_keyboard/emulators/key.py (+33/-0) tests/autopilot/ubuntu_keyboard/emulators/keyboard.py (+164/-211) tests/autopilot/ubuntu_keyboard/emulators/keypad.py (+174/-0) tests/autopilot/ubuntu_keyboard/tests/test_keyboard.py (+27/-22) |
To merge this branch: | bzr merge lp:~bfiller/ubuntu-keyboard/back-to-rev-44 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Bill Filler (community) | Approve | ||
PS Jenkins bot | continuous-integration | Approve | |
Review via email: mp+187829@code.launchpad.net |
Commit message
revert back to rev 44
Description of the change
revert back to rev 44
To post a comment you must log in.
- 48. By Bill Filler
-
fix changelog
- 49. By Bill Filler
-
fix changelog
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : | # |
review:
Approve
(continuous-integration)
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'data/data.pro' |
2 | --- data/data.pro 2013-09-26 10:08:30 +0000 |
3 | +++ data/data.pro 2013-09-26 16:09:59 +0000 |
4 | @@ -8,10 +8,13 @@ |
5 | # make it available for testing, not intended for proper release though: |
6 | languages.files += languages/debug/showcase.xml |
7 | |
8 | +schemas.path = $${PREFIX}/share/glib-2.0/schemas |
9 | +schemas.files = schemas/*.gschema.xml |
10 | + |
11 | styles.path = $${UBUNTU_KEYBOARD_DATA_DIR} |
12 | styles.files = styles |
13 | |
14 | -INSTALLS += languages styles |
15 | +INSTALLS += languages schemas styles |
16 | |
17 | QMAKE_EXTRA_TARGETS += check |
18 | check.target = check |
19 | |
20 | === added directory 'data/schemas' |
21 | === added file 'data/schemas/com.canonical.keyboard.maliit.gschema.xml' |
22 | --- data/schemas/com.canonical.keyboard.maliit.gschema.xml 1970-01-01 00:00:00 +0000 |
23 | +++ data/schemas/com.canonical.keyboard.maliit.gschema.xml 2013-09-26 16:09:59 +0000 |
24 | @@ -0,0 +1,30 @@ |
25 | +<?xml version="1.0" encoding="UTF-8"?> |
26 | +<schemalist> |
27 | + <schema id="com.canonical.keyboard.maliit" path="/com/canonical/keyboard/maliit/"> |
28 | + <key name="enabled-languages" type="as"> |
29 | + <summary>Enabled languages</summary> |
30 | + <description>User-defined list of activatable languages.</description> |
31 | + <default>[]</default> |
32 | + </key> |
33 | + <key name="auto-capitalization" type="b"> |
34 | + <summary>Auto-capitalization</summary> |
35 | + <description>Capitalize the first letter of each sentence.</description> |
36 | + <default>true</default> |
37 | + </key> |
38 | + <key name="auto-completion" type="b"> |
39 | + <summary>Auto-completion</summary> |
40 | + <description>Complete current word with first suggestion when hitting space.</description> |
41 | + <default>true</default> |
42 | + </key> |
43 | + <key name="predictive-text" type="b"> |
44 | + <summary>Predictive text</summary> |
45 | + <description>Suggest potential words in word ribbon.</description> |
46 | + <default>true</default> |
47 | + </key> |
48 | + <key name="key-press-feedback" type="b"> |
49 | + <summary>Key press feedback</summary> |
50 | + <description>Vibrate or emit sound on key press.</description> |
51 | + <default>true</default> |
52 | + </key> |
53 | + </schema> |
54 | +</schemalist> |
55 | |
56 | === modified file 'debian/changelog' |
57 | --- debian/changelog 2013-09-26 10:37:17 +0000 |
58 | +++ debian/changelog 2013-09-26 16:09:59 +0000 |
59 | @@ -1,7 +1,11 @@ |
60 | ubuntu-keyboard (0.99.trunk.phablet2+13.10.20130926-0ubuntu1) saucy; urgency=low |
61 | - |
62 | + |
63 | [ Ubuntu daily release ] |
64 | - * New rebuild forced |
65 | + * New rebuild forced from rev 45 |
66 | + |
67 | + -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Thu, 26 Sep 2013 10:37:17 +0000 |
68 | + |
69 | +ubuntu-keyboard (0.99.trunk.phablet2+13.10.20130925-0ubuntu1) saucy; urgency=low |
70 | |
71 | [ William Hua ] |
72 | * Add GSettings schema file. |
73 | @@ -11,9 +15,9 @@ |
74 | (moving more UI code to QML). |
75 | |
76 | [ Ubuntu daily release ] |
77 | - * Automatic snapshot from revision 45 |
78 | + * Automatic snapshot from revision 43 |
79 | |
80 | - -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Thu, 26 Sep 2013 10:37:17 +0000 |
81 | + -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Wed, 25 Sep 2013 11:05:45 +0000 |
82 | |
83 | ubuntu-keyboard (0.99.trunk.phablet2+13.10.20130920-0ubuntu1) saucy; urgency=low |
84 | |
85 | |
86 | === modified file 'debian/ubuntu-keyboard-data.install' |
87 | --- debian/ubuntu-keyboard-data.install 2013-09-26 10:08:30 +0000 |
88 | +++ debian/ubuntu-keyboard-data.install 2013-09-26 16:09:59 +0000 |
89 | @@ -1,1 +1,2 @@ |
90 | +usr/share/glib-2.0/schemas/ |
91 | usr/share/maliit/plugins/com/ubuntu/ |
92 | |
93 | === modified file 'qml/KeyboardContainer.qml' |
94 | --- qml/KeyboardContainer.qml 2013-09-26 10:08:30 +0000 |
95 | +++ qml/KeyboardContainer.qml 2013-09-26 16:09:59 +0000 |
96 | @@ -47,6 +47,7 @@ |
97 | |
98 | Loader { |
99 | id: characterKeypadLoader |
100 | + objectName: "characterKeyPadLoader" |
101 | anchors.fill: parent |
102 | asynchronous: true |
103 | source: "languages/Keyboard_en_us.qml" |
104 | @@ -59,6 +60,7 @@ |
105 | |
106 | Loader { |
107 | id: symbolKeypadLoader |
108 | + objectName: "symbolKeyPadLoader" |
109 | anchors.fill: parent |
110 | asynchronous: true |
111 | } |
112 | |
113 | === modified file 'tests/autopilot/ubuntu_keyboard/emulators/__init__.py' |
114 | --- tests/autopilot/ubuntu_keyboard/emulators/__init__.py 2013-09-26 10:08:30 +0000 |
115 | +++ tests/autopilot/ubuntu_keyboard/emulators/__init__.py 2013-09-26 16:09:59 +0000 |
116 | @@ -16,3 +16,9 @@ |
117 | # You should have received a copy of the GNU General Public License |
118 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
119 | # |
120 | + |
121 | +from autopilot.introspection import CustomEmulatorBase |
122 | + |
123 | + |
124 | +class UbuntuKeyboardEmulatorBase(CustomEmulatorBase): |
125 | + """A base class for all Ubuntu Keyboard emulators.""" |
126 | |
127 | === added file 'tests/autopilot/ubuntu_keyboard/emulators/key.py' |
128 | --- tests/autopilot/ubuntu_keyboard/emulators/key.py 1970-01-01 00:00:00 +0000 |
129 | +++ tests/autopilot/ubuntu_keyboard/emulators/key.py 2013-09-26 16:09:59 +0000 |
130 | @@ -0,0 +1,33 @@ |
131 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
132 | +# |
133 | +# Ubuntu Keyboard Test Suite |
134 | +# Copyright (C) 2013 Canonical |
135 | +# |
136 | +# This program is free software: you can redistribute it and/or modify |
137 | +# it under the terms of the GNU General Public License as published by |
138 | +# the Free Software Foundation, either version 3 of the License, or |
139 | +# (at your option) any later version. |
140 | +# |
141 | +# This program is distributed in the hope that it will be useful, |
142 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
143 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
144 | +# GNU General Public License for more details. |
145 | +# |
146 | +# You should have received a copy of the GNU General Public License |
147 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
148 | +# |
149 | + |
150 | +from ubuntu_keyboard.emulators import UbuntuKeyboardEmulatorBase |
151 | + |
152 | +from collections import namedtuple |
153 | +import logging |
154 | + |
155 | +logger = logging.getLogger(__name__) |
156 | + |
157 | + |
158 | +class Key(UbuntuKeyboardEmulatorBase): |
159 | + """An emulator that encapsulates details of a keyboard key (i.e. extended |
160 | + characters). |
161 | + |
162 | + """ |
163 | + Pos = namedtuple("KeyPosition", ['x', 'y', 'w', 'h']) |
164 | |
165 | === modified file 'tests/autopilot/ubuntu_keyboard/emulators/keyboard.py' |
166 | --- tests/autopilot/ubuntu_keyboard/emulators/keyboard.py 2013-09-26 10:08:30 +0000 |
167 | +++ tests/autopilot/ubuntu_keyboard/emulators/keyboard.py 2013-09-26 16:09:59 +0000 |
168 | @@ -17,7 +17,9 @@ |
169 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
170 | # |
171 | |
172 | -from collections import defaultdict, namedtuple |
173 | +from ubuntu_keyboard.emulators import UbuntuKeyboardEmulatorBase |
174 | +from ubuntu_keyboard.emulators.keypad import KeyPad |
175 | + |
176 | from time import sleep |
177 | import logging |
178 | |
179 | @@ -31,70 +33,69 @@ |
180 | logger = logging.getLogger(__name__) |
181 | |
182 | |
183 | -# Definitions of enums used within the cpp source code. |
184 | -class KeyboardState: |
185 | - DEFAULT = 0 |
186 | - SHIFTED = 1 |
187 | - SYMBOL_1 = 2 |
188 | - SYMBOL_2 = 3 |
189 | - |
190 | - |
191 | -class KeyAction: |
192 | - INSERT = 0 |
193 | - SHIFT = 1 |
194 | - BACKSPACE = 2 |
195 | - SPACE = 3 |
196 | - SYM = 6 |
197 | - RETURN = 7 |
198 | - SWITCH = 11 |
199 | - |
200 | - |
201 | -class UnsupportedKey(RuntimeError): |
202 | +class KeyPadNotLoaded(Exception): |
203 | + pass |
204 | + |
205 | + |
206 | +class QQuickLoader(UbuntuKeyboardEmulatorBase): |
207 | + # This is a work around so that when we select_single a KeyPad we don't get |
208 | + # back a generic version. |
209 | pass |
210 | |
211 | |
212 | class Keyboard(object): |
213 | |
214 | - KeyPos = namedtuple("KeyPos", ['x', 'y', 'h', 'w']) |
215 | - |
216 | - # Note (veebers 19-aug-13): this hardcoded right now, but will be reading |
217 | - # data from the keyboard itself in the very near future. Moved '/' to |
218 | - # primary symbol, default layout can have a .com instead. |
219 | - default_keys = "qwertyuiopasdfghjklzxcvbnm." |
220 | - shifted_keys = "QWERTYUIOPASDFGHJKLZXCVBNM." |
221 | - primary_symbol = "1234567890*#+-=()!?@~/\\';:,." |
222 | - secondary_symbol = u"$%<>[]`^|_{}\"&,.\u20ac\xa3\xa5\u20b9\xa7\xa1\xbf" \ |
223 | - u"\xab\xbb\u201c\u201d\u201e" |
224 | - |
225 | - # The ability to name the non-text keys. |
226 | - _action_id_to_text = { |
227 | - KeyAction.SHIFT: 'SHIFT', |
228 | - KeyAction.BACKSPACE: '\b', |
229 | - KeyAction.SPACE: ' ', |
230 | - KeyAction.RETURN: '\n' |
231 | + _action_to_label = { |
232 | + 'SHIFT': 'shift', |
233 | + '\b': 'backspace', |
234 | + 'ABC': 'symbols', |
235 | + '?123': 'symbols', |
236 | + ' ': 'space', |
237 | + '\n': 'return', |
238 | } |
239 | |
240 | + # mallit is a class attribute because get_proxy_object_for_existing_process |
241 | + # clears backends for proxy objects, this means that with: |
242 | + # kb = Keyboard() |
243 | + # kb2 = Keyboard() |
244 | + # The proxy objects in kb have had their _Backends cleared which means we |
245 | + # can no longer query them. |
246 | + try: |
247 | + maliit = get_proxy_object_for_existing_process( |
248 | + connection_name='org.maliit.server', |
249 | + emulator_base=UbuntuKeyboardEmulatorBase |
250 | + ) |
251 | + except ProcessSearchError as e: |
252 | + e.args += ( |
253 | + "Unable to find maliit-server dbus object. Has it been " |
254 | + "started with introspection enabled?", |
255 | + ) |
256 | + raise |
257 | + |
258 | def __init__(self, pointer=None): |
259 | try: |
260 | - maliit = get_proxy_object_for_existing_process( |
261 | - connection_name='org.maliit.server' |
262 | + self.orientation = Keyboard.maliit.select_single( |
263 | + "OrientationHelper" |
264 | ) |
265 | - except ProcessSearchError as e: |
266 | + if self.orientation is None: |
267 | + raise RuntimeError( |
268 | + "Unable to find the Orientation Helper, aborting." |
269 | + ) |
270 | + except ValueError as e: |
271 | e.args += ( |
272 | - "Unable to find maliit-server dbus object. Has it been " |
273 | - "started with introspection enabled?", |
274 | + "More than one OrientationHelper object was found, aborting." |
275 | ) |
276 | raise |
277 | |
278 | try: |
279 | - self.keyboard = maliit.select_single( |
280 | - "Keyboard", |
281 | + self.keyboard = Keyboard.maliit.select_single( |
282 | + "QQuickItem", |
283 | objectName="ubuntuKeyboard" |
284 | ) |
285 | if self.keyboard is None: |
286 | raise RuntimeError( |
287 | "Unable to find the Ubuntu Keyboard object within the " |
288 | - "maliit server" |
289 | + "maliit server." |
290 | ) |
291 | except ValueError as e: |
292 | e.args += ( |
293 | @@ -102,35 +103,54 @@ |
294 | ) |
295 | raise |
296 | |
297 | - try: |
298 | - self.keypad = self.keyboard.select_single( |
299 | - "QQuickItem", |
300 | - objectName="keyboardKeypad" |
301 | - ) |
302 | - |
303 | - if self.keypad is None: |
304 | - raise RuntimeError( |
305 | - "Unable to find the keypad object within the " |
306 | - "maliit server" |
307 | - ) |
308 | - except ValueError as e: |
309 | - e.args += ( |
310 | - "There was more than one keyboard keypad object found, " |
311 | - "aborting.", |
312 | - ) |
313 | - raise |
314 | - |
315 | - # Contains instructions on how to move the keyboard into a specific |
316 | - # state/layout so that we can successfully press the required key. |
317 | - self._state_lookup_table = self._generate_state_lookup_table() |
318 | - # Cache the position of the keys |
319 | - self._key_pos_table = defaultdict(dict) |
320 | + self._character_keypad = None |
321 | + self._symbol_keypad = None |
322 | + |
323 | + self._store_current_orientation() |
324 | + self._store_current_language_id() |
325 | |
326 | if pointer is None: |
327 | self.pointer = Pointer(Touch.create()) |
328 | else: |
329 | self.pointer = pointer |
330 | |
331 | + def _keyboard_details_changed(self): |
332 | + return self._language_changed() or self._orientation_changed() |
333 | + |
334 | + @property |
335 | + def character_keypad(self): |
336 | + if self._character_keypad is None or self._keyboard_details_changed(): |
337 | + self._character_keypad = self._get_keypad("character") |
338 | + return self._character_keypad |
339 | + |
340 | + @property |
341 | + def symbol_keypad(self): |
342 | + if (self._symbol_keypad is None |
343 | + or (self._language_changed() or self._orientation_changed())): |
344 | + self._symbol_keypad = self._get_keypad("symbol") |
345 | + |
346 | + return self._symbol_keypad |
347 | + |
348 | + def _get_keypad(self, name): |
349 | + """Attempt to retrieve KeyPad object of either 'character' or 'symbol' |
350 | + |
351 | + *name* must be either 'character' or 'symbol'. |
352 | + |
353 | + Raises KeyPadNotLoaded exception if none or more than one keypad is |
354 | + found. |
355 | + |
356 | + """ |
357 | + objectName = "{name}KeyPadLoader".format(name=name) |
358 | + loader = Keyboard.maliit.select_single( |
359 | + QQuickLoader, |
360 | + objectName=objectName |
361 | + ) |
362 | + keypad = loader.select_single(KeyPad) |
363 | + if keypad is None: |
364 | + raise KeyPadNotLoaded("{name} keypad is not currently loaded.") |
365 | + |
366 | + return keypad |
367 | + |
368 | def dismiss(self): |
369 | """Swipe the keyboard down to hide it. |
370 | |
371 | @@ -150,14 +170,20 @@ |
372 | |
373 | def is_available(self): |
374 | """Returns true if the keyboard is shown and ready to use.""" |
375 | - return ( |
376 | - self.keyboard.state == "SHOWN" |
377 | - and not self.keyboard.hideAnimationFinished |
378 | - ) |
379 | + return (self.keyboard.state == "SHOWN") |
380 | |
381 | @property |
382 | def current_state(self): |
383 | - return self.keyboard.layoutState |
384 | + return self.keyboard.state |
385 | + |
386 | + @property |
387 | + def active_keypad(self): |
388 | + if self.character_keypad.enabled: |
389 | + return self.character_keypad |
390 | + elif self.symbol_keypad.enabled: |
391 | + return self.symbol_keypad |
392 | + else: |
393 | + raise RuntimeError("There are no currently active KeyPads.") |
394 | |
395 | # Much like is_available, but attempts to wait for the keyboard to be |
396 | # ready. |
397 | @@ -177,31 +203,6 @@ |
398 | except RuntimeError: |
399 | return False |
400 | |
401 | - def get_key_position(self, key): |
402 | - """Returns the global rect of the given key. |
403 | - |
404 | - It may need to do a lookup to update the table of positions. |
405 | - |
406 | - """ |
407 | - current_state = self.keyboard.layoutState |
408 | - if self._key_pos_table.get(current_state) is None: |
409 | - self._update_pos_table_for_current_state() |
410 | - |
411 | - return self._key_pos_table[current_state][key] |
412 | - |
413 | - def _update_pos_table_for_current_state(self): |
414 | - all_keys = self.keypad.select_many('QQuickText') |
415 | - current_state = self.keyboard.layoutState |
416 | - labeled_keys = (KeyAction.INSERT, KeyAction.SWITCH, KeyAction.SYM) |
417 | - for key in all_keys: |
418 | - with key.no_automatic_refreshing(): |
419 | - key_pos = Keyboard.KeyPos(*key.globalRect) |
420 | - if key.action_type in labeled_keys: |
421 | - self._key_pos_table[current_state][key.text] = key_pos |
422 | - else: |
423 | - key_text = Keyboard._action_id_to_text[key.action_type] |
424 | - self._key_pos_table[current_state][key_text] = key_pos |
425 | - |
426 | def press_key(self, key): |
427 | """Tap on the key with the internal pointer |
428 | |
429 | @@ -209,18 +210,31 @@ |
430 | |
431 | :raises: *RuntimeError* if the keyboard is not available and thus not |
432 | ready to be used. |
433 | - :raises: *UnsupportedKey* if the supplied key cannot be found on any of |
434 | + :raises: *ValueError* if the supplied key cannot be found on any of |
435 | the the current keyboards layouts. |
436 | """ |
437 | if not self.is_available(): |
438 | raise RuntimeError("Keyboard is not on screen") |
439 | |
440 | - if not self._is_special_key(key): |
441 | - required_state_for_key = self._get_keys_required_state(key) |
442 | - self._switch_keyboard_to_state(required_state_for_key) |
443 | - |
444 | - key_rect = self.get_key_position(key) |
445 | - self.pointer.click_object(key_rect) |
446 | + key = self._translate_key(key) |
447 | + active_keypad = None |
448 | + |
449 | + try: |
450 | + if self.character_keypad.contains_key(key): |
451 | + self._show_character_keypad() |
452 | + active_keypad = self.character_keypad |
453 | + elif self.symbol_keypad.contains_key(key): |
454 | + self._show_symbol_keypad() |
455 | + active_keypad = self.symbol_keypad |
456 | + except KeyPadNotLoaded: |
457 | + pass |
458 | + |
459 | + if active_keypad is None: |
460 | + raise ValueError( |
461 | + "Key '%s' was not found on the keyboard." % key |
462 | + ) |
463 | + |
464 | + active_keypad.press_key(key) |
465 | |
466 | def type(self, string, delay=0.1): |
467 | """Type the string *string* with a delay of *delay* between each key |
468 | @@ -231,7 +245,7 @@ |
469 | |
470 | Only 'normal' or single characters can be typed this way. |
471 | |
472 | - :raises: *UnsupportedKey* if one of the the supplied keys cannot be |
473 | + :raises: *ValueError* if one of the the supplied keys cannot be |
474 | found on any of the the current keyboards layouts. |
475 | |
476 | """ |
477 | @@ -239,108 +253,47 @@ |
478 | self.press_key(char) |
479 | sleep(delay) |
480 | |
481 | - def _get_keys_required_state(self, char): |
482 | - """Given a character determine which state the keyboard needs to be in |
483 | - so that it is visible and can be clicked. |
484 | - |
485 | - """ |
486 | - |
487 | - if char in Keyboard.default_keys: |
488 | - return KeyboardState.DEFAULT |
489 | - elif char in Keyboard.shifted_keys: |
490 | - return KeyboardState.SHIFTED |
491 | - elif char in Keyboard.primary_symbol: |
492 | - return KeyboardState.SYMBOL_1 |
493 | - elif char in Keyboard.secondary_symbol: |
494 | - return KeyboardState.SYMBOL_2 |
495 | - else: |
496 | - raise UnsupportedKey( |
497 | - "Don't know which state key '%s' requires" % char |
498 | - ) |
499 | - |
500 | - def _switch_keyboard_to_state(self, target_state): |
501 | - """Given a target_state, presses the required keys to bring the |
502 | - keyboard into the correct state. |
503 | - |
504 | - :raises: *RuntimeError* if unable to change the keyboard into the |
505 | - expected state. |
506 | - |
507 | - """ |
508 | - current_state = self.keyboard.layoutState |
509 | - |
510 | - if target_state == current_state: |
511 | - return |
512 | - |
513 | - instructions = self._state_lookup_table[target_state].get( |
514 | - current_state, |
515 | - None |
516 | - ) |
517 | - if instructions is None: |
518 | - raise RuntimeError( |
519 | - "Don't know how to get to state %d from current state (%d)" |
520 | - % (target_state, current_state) |
521 | - ) |
522 | - |
523 | - for step in instructions: |
524 | - key, expected_state = step |
525 | - self.press_key(key) |
526 | - self.keyboard.layoutState.wait_for(expected_state) |
527 | - |
528 | - def _is_special_key(self, key): |
529 | - return key in ["\n", "\b", " ", "SHIFT", "?123", "ABC", "1/2", "2/2"] |
530 | - |
531 | - # Give the state that you want and the current state, get instructions on |
532 | - # how to move to that state. |
533 | - # lookup_table[REQUESTED_STATE][CURRENT_STATE] -> Instructions(Key to |
534 | - # press, Expected state after key press.) |
535 | - def _generate_state_lookup_table(self): |
536 | - return { |
537 | - KeyboardState.DEFAULT: { |
538 | - KeyboardState.SHIFTED: [ |
539 | - ("SHIFT", KeyboardState.DEFAULT) |
540 | - ], |
541 | - KeyboardState.SYMBOL_1: [ |
542 | - ("ABC", KeyboardState.DEFAULT) |
543 | - ], |
544 | - KeyboardState.SYMBOL_2: [ |
545 | - ("ABC", KeyboardState.DEFAULT) |
546 | - ], |
547 | - }, |
548 | - KeyboardState.SHIFTED: { |
549 | - KeyboardState.DEFAULT: [ |
550 | - ("SHIFT", KeyboardState.SHIFTED) |
551 | - ], |
552 | - KeyboardState.SYMBOL_1: [ |
553 | - ("ABC", KeyboardState.DEFAULT), |
554 | - ("SHIFT", KeyboardState.SHIFTED) |
555 | - ], |
556 | - KeyboardState.SYMBOL_2: [ |
557 | - ("ABC", KeyboardState.DEFAULT), |
558 | - ("SHIFT", KeyboardState.SHIFTED) |
559 | - ], |
560 | - }, |
561 | - KeyboardState.SYMBOL_1: { |
562 | - KeyboardState.DEFAULT: [ |
563 | - ("?123", KeyboardState.SYMBOL_1) |
564 | - ], |
565 | - KeyboardState.SHIFTED: [ |
566 | - ("?123", KeyboardState.SYMBOL_1) |
567 | - ], |
568 | - KeyboardState.SYMBOL_2: [ |
569 | - ("2/2", KeyboardState.SYMBOL_1) |
570 | - ], |
571 | - }, |
572 | - KeyboardState.SYMBOL_2: { |
573 | - KeyboardState.DEFAULT: [ |
574 | - ("?123", KeyboardState.SYMBOL_1), |
575 | - ("1/2", KeyboardState.SYMBOL_2) |
576 | - ], |
577 | - KeyboardState.SHIFTED: [ |
578 | - ("?123", KeyboardState.SYMBOL_1), |
579 | - ("1/2", KeyboardState.SYMBOL_2) |
580 | - ], |
581 | - KeyboardState.SYMBOL_1: [ |
582 | - ("1/2", KeyboardState.SYMBOL_2) |
583 | - ], |
584 | - }, |
585 | - } |
586 | + def _orientation_changed(self): |
587 | + if self._stored_orientation != self.orientation.orientationAngle: |
588 | + self._store_current_orientation() |
589 | + return True |
590 | + else: |
591 | + return False |
592 | + |
593 | + def _language_changed(self): |
594 | + if self._stored_language_id != self.keyboard.layoutId: |
595 | + self._store_current_language_id() |
596 | + return True |
597 | + else: |
598 | + return False |
599 | + |
600 | + def _store_current_orientation(self): |
601 | + self._stored_orientation = self.orientation.orientationAngle |
602 | + |
603 | + def _store_current_language_id(self): |
604 | + self._stored_language_id = self.keyboard.layoutId |
605 | + |
606 | + def _show_character_keypad(self): |
607 | + """Brings the characters KeyPad to the forefront.""" |
608 | + if not self.character_keypad.enabled: |
609 | + # If the character keypad isn't enabled than the symbol keypad must |
610 | + # be active |
611 | + self.symbol_keypad.press_key("symbols") |
612 | + self.character_keypad.enabled.wait_for(True) |
613 | + self.character_keypad.opacity.wait_for(1.0) |
614 | + |
615 | + def _show_symbol_keypad(self): |
616 | + """Brings the symbol KeyPad to the forefront.""" |
617 | + if not self.symbol_keypad.enabled: |
618 | + # If the symbol keypad isn't enabled than the character keypad must |
619 | + # be active |
620 | + self.character_keypad.press_key("symbols") |
621 | + self.symbol_keypad.enabled.wait_for(True) |
622 | + self.symbol_keypad.opacity.wait_for(1.0) |
623 | + |
624 | + def _translate_key(self, label): |
625 | + """Get the label for a 'special key' (i.e. space) so that it can be |
626 | + addressed and clicked. |
627 | + |
628 | + """ |
629 | + return Keyboard._action_to_label.get(label, label) |
630 | |
631 | === added file 'tests/autopilot/ubuntu_keyboard/emulators/keypad.py' |
632 | --- tests/autopilot/ubuntu_keyboard/emulators/keypad.py 1970-01-01 00:00:00 +0000 |
633 | +++ tests/autopilot/ubuntu_keyboard/emulators/keypad.py 2013-09-26 16:09:59 +0000 |
634 | @@ -0,0 +1,174 @@ |
635 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
636 | +# |
637 | +# Ubuntu Keyboard Test Suite |
638 | +# Copyright (C) 2013 Canonical |
639 | +# |
640 | +# This program is free software: you can redistribute it and/or modify |
641 | +# it under the terms of the GNU General Public License as published by |
642 | +# the Free Software Foundation, either version 3 of the License, or |
643 | +# (at your option) any later version. |
644 | +# |
645 | +# This program is distributed in the hope that it will be useful, |
646 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
647 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
648 | +# GNU General Public License for more details. |
649 | +# |
650 | +# You should have received a copy of the GNU General Public License |
651 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
652 | +# |
653 | + |
654 | +from ubuntu_keyboard.emulators import UbuntuKeyboardEmulatorBase |
655 | +from ubuntu_keyboard.emulators.key import Key |
656 | + |
657 | +import logging |
658 | + |
659 | +from autopilot.input import Pointer, Touch |
660 | +from time import sleep |
661 | + |
662 | +logger = logging.getLogger(__name__) |
663 | + |
664 | + |
665 | +class CharKey(UbuntuKeyboardEmulatorBase): |
666 | + pass |
667 | + |
668 | + |
669 | +class ActionKey(UbuntuKeyboardEmulatorBase): |
670 | + pass |
671 | + |
672 | + |
673 | +class KeyPad(UbuntuKeyboardEmulatorBase): |
674 | + """An emulator that understands the KeyPad and its internals and how to |
675 | + interact with it. |
676 | + |
677 | + - Which keys are displayed within it |
678 | + - The positions of these keys |
679 | + - The state (NORMAL/SHIFTED) the KeyPad is in |
680 | + |
681 | + """ |
682 | + |
683 | + class State: |
684 | + NORMAL = "NORMAL" |
685 | + SHIFTED = "SHIFTED" |
686 | + CAPSLOCK = "CAPSLOCK" |
687 | + |
688 | + def __init__(self, *args): |
689 | + super(KeyPad, self).__init__(*args) |
690 | + self._key_pos = dict() |
691 | + self._contained_keys = [] |
692 | + self._contained_shifted_keys = [] |
693 | + |
694 | + self.update_key_details() |
695 | + |
696 | + def contains_key(self, label): |
697 | + """Returns true if a key with the label *label* is contained within |
698 | + this KeyPad. |
699 | + |
700 | + """ |
701 | + return (label in self._contained_keys |
702 | + or label in self._contained_shifted_keys) |
703 | + |
704 | + def update_key_details(self): |
705 | + def _iter_keys(key_type, label_fn): |
706 | + for key in self.select_many(key_type): |
707 | + with key.no_automatic_refreshing(): |
708 | + key_pos = Key.Pos(*key.globalRect) |
709 | + label = label_fn(key) |
710 | + if label != '': |
711 | + self._contained_keys.append(label) |
712 | + self._key_pos[label] = key_pos |
713 | + if key.shifted != '': |
714 | + self._contained_shifted_keys.append(key.shifted) |
715 | + self._key_pos[key.shifted] = key_pos |
716 | + |
717 | + _iter_keys(CharKey, lambda x: x.label) |
718 | + _iter_keys(ActionKey, lambda x: x.action) |
719 | + |
720 | + def press_key(self, key, pointer=None): |
721 | + """Taps key *key* with *pointer* |
722 | + |
723 | + If no pointer is passed in one is created for this purpose |
724 | + |
725 | + raises *ValueError* if *key* is not contained within this KeyPad. |
726 | + raises *RuntimeError* if this KeyPad is not visible. |
727 | + |
728 | + """ |
729 | + if not self.contains_key(key): |
730 | + raise ValueError( |
731 | + "Key '%s' is not contained by this KeyPad (%s)," |
732 | + " cannot press it." % (key, self.objectName) |
733 | + ) |
734 | + |
735 | + if not self.visible: |
736 | + raise RuntimeError( |
737 | + "This Keypad (%s) is not visible" % self.objectName |
738 | + ) |
739 | + |
740 | + required_state = self._get_keys_required_keypad_state(key) |
741 | + |
742 | + self._switch_to_state(required_state, pointer) |
743 | + |
744 | + key_rect = self.get_key_position(key) |
745 | + self._tap_key(key_rect, pointer) |
746 | + |
747 | + def get_key_position(self, key): |
748 | + """Returns Key.Pos for the key with *key*. |
749 | + |
750 | + raises *ValueError* if unable to find the stored position for *key* |
751 | + (i.e. not contained within this KeyPad.) |
752 | + |
753 | + """ |
754 | + key_rect = self._key_pos.get(key) |
755 | + |
756 | + if key_rect is None: |
757 | + raise ValueError("Unknown position for key '%s'" % key) |
758 | + |
759 | + return key_rect |
760 | + |
761 | + def _get_keys_required_keypad_state(self, key): |
762 | + if key in ('shift', 'backspace', 'symbols', 'space', 'return'): |
763 | + return self.state |
764 | + elif key in self._contained_keys: |
765 | + return KeyPad.State.NORMAL |
766 | + elif key in self._contained_shifted_keys: |
767 | + return KeyPad.State.SHIFTED |
768 | + else: |
769 | + raise ValueError( |
770 | + "Don't know which state key '%s' requires." % key |
771 | + ) |
772 | + |
773 | + def _switch_to_state(self, state, pointer): |
774 | + """Move from one state to the other. |
775 | + |
776 | + i.e. move from NORMAL to SHIFTED |
777 | + |
778 | + """ |
779 | + if state == self.state: |
780 | + return |
781 | + |
782 | + # If shifted is needed and we're in CAPSLOCK that's fine. |
783 | + if (state == KeyPad.State.SHIFTED |
784 | + and self.state == KeyPad.State.CAPSLOCK): |
785 | + logger.debug( |
786 | + "Ignoring request to switch to SHIFTED, already in CAPSLOCK." |
787 | + ) |
788 | + return |
789 | + |
790 | + logger.debug("Switching from %s to %s" % (self.state, state)) |
791 | + |
792 | + if self.state == KeyPad.State.NORMAL: |
793 | + expected_state = KeyPad.State.SHIFTED |
794 | + else: |
795 | + expected_state = KeyPad.State.NORMAL |
796 | + |
797 | + key_rect = self.get_key_position("shift") |
798 | + |
799 | + # Hack as we cannot tell when the shift key is ready to be pressed |
800 | + # bug lp:1229003 and lp:1229001 |
801 | + sleep(.2) |
802 | + self._tap_key(key_rect, pointer) |
803 | + self.state.wait_for(expected_state) |
804 | + |
805 | + def _tap_key(self, key_rect, pointer): |
806 | + if pointer is None: |
807 | + pointer = Pointer(Touch.create()) |
808 | + pointer.click_object(key_rect) |
809 | |
810 | === modified file 'tests/autopilot/ubuntu_keyboard/tests/test_keyboard.py' |
811 | --- tests/autopilot/ubuntu_keyboard/tests/test_keyboard.py 2013-09-26 10:08:30 +0000 |
812 | +++ tests/autopilot/ubuntu_keyboard/tests/test_keyboard.py 2013-09-26 16:09:59 +0000 |
813 | @@ -22,12 +22,14 @@ |
814 | from testtools.matchers import Equals |
815 | from tempfile import mktemp |
816 | from textwrap import dedent |
817 | +from time import sleep |
818 | |
819 | from autopilot.testcase import AutopilotTestCase |
820 | from autopilot.input import Pointer, Touch |
821 | from autopilot.matchers import Eventually |
822 | |
823 | -from ubuntu_keyboard.emulators.keyboard import Keyboard, KeyboardState |
824 | +from ubuntu_keyboard.emulators.keyboard import Keyboard |
825 | +from ubuntu_keyboard.emulators.keypad import KeyPad |
826 | |
827 | |
828 | class UbuntuKeyboardTests(AutopilotTestCase): |
829 | @@ -141,10 +143,12 @@ |
830 | ) |
831 | ), |
832 | ( |
833 | + # Currently the en_us layout doesn't have ", but has \u201c and |
834 | + # \u201d |
835 | 'punctuation', |
836 | dict( |
837 | label="Puncuation", |
838 | - input='`~!@#$%^&*()_-+={}[]|\\:;"\'<>,.?/' |
839 | + input=u'`~!@#$%^&*()_-+={}[]|\\:;\'<>,.?/\u201c' |
840 | ) |
841 | ) |
842 | ] |
843 | @@ -169,14 +173,18 @@ |
844 | shifted/capitalised. |
845 | |
846 | """ |
847 | + self.skip( |
848 | + "Skipping as feature hasn't landed yet, refer to bug lp:1214695" |
849 | + ) |
850 | + |
851 | text_area = self.launch_test_input_area() |
852 | self.ensure_focus_on_input(text_area) |
853 | keyboard = Keyboard() |
854 | self.addCleanup(keyboard.dismiss) |
855 | |
856 | self.assertThat( |
857 | - keyboard.keyboard.layoutState, |
858 | - Eventually(Equals(KeyboardState.SHIFTED)) |
859 | + keyboard.active_keypad.state, |
860 | + Eventually(Equals(KeyPad.State.SHIFTED)) |
861 | ) |
862 | |
863 | def test_shift_latch(self): |
864 | @@ -194,13 +202,15 @@ |
865 | self.addCleanup(keyboard.dismiss) |
866 | |
867 | keyboard.type('abc') |
868 | - keyboard.press_key('SHIFT') |
869 | - keyboard.press_key('SHIFT') |
870 | + # Bug lp:1229003 and lp:1229001 |
871 | + sleep(.2) |
872 | + keyboard.press_key('shift') |
873 | + keyboard.press_key('shift') |
874 | keyboard.type('S') |
875 | |
876 | self.assertThat( |
877 | - keyboard.keyboard.layoutState, |
878 | - Eventually(Equals(KeyboardState.SHIFTED)) |
879 | + keyboard.active_keypad.state, |
880 | + Eventually(Equals(KeyPad.State.CAPSLOCK)) |
881 | ) |
882 | self.assertThat(text_area.text, Eventually(Equals('abcS'))) |
883 | |
884 | @@ -224,8 +234,8 @@ |
885 | # Once the capital letter has been typed, we must be able to access the |
886 | # lowercase letters, otherwise it's not in the correct state. |
887 | self.assertThat( |
888 | - keyboard.keyboard.layoutState, |
889 | - Eventually(Equals(KeyboardState.DEFAULT)) |
890 | + keyboard.active_keypad.state, |
891 | + Eventually(Equals(KeyPad.State.NORMAL)) |
892 | ) |
893 | |
894 | self.assertThat(text_area.text, Eventually(Equals('abcA'))) |
895 | @@ -237,6 +247,9 @@ |
896 | enter the shifted state. |
897 | |
898 | """ |
899 | + self.skip( |
900 | + "Skipping as feature hasn't landed yet, refer to bug lp:1214695" |
901 | + ) |
902 | text_area = self.launch_test_input_area() |
903 | self.ensure_focus_on_input(text_area) |
904 | keyboard = Keyboard() |
905 | @@ -250,8 +263,8 @@ |
906 | ) |
907 | |
908 | self.assertThat( |
909 | - keyboard.keyboard.layoutState, |
910 | - Eventually(Equals(KeyboardState.SHIFTED)) |
911 | + keyboard.active_keypad.state, |
912 | + Eventually(Equals(KeyPad.State.SHIFTED)) |
913 | ) |
914 | |
915 | def test_switching_between_states(self): |
916 | @@ -292,14 +305,6 @@ |
917 | ) |
918 | ), |
919 | ( |
920 | - "Password", |
921 | - dict( |
922 | - label="Password", |
923 | - hints=['Qt.ImhHiddenText', 'Qt.ImhSensitiveData'], |
924 | - expected_activeview="password" |
925 | - ) |
926 | - ), |
927 | - ( |
928 | "Email", |
929 | dict( |
930 | label="Email", |
931 | @@ -320,7 +325,7 @@ |
932 | dict( |
933 | label="Telephone", |
934 | hints=['Qt.ImhDigitsOnly'], |
935 | - expected_activeview="phonenumber" |
936 | + expected_activeview="number" |
937 | ) |
938 | ), |
939 | ] |
940 | @@ -337,6 +342,6 @@ |
941 | self.addCleanup(keyboard.dismiss) |
942 | |
943 | self.assertThat( |
944 | - keyboard.keyboard.activeView, |
945 | + keyboard.keyboard.layoutId, |
946 | Eventually(Equals(self.expected_activeview)) |
947 | ) |
PASSED: Continuous integration, rev:49 jenkins. qa.ubuntu. com/job/ ubuntu- keyboard- ci/102/ jenkins. qa.ubuntu. com/job/ ubuntu- keyboard- saucy-amd64- ci/103 jenkins. qa.ubuntu. com/job/ ubuntu- keyboard- saucy-armhf- ci/102 jenkins. qa.ubuntu. com/job/ ubuntu- keyboard- saucy-armhf- ci/102/ artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ ubuntu- keyboard- saucy-i386- ci/102
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild: s-jenkins: 8080/job/ ubuntu- keyboard- ci/102/ rebuild
http://