Merge lp:~bfiller/ubuntu-keyboard/back-to-rev-44 into lp:ubuntu-keyboard

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
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)
Revision history for this message
Bill Filler (bfiller) wrote :

approve

review: Approve

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 )

Subscribers

People subscribed via source and target branches