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
=== modified file 'data/data.pro'
--- data/data.pro 2013-09-26 10:08:30 +0000
+++ data/data.pro 2013-09-26 16:09:59 +0000
@@ -8,10 +8,13 @@
8# make it available for testing, not intended for proper release though:8# make it available for testing, not intended for proper release though:
9languages.files += languages/debug/showcase.xml9languages.files += languages/debug/showcase.xml
1010
11schemas.path = $${PREFIX}/share/glib-2.0/schemas
12schemas.files = schemas/*.gschema.xml
13
11styles.path = $${UBUNTU_KEYBOARD_DATA_DIR}14styles.path = $${UBUNTU_KEYBOARD_DATA_DIR}
12styles.files = styles15styles.files = styles
1316
14INSTALLS += languages styles17INSTALLS += languages schemas styles
1518
16QMAKE_EXTRA_TARGETS += check19QMAKE_EXTRA_TARGETS += check
17check.target = check20check.target = check
1821
=== added directory 'data/schemas'
=== added file 'data/schemas/com.canonical.keyboard.maliit.gschema.xml'
--- data/schemas/com.canonical.keyboard.maliit.gschema.xml 1970-01-01 00:00:00 +0000
+++ data/schemas/com.canonical.keyboard.maliit.gschema.xml 2013-09-26 16:09:59 +0000
@@ -0,0 +1,30 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<schemalist>
3 <schema id="com.canonical.keyboard.maliit" path="/com/canonical/keyboard/maliit/">
4 <key name="enabled-languages" type="as">
5 <summary>Enabled languages</summary>
6 <description>User-defined list of activatable languages.</description>
7 <default>[]</default>
8 </key>
9 <key name="auto-capitalization" type="b">
10 <summary>Auto-capitalization</summary>
11 <description>Capitalize the first letter of each sentence.</description>
12 <default>true</default>
13 </key>
14 <key name="auto-completion" type="b">
15 <summary>Auto-completion</summary>
16 <description>Complete current word with first suggestion when hitting space.</description>
17 <default>true</default>
18 </key>
19 <key name="predictive-text" type="b">
20 <summary>Predictive text</summary>
21 <description>Suggest potential words in word ribbon.</description>
22 <default>true</default>
23 </key>
24 <key name="key-press-feedback" type="b">
25 <summary>Key press feedback</summary>
26 <description>Vibrate or emit sound on key press.</description>
27 <default>true</default>
28 </key>
29 </schema>
30</schemalist>
031
=== modified file 'debian/changelog'
--- debian/changelog 2013-09-26 10:37:17 +0000
+++ debian/changelog 2013-09-26 16:09:59 +0000
@@ -1,7 +1,11 @@
1ubuntu-keyboard (0.99.trunk.phablet2+13.10.20130926-0ubuntu1) saucy; urgency=low1ubuntu-keyboard (0.99.trunk.phablet2+13.10.20130926-0ubuntu1) saucy; urgency=low
22
3 [ Ubuntu daily release ]3 [ Ubuntu daily release ]
4 * New rebuild forced4 * New rebuild forced from rev 45
5
6 -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Thu, 26 Sep 2013 10:37:17 +0000
7
8ubuntu-keyboard (0.99.trunk.phablet2+13.10.20130925-0ubuntu1) saucy; urgency=low
59
6 [ William Hua ]10 [ William Hua ]
7 * Add GSettings schema file.11 * Add GSettings schema file.
@@ -11,9 +15,9 @@
11 (moving more UI code to QML).15 (moving more UI code to QML).
1216
13 [ Ubuntu daily release ]17 [ Ubuntu daily release ]
14 * Automatic snapshot from revision 4518 * Automatic snapshot from revision 43
1519
16 -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Thu, 26 Sep 2013 10:37:17 +000020 -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Wed, 25 Sep 2013 11:05:45 +0000
1721
18ubuntu-keyboard (0.99.trunk.phablet2+13.10.20130920-0ubuntu1) saucy; urgency=low22ubuntu-keyboard (0.99.trunk.phablet2+13.10.20130920-0ubuntu1) saucy; urgency=low
1923
2024
=== modified file 'debian/ubuntu-keyboard-data.install'
--- debian/ubuntu-keyboard-data.install 2013-09-26 10:08:30 +0000
+++ debian/ubuntu-keyboard-data.install 2013-09-26 16:09:59 +0000
@@ -1,1 +1,2 @@
1usr/share/glib-2.0/schemas/
1usr/share/maliit/plugins/com/ubuntu/2usr/share/maliit/plugins/com/ubuntu/
23
=== modified file 'qml/KeyboardContainer.qml'
--- qml/KeyboardContainer.qml 2013-09-26 10:08:30 +0000
+++ qml/KeyboardContainer.qml 2013-09-26 16:09:59 +0000
@@ -47,6 +47,7 @@
4747
48 Loader {48 Loader {
49 id: characterKeypadLoader49 id: characterKeypadLoader
50 objectName: "characterKeyPadLoader"
50 anchors.fill: parent51 anchors.fill: parent
51 asynchronous: true52 asynchronous: true
52 source: "languages/Keyboard_en_us.qml"53 source: "languages/Keyboard_en_us.qml"
@@ -59,6 +60,7 @@
5960
60 Loader {61 Loader {
61 id: symbolKeypadLoader62 id: symbolKeypadLoader
63 objectName: "symbolKeyPadLoader"
62 anchors.fill: parent64 anchors.fill: parent
63 asynchronous: true65 asynchronous: true
64 }66 }
6567
=== modified file 'tests/autopilot/ubuntu_keyboard/emulators/__init__.py'
--- tests/autopilot/ubuntu_keyboard/emulators/__init__.py 2013-09-26 10:08:30 +0000
+++ tests/autopilot/ubuntu_keyboard/emulators/__init__.py 2013-09-26 16:09:59 +0000
@@ -16,3 +16,9 @@
16# You should have received a copy of the GNU General Public License16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#18#
19
20from autopilot.introspection import CustomEmulatorBase
21
22
23class UbuntuKeyboardEmulatorBase(CustomEmulatorBase):
24 """A base class for all Ubuntu Keyboard emulators."""
1925
=== added file 'tests/autopilot/ubuntu_keyboard/emulators/key.py'
--- tests/autopilot/ubuntu_keyboard/emulators/key.py 1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntu_keyboard/emulators/key.py 2013-09-26 16:09:59 +0000
@@ -0,0 +1,33 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Ubuntu Keyboard Test Suite
4# Copyright (C) 2013 Canonical
5#
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#
19
20from ubuntu_keyboard.emulators import UbuntuKeyboardEmulatorBase
21
22from collections import namedtuple
23import logging
24
25logger = logging.getLogger(__name__)
26
27
28class Key(UbuntuKeyboardEmulatorBase):
29 """An emulator that encapsulates details of a keyboard key (i.e. extended
30 characters).
31
32 """
33 Pos = namedtuple("KeyPosition", ['x', 'y', 'w', 'h'])
034
=== modified file 'tests/autopilot/ubuntu_keyboard/emulators/keyboard.py'
--- tests/autopilot/ubuntu_keyboard/emulators/keyboard.py 2013-09-26 10:08:30 +0000
+++ tests/autopilot/ubuntu_keyboard/emulators/keyboard.py 2013-09-26 16:09:59 +0000
@@ -17,7 +17,9 @@
17# along with this program. If not, see <http://www.gnu.org/licenses/>.17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#18#
1919
20from collections import defaultdict, namedtuple20from ubuntu_keyboard.emulators import UbuntuKeyboardEmulatorBase
21from ubuntu_keyboard.emulators.keypad import KeyPad
22
21from time import sleep23from time import sleep
22import logging24import logging
2325
@@ -31,70 +33,69 @@
31logger = logging.getLogger(__name__)33logger = logging.getLogger(__name__)
3234
3335
34# Definitions of enums used within the cpp source code.36class KeyPadNotLoaded(Exception):
35class KeyboardState:37 pass
36 DEFAULT = 038
37 SHIFTED = 139
38 SYMBOL_1 = 240class QQuickLoader(UbuntuKeyboardEmulatorBase):
39 SYMBOL_2 = 341 # This is a work around so that when we select_single a KeyPad we don't get
4042 # back a generic version.
41
42class KeyAction:
43 INSERT = 0
44 SHIFT = 1
45 BACKSPACE = 2
46 SPACE = 3
47 SYM = 6
48 RETURN = 7
49 SWITCH = 11
50
51
52class UnsupportedKey(RuntimeError):
53 pass43 pass
5444
5545
56class Keyboard(object):46class Keyboard(object):
5747
58 KeyPos = namedtuple("KeyPos", ['x', 'y', 'h', 'w'])48 _action_to_label = {
5949 'SHIFT': 'shift',
60 # Note (veebers 19-aug-13): this hardcoded right now, but will be reading50 '\b': 'backspace',
61 # data from the keyboard itself in the very near future. Moved '/' to51 'ABC': 'symbols',
62 # primary symbol, default layout can have a .com instead.52 '?123': 'symbols',
63 default_keys = "qwertyuiopasdfghjklzxcvbnm."53 ' ': 'space',
64 shifted_keys = "QWERTYUIOPASDFGHJKLZXCVBNM."54 '\n': 'return',
65 primary_symbol = "1234567890*#+-=()!?@~/\\';:,."
66 secondary_symbol = u"$%<>[]`^|_{}\"&,.\u20ac\xa3\xa5\u20b9\xa7\xa1\xbf" \
67 u"\xab\xbb\u201c\u201d\u201e"
68
69 # The ability to name the non-text keys.
70 _action_id_to_text = {
71 KeyAction.SHIFT: 'SHIFT',
72 KeyAction.BACKSPACE: '\b',
73 KeyAction.SPACE: ' ',
74 KeyAction.RETURN: '\n'
75 }55 }
7656
57 # mallit is a class attribute because get_proxy_object_for_existing_process
58 # clears backends for proxy objects, this means that with:
59 # kb = Keyboard()
60 # kb2 = Keyboard()
61 # The proxy objects in kb have had their _Backends cleared which means we
62 # can no longer query them.
63 try:
64 maliit = get_proxy_object_for_existing_process(
65 connection_name='org.maliit.server',
66 emulator_base=UbuntuKeyboardEmulatorBase
67 )
68 except ProcessSearchError as e:
69 e.args += (
70 "Unable to find maliit-server dbus object. Has it been "
71 "started with introspection enabled?",
72 )
73 raise
74
77 def __init__(self, pointer=None):75 def __init__(self, pointer=None):
78 try:76 try:
79 maliit = get_proxy_object_for_existing_process(77 self.orientation = Keyboard.maliit.select_single(
80 connection_name='org.maliit.server'78 "OrientationHelper"
81 )79 )
82 except ProcessSearchError as e:80 if self.orientation is None:
81 raise RuntimeError(
82 "Unable to find the Orientation Helper, aborting."
83 )
84 except ValueError as e:
83 e.args += (85 e.args += (
84 "Unable to find maliit-server dbus object. Has it been "86 "More than one OrientationHelper object was found, aborting."
85 "started with introspection enabled?",
86 )87 )
87 raise88 raise
8889
89 try:90 try:
90 self.keyboard = maliit.select_single(91 self.keyboard = Keyboard.maliit.select_single(
91 "Keyboard",92 "QQuickItem",
92 objectName="ubuntuKeyboard"93 objectName="ubuntuKeyboard"
93 )94 )
94 if self.keyboard is None:95 if self.keyboard is None:
95 raise RuntimeError(96 raise RuntimeError(
96 "Unable to find the Ubuntu Keyboard object within the "97 "Unable to find the Ubuntu Keyboard object within the "
97 "maliit server"98 "maliit server."
98 )99 )
99 except ValueError as e:100 except ValueError as e:
100 e.args += (101 e.args += (
@@ -102,35 +103,54 @@
102 )103 )
103 raise104 raise
104105
105 try:106 self._character_keypad = None
106 self.keypad = self.keyboard.select_single(107 self._symbol_keypad = None
107 "QQuickItem",108
108 objectName="keyboardKeypad"109 self._store_current_orientation()
109 )110 self._store_current_language_id()
110
111 if self.keypad is None:
112 raise RuntimeError(
113 "Unable to find the keypad object within the "
114 "maliit server"
115 )
116 except ValueError as e:
117 e.args += (
118 "There was more than one keyboard keypad object found, "
119 "aborting.",
120 )
121 raise
122
123 # Contains instructions on how to move the keyboard into a specific
124 # state/layout so that we can successfully press the required key.
125 self._state_lookup_table = self._generate_state_lookup_table()
126 # Cache the position of the keys
127 self._key_pos_table = defaultdict(dict)
128111
129 if pointer is None:112 if pointer is None:
130 self.pointer = Pointer(Touch.create())113 self.pointer = Pointer(Touch.create())
131 else:114 else:
132 self.pointer = pointer115 self.pointer = pointer
133116
117 def _keyboard_details_changed(self):
118 return self._language_changed() or self._orientation_changed()
119
120 @property
121 def character_keypad(self):
122 if self._character_keypad is None or self._keyboard_details_changed():
123 self._character_keypad = self._get_keypad("character")
124 return self._character_keypad
125
126 @property
127 def symbol_keypad(self):
128 if (self._symbol_keypad is None
129 or (self._language_changed() or self._orientation_changed())):
130 self._symbol_keypad = self._get_keypad("symbol")
131
132 return self._symbol_keypad
133
134 def _get_keypad(self, name):
135 """Attempt to retrieve KeyPad object of either 'character' or 'symbol'
136
137 *name* must be either 'character' or 'symbol'.
138
139 Raises KeyPadNotLoaded exception if none or more than one keypad is
140 found.
141
142 """
143 objectName = "{name}KeyPadLoader".format(name=name)
144 loader = Keyboard.maliit.select_single(
145 QQuickLoader,
146 objectName=objectName
147 )
148 keypad = loader.select_single(KeyPad)
149 if keypad is None:
150 raise KeyPadNotLoaded("{name} keypad is not currently loaded.")
151
152 return keypad
153
134 def dismiss(self):154 def dismiss(self):
135 """Swipe the keyboard down to hide it.155 """Swipe the keyboard down to hide it.
136156
@@ -150,14 +170,20 @@
150170
151 def is_available(self):171 def is_available(self):
152 """Returns true if the keyboard is shown and ready to use."""172 """Returns true if the keyboard is shown and ready to use."""
153 return (173 return (self.keyboard.state == "SHOWN")
154 self.keyboard.state == "SHOWN"
155 and not self.keyboard.hideAnimationFinished
156 )
157174
158 @property175 @property
159 def current_state(self):176 def current_state(self):
160 return self.keyboard.layoutState177 return self.keyboard.state
178
179 @property
180 def active_keypad(self):
181 if self.character_keypad.enabled:
182 return self.character_keypad
183 elif self.symbol_keypad.enabled:
184 return self.symbol_keypad
185 else:
186 raise RuntimeError("There are no currently active KeyPads.")
161187
162 # Much like is_available, but attempts to wait for the keyboard to be188 # Much like is_available, but attempts to wait for the keyboard to be
163 # ready.189 # ready.
@@ -177,31 +203,6 @@
177 except RuntimeError:203 except RuntimeError:
178 return False204 return False
179205
180 def get_key_position(self, key):
181 """Returns the global rect of the given key.
182
183 It may need to do a lookup to update the table of positions.
184
185 """
186 current_state = self.keyboard.layoutState
187 if self._key_pos_table.get(current_state) is None:
188 self._update_pos_table_for_current_state()
189
190 return self._key_pos_table[current_state][key]
191
192 def _update_pos_table_for_current_state(self):
193 all_keys = self.keypad.select_many('QQuickText')
194 current_state = self.keyboard.layoutState
195 labeled_keys = (KeyAction.INSERT, KeyAction.SWITCH, KeyAction.SYM)
196 for key in all_keys:
197 with key.no_automatic_refreshing():
198 key_pos = Keyboard.KeyPos(*key.globalRect)
199 if key.action_type in labeled_keys:
200 self._key_pos_table[current_state][key.text] = key_pos
201 else:
202 key_text = Keyboard._action_id_to_text[key.action_type]
203 self._key_pos_table[current_state][key_text] = key_pos
204
205 def press_key(self, key):206 def press_key(self, key):
206 """Tap on the key with the internal pointer207 """Tap on the key with the internal pointer
207208
@@ -209,18 +210,31 @@
209210
210 :raises: *RuntimeError* if the keyboard is not available and thus not211 :raises: *RuntimeError* if the keyboard is not available and thus not
211 ready to be used.212 ready to be used.
212 :raises: *UnsupportedKey* if the supplied key cannot be found on any of213 :raises: *ValueError* if the supplied key cannot be found on any of
213 the the current keyboards layouts.214 the the current keyboards layouts.
214 """215 """
215 if not self.is_available():216 if not self.is_available():
216 raise RuntimeError("Keyboard is not on screen")217 raise RuntimeError("Keyboard is not on screen")
217218
218 if not self._is_special_key(key):219 key = self._translate_key(key)
219 required_state_for_key = self._get_keys_required_state(key)220 active_keypad = None
220 self._switch_keyboard_to_state(required_state_for_key)221
221222 try:
222 key_rect = self.get_key_position(key)223 if self.character_keypad.contains_key(key):
223 self.pointer.click_object(key_rect)224 self._show_character_keypad()
225 active_keypad = self.character_keypad
226 elif self.symbol_keypad.contains_key(key):
227 self._show_symbol_keypad()
228 active_keypad = self.symbol_keypad
229 except KeyPadNotLoaded:
230 pass
231
232 if active_keypad is None:
233 raise ValueError(
234 "Key '%s' was not found on the keyboard." % key
235 )
236
237 active_keypad.press_key(key)
224238
225 def type(self, string, delay=0.1):239 def type(self, string, delay=0.1):
226 """Type the string *string* with a delay of *delay* between each key240 """Type the string *string* with a delay of *delay* between each key
@@ -231,7 +245,7 @@
231245
232 Only 'normal' or single characters can be typed this way.246 Only 'normal' or single characters can be typed this way.
233247
234 :raises: *UnsupportedKey* if one of the the supplied keys cannot be248 :raises: *ValueError* if one of the the supplied keys cannot be
235 found on any of the the current keyboards layouts.249 found on any of the the current keyboards layouts.
236250
237 """251 """
@@ -239,108 +253,47 @@
239 self.press_key(char)253 self.press_key(char)
240 sleep(delay)254 sleep(delay)
241255
242 def _get_keys_required_state(self, char):256 def _orientation_changed(self):
243 """Given a character determine which state the keyboard needs to be in257 if self._stored_orientation != self.orientation.orientationAngle:
244 so that it is visible and can be clicked.258 self._store_current_orientation()
245259 return True
246 """260 else:
247261 return False
248 if char in Keyboard.default_keys:262
249 return KeyboardState.DEFAULT263 def _language_changed(self):
250 elif char in Keyboard.shifted_keys:264 if self._stored_language_id != self.keyboard.layoutId:
251 return KeyboardState.SHIFTED265 self._store_current_language_id()
252 elif char in Keyboard.primary_symbol:266 return True
253 return KeyboardState.SYMBOL_1267 else:
254 elif char in Keyboard.secondary_symbol:268 return False
255 return KeyboardState.SYMBOL_2269
256 else:270 def _store_current_orientation(self):
257 raise UnsupportedKey(271 self._stored_orientation = self.orientation.orientationAngle
258 "Don't know which state key '%s' requires" % char272
259 )273 def _store_current_language_id(self):
260274 self._stored_language_id = self.keyboard.layoutId
261 def _switch_keyboard_to_state(self, target_state):275
262 """Given a target_state, presses the required keys to bring the276 def _show_character_keypad(self):
263 keyboard into the correct state.277 """Brings the characters KeyPad to the forefront."""
264278 if not self.character_keypad.enabled:
265 :raises: *RuntimeError* if unable to change the keyboard into the279 # If the character keypad isn't enabled than the symbol keypad must
266 expected state.280 # be active
267281 self.symbol_keypad.press_key("symbols")
268 """282 self.character_keypad.enabled.wait_for(True)
269 current_state = self.keyboard.layoutState283 self.character_keypad.opacity.wait_for(1.0)
270284
271 if target_state == current_state:285 def _show_symbol_keypad(self):
272 return286 """Brings the symbol KeyPad to the forefront."""
273287 if not self.symbol_keypad.enabled:
274 instructions = self._state_lookup_table[target_state].get(288 # If the symbol keypad isn't enabled than the character keypad must
275 current_state,289 # be active
276 None290 self.character_keypad.press_key("symbols")
277 )291 self.symbol_keypad.enabled.wait_for(True)
278 if instructions is None:292 self.symbol_keypad.opacity.wait_for(1.0)
279 raise RuntimeError(293
280 "Don't know how to get to state %d from current state (%d)"294 def _translate_key(self, label):
281 % (target_state, current_state)295 """Get the label for a 'special key' (i.e. space) so that it can be
282 )296 addressed and clicked.
283297
284 for step in instructions:298 """
285 key, expected_state = step299 return Keyboard._action_to_label.get(label, label)
286 self.press_key(key)
287 self.keyboard.layoutState.wait_for(expected_state)
288
289 def _is_special_key(self, key):
290 return key in ["\n", "\b", " ", "SHIFT", "?123", "ABC", "1/2", "2/2"]
291
292 # Give the state that you want and the current state, get instructions on
293 # how to move to that state.
294 # lookup_table[REQUESTED_STATE][CURRENT_STATE] -> Instructions(Key to
295 # press, Expected state after key press.)
296 def _generate_state_lookup_table(self):
297 return {
298 KeyboardState.DEFAULT: {
299 KeyboardState.SHIFTED: [
300 ("SHIFT", KeyboardState.DEFAULT)
301 ],
302 KeyboardState.SYMBOL_1: [
303 ("ABC", KeyboardState.DEFAULT)
304 ],
305 KeyboardState.SYMBOL_2: [
306 ("ABC", KeyboardState.DEFAULT)
307 ],
308 },
309 KeyboardState.SHIFTED: {
310 KeyboardState.DEFAULT: [
311 ("SHIFT", KeyboardState.SHIFTED)
312 ],
313 KeyboardState.SYMBOL_1: [
314 ("ABC", KeyboardState.DEFAULT),
315 ("SHIFT", KeyboardState.SHIFTED)
316 ],
317 KeyboardState.SYMBOL_2: [
318 ("ABC", KeyboardState.DEFAULT),
319 ("SHIFT", KeyboardState.SHIFTED)
320 ],
321 },
322 KeyboardState.SYMBOL_1: {
323 KeyboardState.DEFAULT: [
324 ("?123", KeyboardState.SYMBOL_1)
325 ],
326 KeyboardState.SHIFTED: [
327 ("?123", KeyboardState.SYMBOL_1)
328 ],
329 KeyboardState.SYMBOL_2: [
330 ("2/2", KeyboardState.SYMBOL_1)
331 ],
332 },
333 KeyboardState.SYMBOL_2: {
334 KeyboardState.DEFAULT: [
335 ("?123", KeyboardState.SYMBOL_1),
336 ("1/2", KeyboardState.SYMBOL_2)
337 ],
338 KeyboardState.SHIFTED: [
339 ("?123", KeyboardState.SYMBOL_1),
340 ("1/2", KeyboardState.SYMBOL_2)
341 ],
342 KeyboardState.SYMBOL_1: [
343 ("1/2", KeyboardState.SYMBOL_2)
344 ],
345 },
346 }
347300
=== added file 'tests/autopilot/ubuntu_keyboard/emulators/keypad.py'
--- tests/autopilot/ubuntu_keyboard/emulators/keypad.py 1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntu_keyboard/emulators/keypad.py 2013-09-26 16:09:59 +0000
@@ -0,0 +1,174 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Ubuntu Keyboard Test Suite
4# Copyright (C) 2013 Canonical
5#
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#
19
20from ubuntu_keyboard.emulators import UbuntuKeyboardEmulatorBase
21from ubuntu_keyboard.emulators.key import Key
22
23import logging
24
25from autopilot.input import Pointer, Touch
26from time import sleep
27
28logger = logging.getLogger(__name__)
29
30
31class CharKey(UbuntuKeyboardEmulatorBase):
32 pass
33
34
35class ActionKey(UbuntuKeyboardEmulatorBase):
36 pass
37
38
39class KeyPad(UbuntuKeyboardEmulatorBase):
40 """An emulator that understands the KeyPad and its internals and how to
41 interact with it.
42
43 - Which keys are displayed within it
44 - The positions of these keys
45 - The state (NORMAL/SHIFTED) the KeyPad is in
46
47 """
48
49 class State:
50 NORMAL = "NORMAL"
51 SHIFTED = "SHIFTED"
52 CAPSLOCK = "CAPSLOCK"
53
54 def __init__(self, *args):
55 super(KeyPad, self).__init__(*args)
56 self._key_pos = dict()
57 self._contained_keys = []
58 self._contained_shifted_keys = []
59
60 self.update_key_details()
61
62 def contains_key(self, label):
63 """Returns true if a key with the label *label* is contained within
64 this KeyPad.
65
66 """
67 return (label in self._contained_keys
68 or label in self._contained_shifted_keys)
69
70 def update_key_details(self):
71 def _iter_keys(key_type, label_fn):
72 for key in self.select_many(key_type):
73 with key.no_automatic_refreshing():
74 key_pos = Key.Pos(*key.globalRect)
75 label = label_fn(key)
76 if label != '':
77 self._contained_keys.append(label)
78 self._key_pos[label] = key_pos
79 if key.shifted != '':
80 self._contained_shifted_keys.append(key.shifted)
81 self._key_pos[key.shifted] = key_pos
82
83 _iter_keys(CharKey, lambda x: x.label)
84 _iter_keys(ActionKey, lambda x: x.action)
85
86 def press_key(self, key, pointer=None):
87 """Taps key *key* with *pointer*
88
89 If no pointer is passed in one is created for this purpose
90
91 raises *ValueError* if *key* is not contained within this KeyPad.
92 raises *RuntimeError* if this KeyPad is not visible.
93
94 """
95 if not self.contains_key(key):
96 raise ValueError(
97 "Key '%s' is not contained by this KeyPad (%s),"
98 " cannot press it." % (key, self.objectName)
99 )
100
101 if not self.visible:
102 raise RuntimeError(
103 "This Keypad (%s) is not visible" % self.objectName
104 )
105
106 required_state = self._get_keys_required_keypad_state(key)
107
108 self._switch_to_state(required_state, pointer)
109
110 key_rect = self.get_key_position(key)
111 self._tap_key(key_rect, pointer)
112
113 def get_key_position(self, key):
114 """Returns Key.Pos for the key with *key*.
115
116 raises *ValueError* if unable to find the stored position for *key*
117 (i.e. not contained within this KeyPad.)
118
119 """
120 key_rect = self._key_pos.get(key)
121
122 if key_rect is None:
123 raise ValueError("Unknown position for key '%s'" % key)
124
125 return key_rect
126
127 def _get_keys_required_keypad_state(self, key):
128 if key in ('shift', 'backspace', 'symbols', 'space', 'return'):
129 return self.state
130 elif key in self._contained_keys:
131 return KeyPad.State.NORMAL
132 elif key in self._contained_shifted_keys:
133 return KeyPad.State.SHIFTED
134 else:
135 raise ValueError(
136 "Don't know which state key '%s' requires." % key
137 )
138
139 def _switch_to_state(self, state, pointer):
140 """Move from one state to the other.
141
142 i.e. move from NORMAL to SHIFTED
143
144 """
145 if state == self.state:
146 return
147
148 # If shifted is needed and we're in CAPSLOCK that's fine.
149 if (state == KeyPad.State.SHIFTED
150 and self.state == KeyPad.State.CAPSLOCK):
151 logger.debug(
152 "Ignoring request to switch to SHIFTED, already in CAPSLOCK."
153 )
154 return
155
156 logger.debug("Switching from %s to %s" % (self.state, state))
157
158 if self.state == KeyPad.State.NORMAL:
159 expected_state = KeyPad.State.SHIFTED
160 else:
161 expected_state = KeyPad.State.NORMAL
162
163 key_rect = self.get_key_position("shift")
164
165 # Hack as we cannot tell when the shift key is ready to be pressed
166 # bug lp:1229003 and lp:1229001
167 sleep(.2)
168 self._tap_key(key_rect, pointer)
169 self.state.wait_for(expected_state)
170
171 def _tap_key(self, key_rect, pointer):
172 if pointer is None:
173 pointer = Pointer(Touch.create())
174 pointer.click_object(key_rect)
0175
=== modified file 'tests/autopilot/ubuntu_keyboard/tests/test_keyboard.py'
--- tests/autopilot/ubuntu_keyboard/tests/test_keyboard.py 2013-09-26 10:08:30 +0000
+++ tests/autopilot/ubuntu_keyboard/tests/test_keyboard.py 2013-09-26 16:09:59 +0000
@@ -22,12 +22,14 @@
22from testtools.matchers import Equals22from testtools.matchers import Equals
23from tempfile import mktemp23from tempfile import mktemp
24from textwrap import dedent24from textwrap import dedent
25from time import sleep
2526
26from autopilot.testcase import AutopilotTestCase27from autopilot.testcase import AutopilotTestCase
27from autopilot.input import Pointer, Touch28from autopilot.input import Pointer, Touch
28from autopilot.matchers import Eventually29from autopilot.matchers import Eventually
2930
30from ubuntu_keyboard.emulators.keyboard import Keyboard, KeyboardState31from ubuntu_keyboard.emulators.keyboard import Keyboard
32from ubuntu_keyboard.emulators.keypad import KeyPad
3133
3234
33class UbuntuKeyboardTests(AutopilotTestCase):35class UbuntuKeyboardTests(AutopilotTestCase):
@@ -141,10 +143,12 @@
141 )143 )
142 ),144 ),
143 (145 (
146 # Currently the en_us layout doesn't have ", but has \u201c and
147 # \u201d
144 'punctuation',148 'punctuation',
145 dict(149 dict(
146 label="Puncuation",150 label="Puncuation",
147 input='`~!@#$%^&*()_-+={}[]|\\:;"\'<>,.?/'151 input=u'`~!@#$%^&*()_-+={}[]|\\:;\'<>,.?/\u201c'
148 )152 )
149 )153 )
150 ]154 ]
@@ -169,14 +173,18 @@
169 shifted/capitalised.173 shifted/capitalised.
170174
171 """175 """
176 self.skip(
177 "Skipping as feature hasn't landed yet, refer to bug lp:1214695"
178 )
179
172 text_area = self.launch_test_input_area()180 text_area = self.launch_test_input_area()
173 self.ensure_focus_on_input(text_area)181 self.ensure_focus_on_input(text_area)
174 keyboard = Keyboard()182 keyboard = Keyboard()
175 self.addCleanup(keyboard.dismiss)183 self.addCleanup(keyboard.dismiss)
176184
177 self.assertThat(185 self.assertThat(
178 keyboard.keyboard.layoutState,186 keyboard.active_keypad.state,
179 Eventually(Equals(KeyboardState.SHIFTED))187 Eventually(Equals(KeyPad.State.SHIFTED))
180 )188 )
181189
182 def test_shift_latch(self):190 def test_shift_latch(self):
@@ -194,13 +202,15 @@
194 self.addCleanup(keyboard.dismiss)202 self.addCleanup(keyboard.dismiss)
195203
196 keyboard.type('abc')204 keyboard.type('abc')
197 keyboard.press_key('SHIFT')205 # Bug lp:1229003 and lp:1229001
198 keyboard.press_key('SHIFT')206 sleep(.2)
207 keyboard.press_key('shift')
208 keyboard.press_key('shift')
199 keyboard.type('S')209 keyboard.type('S')
200210
201 self.assertThat(211 self.assertThat(
202 keyboard.keyboard.layoutState,212 keyboard.active_keypad.state,
203 Eventually(Equals(KeyboardState.SHIFTED))213 Eventually(Equals(KeyPad.State.CAPSLOCK))
204 )214 )
205 self.assertThat(text_area.text, Eventually(Equals('abcS')))215 self.assertThat(text_area.text, Eventually(Equals('abcS')))
206216
@@ -224,8 +234,8 @@
224 # Once the capital letter has been typed, we must be able to access the234 # Once the capital letter has been typed, we must be able to access the
225 # lowercase letters, otherwise it's not in the correct state.235 # lowercase letters, otherwise it's not in the correct state.
226 self.assertThat(236 self.assertThat(
227 keyboard.keyboard.layoutState,237 keyboard.active_keypad.state,
228 Eventually(Equals(KeyboardState.DEFAULT))238 Eventually(Equals(KeyPad.State.NORMAL))
229 )239 )
230240
231 self.assertThat(text_area.text, Eventually(Equals('abcA')))241 self.assertThat(text_area.text, Eventually(Equals('abcA')))
@@ -237,6 +247,9 @@
237 enter the shifted state.247 enter the shifted state.
238248
239 """249 """
250 self.skip(
251 "Skipping as feature hasn't landed yet, refer to bug lp:1214695"
252 )
240 text_area = self.launch_test_input_area()253 text_area = self.launch_test_input_area()
241 self.ensure_focus_on_input(text_area)254 self.ensure_focus_on_input(text_area)
242 keyboard = Keyboard()255 keyboard = Keyboard()
@@ -250,8 +263,8 @@
250 )263 )
251264
252 self.assertThat(265 self.assertThat(
253 keyboard.keyboard.layoutState,266 keyboard.active_keypad.state,
254 Eventually(Equals(KeyboardState.SHIFTED))267 Eventually(Equals(KeyPad.State.SHIFTED))
255 )268 )
256269
257 def test_switching_between_states(self):270 def test_switching_between_states(self):
@@ -292,14 +305,6 @@
292 )305 )
293 ),306 ),
294 (307 (
295 "Password",
296 dict(
297 label="Password",
298 hints=['Qt.ImhHiddenText', 'Qt.ImhSensitiveData'],
299 expected_activeview="password"
300 )
301 ),
302 (
303 "Email",308 "Email",
304 dict(309 dict(
305 label="Email",310 label="Email",
@@ -320,7 +325,7 @@
320 dict(325 dict(
321 label="Telephone",326 label="Telephone",
322 hints=['Qt.ImhDigitsOnly'],327 hints=['Qt.ImhDigitsOnly'],
323 expected_activeview="phonenumber"328 expected_activeview="number"
324 )329 )
325 ),330 ),
326 ]331 ]
@@ -337,6 +342,6 @@
337 self.addCleanup(keyboard.dismiss)342 self.addCleanup(keyboard.dismiss)
338343
339 self.assertThat(344 self.assertThat(
340 keyboard.keyboard.activeView,345 keyboard.keyboard.layoutId,
341 Eventually(Equals(self.expected_activeview))346 Eventually(Equals(self.expected_activeview))
342 )347 )

Subscribers

People subscribed via source and target branches