Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(15)

Side by Side Diff: ui/keyboard/resources/common.js

Issue 13652010: Add a virtual keyboard webui at chrome://keyboard/ (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: build fix Created 7 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « ui/keyboard/keyboard_ui_controller.cc ('k') | ui/keyboard/resources/images/chevron.svg » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 /**
6 * @fileoverview A simple virtual keyboard implementation.
7 */
8
9 var KEY_MODE = 'key';
10 var SHIFT_MODE = 'shift';
11 var NUMBER_MODE = 'number';
12 var SYMBOL_MODE = 'symbol';
13 // TODO(bryeung): tear out all of this mode switching code
14 var MODES = [KEY_MODE, SHIFT_MODE, NUMBER_MODE, SYMBOL_MODE];
15 var currentMode = KEY_MODE;
16 var enterShiftModeOnSpace = false;
17 var MODE_CODES = {};
18 var MODE_TRANSITIONS = {};
19
20 MODE_CODES[KEY_MODE] = 0;
21 MODE_CODES[SHIFT_MODE] = 1;
22 MODE_CODES[NUMBER_MODE] = 2;
23 MODE_CODES[SYMBOL_MODE] = 3;
24
25 MODE_TRANSITIONS[KEY_MODE + SHIFT_MODE] = SHIFT_MODE;
26 MODE_TRANSITIONS[KEY_MODE + NUMBER_MODE] = NUMBER_MODE;
27 MODE_TRANSITIONS[SHIFT_MODE + SHIFT_MODE] = KEY_MODE;
28 MODE_TRANSITIONS[SHIFT_MODE + NUMBER_MODE] = NUMBER_MODE;
29 MODE_TRANSITIONS[NUMBER_MODE + SHIFT_MODE] = SYMBOL_MODE;
30 MODE_TRANSITIONS[NUMBER_MODE + NUMBER_MODE] = KEY_MODE;
31 MODE_TRANSITIONS[SYMBOL_MODE + SHIFT_MODE] = NUMBER_MODE;
32 MODE_TRANSITIONS[SYMBOL_MODE + NUMBER_MODE] = KEY_MODE;
33
34 var KEYBOARDS = {};
35
36 /**
37 * The long-press delay in milliseconds before long-press handler is invoked.
38 * @type {number}
39 */
40 var LONGPRESS_DELAY_MSEC = 500;
41
42 /**
43 * The repeat delay in milliseconds before a key starts repeating. Use the same
44 * rate as Chromebook. (See chrome/browser/chromeos/language_preferences.cc)
45 * @type {number}
46 */
47 var REPEAT_DELAY_MSEC = 500;
48
49 /**
50 * The repeat interval or number of milliseconds between subsequent keypresses.
51 * Use the same rate as Chromebook.
52 * @type {number}
53 */
54 var REPEAT_INTERVAL_MSEC = 50;
55
56 /**
57 * The keyboard layout name currently in use.
58 * @type {string}
59 */
60 var currentKeyboardLayout = 'us';
61
62 /**
63 * A structure to track the currently repeating key on the keyboard.
64 */
65 var repeatKey = {
66 /**
67 * The timer for the delay before repeating behaviour begins.
68 * @type {number|undefined}
69 */
70 timer: undefined,
71
72 /**
73 * The interval timer for issuing keypresses of a repeating key.
74 * @type {number|undefined}
75 */
76 interval: undefined,
77
78 /**
79 * The key which is currently repeating.
80 * @type {BaseKey|undefined}
81 */
82 key: undefined,
83
84 /**
85 * Cancel the repeat timers of the currently active key.
86 */
87 cancel: function() {
88 clearTimeout(this.timer);
89 clearInterval(this.interval);
90 this.timer = undefined;
91 this.interval = undefined;
92 this.key = undefined;
93 }
94 };
95
96 /**
97 * Set the keyboard mode.
98 * @param {string} mode The new mode.
99 */
100 function setMode(mode) {
101 currentMode = mode;
102
103 var rows = KEYBOARDS[currentKeyboardLayout]['rows'];
104 for (var i = 0; i < rows.length; ++i) {
105 rows[i].showMode(currentMode);
106 }
107 }
108
109 /**
110 * Transition the mode according to the given transition.
111 * @param {string} transition The transition to take.
112 */
113 function transitionMode(transition) {
114 setMode(MODE_TRANSITIONS[currentMode + transition]);
115 }
116
117 function logIfError() {
118 if (chrome.runtime.lastError) {
119 console.log(chrome.runtime.lastError);
120 }
121 }
122
123 /**
124 * Send the given key to chrome, via the experimental extension API.
125 * @param {string} keyIdentifier The key to send.
126 */
127 function sendKey(keyIdentifier) {
128 // FIXME(bryeung)
129 console.log('Typed: ' + keyIdentifier);
130 var keyEvent = {
131 type: 'keydown',
132 keyIdentifier: keyIdentifier
133 };
134 chrome.experimental.input.virtualKeyboard.sendKeyboardEvent(keyEvent,
135 logIfError);
136 keyEvent.type = 'keyup';
137 chrome.experimental.input.virtualKeyboard.sendKeyboardEvent(keyEvent,
138 logIfError);
139
140 // Exit shift mode after pressing any key but space.
141 if (currentMode == SHIFT_MODE && keyIdentifier != 'Spacebar') {
142 transitionMode(SHIFT_MODE);
143 }
144 // Enter shift mode after typing a closing punctuation and then a space for a
145 // new sentence.
146 if (enterShiftModeOnSpace) {
147 enterShiftModeOnSpace = false;
148 if (currentMode != SHIFT_MODE && keyIdentifier == 'Spacebar') {
149 setMode(SHIFT_MODE);
150 }
151 }
152 if (currentMode != SHIFT_MODE &&
153 (keyIdentifier == '.' || keyIdentifier == '?' || keyIdentifier == '!')) {
154 enterShiftModeOnSpace = true;
155 }
156 }
157
158 /**
159 * Add a child div element that represents the content of the given element.
160 * A child div element that represents a text content is added if
161 * opt_textContent is given. Otherwise a child element that represents an image
162 * content is added. If the given element already has a child, the child element
163 * is modified.
164 * @param {Element} element The DOM Element to which the content is added.
165 * @param {string} opt_textContent The text to be inserted.
166 */
167 function addContent(element, opt_textContent) {
168 if (element.childNodes.length > 0) {
169 var content = element.childNodes[0];
170 if (opt_textContent) {
171 content.textContent = opt_textContent;
172 }
173 return;
174 }
175
176 var content = document.createElement('div');
177 if (opt_textContent) {
178 content.textContent = opt_textContent;
179 content.className = 'text-key';
180 } else {
181 content.className = 'image-key';
182 }
183 element.appendChild(content);
184 }
185
186 /**
187 * Set up the event handlers necessary to respond to mouse and touch events on
188 * the virtual keyboard.
189 * @param {BaseKey} key The BaseKey object corresponding to this key.
190 * @param {Element} element The top-level DOM Element to set event handlers on.
191 * @param {Object.<string, function()>} handlers The object that contains key
192 * event handlers in the following form.
193 *
194 * { 'up': keyUpHandler,
195 * 'down': keyDownHandler,
196 * 'long': keyLongHandler }
197 *
198 * keyDownHandler: Called when the key is pressed. This will be called
199 * repeatedly when holding a repeating key.
200 * keyUpHandler: Called when the key is released. This is only called
201 * once per actual key press.
202 * keyLongHandler: Called when the key is long-pressed for
203 * |LONGPRESS_DELAY_MSEC| milliseconds.
204 *
205 * The object does not necessarily contain all the handlers above, but
206 * needs to contain at least one of them.
207 */
208 function setupKeyEventHandlers(key, element, handlers) {
209 var keyDownHandler = handlers['down'];
210 var keyUpHandler = handlers['up'];
211 var keyLongHandler = handlers['long'];
212 if (!(keyDownHandler || keyUpHandler || keyLongPressHandler)) {
213 throw new Error('Invalid handlers passed to setupKeyEventHandlers');
214 }
215
216 /**
217 * Handle a key down event on the virtual key.
218 * @param {UIEvent} evt The UI event which triggered the key down.
219 */
220 var downHandler = function(evt) {
221 // Prevent any of the system gestures from happening.
222 evt.preventDefault();
223
224 // Don't process a key down if the key is already down.
225 if (key.pressed) {
226 return;
227 }
228 key.pressed = true;
229 if (keyDownHandler) {
230 keyDownHandler();
231 }
232 repeatKey.cancel();
233
234 // Start a repeating timer if there is a repeat interval and a function to
235 // process key down events.
236 if (key.repeat && keyDownHandler) {
237 repeatKey.key = key;
238 // The timeout for the repeating timer occurs at
239 // REPEAT_DELAY_MSEC - REPEAT_INTERVAL_MSEC so that the interval
240 // function can handle all repeat keypresses and will get the first one
241 // at the correct time.
242 repeatKey.timer = setTimeout(function() {
243 repeatKey.timer = undefined;
244 repeatKey.interval = setInterval(function() {
245 keyDownHandler();
246 }, REPEAT_INTERVAL_MSEC);
247 }, Math.max(0, REPEAT_DELAY_MSEC - REPEAT_INTERVAL_MSEC));
248 }
249
250 if (keyLongHandler) {
251 // Copy the currentTarget of event, which is neccessary because |evt| can
252 // be modified before |keyLongHandler| is called.
253 var evtCopy = {};
254 evtCopy.currentTarget = evt.currentTarget;
255 key.longPressTimer = setTimeout(function() {
256 keyLongHandler(evtCopy),
257 clearTimeout(key.longPressTimer);
258 delete key.longPressTimer;
259 key.pressed = false;
260 }, LONGPRESS_DELAY_MSEC);
261 }
262 };
263
264 /**
265 * Handle a key up event on the virtual key.
266 * @param {UIEvent} evt The UI event which triggered the key up.
267 */
268 var upHandler = function(evt) {
269 // Prevent any of the system gestures from happening.
270 evt.preventDefault();
271
272 // Reset long-press timer.
273 if (key.longPressTimer) {
274 clearTimeout(key.longPressTimer);
275 delete key.longPressTimer;
276 }
277
278 // If they key was not actually pressed do not send a key up event.
279 if (!key.pressed) {
280 return;
281 }
282 key.pressed = false;
283
284 // Cancel running repeat timer for the released key only.
285 if (repeatKey.key == key) {
286 repeatKey.cancel();
287 }
288
289 if (keyUpHandler) {
290 keyUpHandler();
291 }
292 };
293
294 // Setup mouse event handlers.
295 element.addEventListener('mousedown', downHandler);
296 element.addEventListener('mouseup', upHandler);
297
298 // Setup touch handlers.
299 element.addEventListener('touchstart', downHandler);
300 element.addEventListener('touchend', upHandler);
301 }
302
303 /**
304 * Create closure for the sendKey function.
305 * @param {string} key The key paramater to sendKey.
306 * @return {function()} A function which calls sendKey(key).
307 */
308 function sendKeyFunction(key) {
309 return function() {
310 sendKey(key);
311 };
312 }
313
314 /**
315 * Plain-old-data class to represent a character.
316 * @param {string} display The HTML to be displayed.
317 * @param {string} id The key identifier for this Character.
318 * @constructor
319 */
320 function Character(display, id) {
321 this.display = display;
322 this.keyIdentifier = id;
323 }
324
325 /**
326 * Convenience function to make the keyboard data more readable.
327 * @param {string} display The display for the created Character.
328 * @param {string} opt_id The id for the created Character.
329 * @return {Character} A character that contains display and opt_id. If
330 * opt_id is omitted, display is used as the id.
331 */
332 function C(display, opt_id) {
333 var id = opt_id || display;
334 return new Character(display, id);
335 }
336
337 /**
338 * An abstract base-class for all keys on the keyboard.
339 * @constructor
340 */
341 function BaseKey() {}
342
343 BaseKey.prototype = {
344 /**
345 * The cell type of this key. Determines the background colour.
346 * @type {string}
347 */
348 cellType_: '',
349
350 /**
351 * If true, holding this key will issue repeat keypresses.
352 * @type {boolean}
353 */
354 repeat_: false,
355
356 /**
357 * Track the pressed state of the key. This is true if currently pressed.
358 * @type {boolean}
359 */
360 pressed_: false,
361
362 /**
363 * Get the repeat behaviour of the key.
364 * @return {boolean} True if the key will repeat.
365 */
366 get repeat() {
367 return this.repeat_;
368 },
369
370 /**
371 * Set the repeat behaviour of the key
372 * @param {boolean} repeat True if the key should repeat.
373 */
374 set repeat(repeat) {
375 this.repeat_ = repeat;
376 },
377
378 /**
379 * Get the pressed state of the key.
380 * @return {boolean} True if the key is currently pressed.
381 */
382 get pressed() {
383 return this.pressed_;
384 },
385
386 /**
387 * Set the pressed state of the key.
388 * @param {boolean} pressed True if the key is currently pressed.
389 */
390 set pressed(pressed) {
391 this.pressed_ = pressed;
392 },
393
394 /**
395 * Create the DOM elements for the given keyboard mode. Must be overridden.
396 * @param {string} mode The keyboard mode to create elements for.
397 * @return {Element} The top-level DOM Element for the key.
398 */
399 makeDOM: function(mode) {
400 throw new Error('makeDOM not implemented in BaseKey');
401 },
402 };
403
404 /**
405 * A simple key which displays Characters.
406 * @param {Object} key The Character for KEY_MODE.
407 * @param {Object} shift The Character for SHIFT_MODE.
408 * @param {string} className An optional class name for the key.
409 * @constructor
410 * @extends {BaseKey}
411 */
412 function Key(key, shift, className) {
413 this.modeElements_ = {};
414 this.cellType_ = '';
415 this.className_ = (className) ? 'key ' + className : 'key';
416
417 this.modes_ = {};
418 this.modes_[KEY_MODE] = key;
419 this.modes_[SHIFT_MODE] = shift;
420 }
421
422 Key.prototype = {
423 __proto__: BaseKey.prototype,
424
425 /** @override */
426 makeDOM: function(mode) {
427 if (!this.modes_[mode]) {
428 return null;
429 }
430
431 this.modeElements_[mode] = document.createElement('div');
432 var element = this.modeElements_[mode];
433 element.className = this.className_;
434
435 addContent(element, this.modes_[mode].display);
436
437 setupKeyEventHandlers(this, element,
438 { 'up': sendKeyFunction(this.modes_[mode].keyIdentifier) });
439 return element;
440 }
441 };
442
443 /**
444 * A key which displays an SVG image.
445 * @param {string} className The class that provides the image.
446 * @param {string} keyId The key identifier for the key.
447 * @param {boolean} opt_repeat True if the key should repeat.
448 * @constructor
449 * @extends {BaseKey}
450 */
451 function SvgKey(className, keyId, opt_repeat) {
452 this.modeElements_ = {};
453 this.cellType_ = 'nc';
454 this.className_ = className;
455 this.keyId_ = keyId;
456 this.repeat_ = opt_repeat || false;
457 }
458
459 SvgKey.prototype = {
460 __proto__: BaseKey.prototype,
461
462 /** @override */
463 makeDOM: function(mode) {
464 this.modeElements_[mode] = document.createElement('div');
465 this.modeElements_[mode].className = 'key';
466 this.modeElements_[mode].classList.add(this.className_);
467 addContent(this.modeElements_[mode]);
468
469 // send the key event on key down if key repeat is enabled
470 var handler = this.repeat_ ? { 'down' : sendKeyFunction(this.keyId_) } :
471 { 'up' : sendKeyFunction(this.keyId_) };
472 setupKeyEventHandlers(this, this.modeElements_[mode], handler);
473
474 return this.modeElements_[mode];
475 }
476 };
477
478 /**
479 * A Key that remains the same through all modes.
480 * @param {string} className The class name for the key.
481 * @param {string} content The display text for the key.
482 * @param {string} keyId The key identifier for the key.
483 * @constructor
484 * @extends {BaseKey}
485 */
486 function SpecialKey(className, content, keyId) {
487 this.modeElements_ = {};
488 this.cellType_ = 'nc';
489 this.content_ = content;
490 this.keyId_ = keyId;
491 this.className_ = className;
492 }
493
494 SpecialKey.prototype = {
495 __proto__: BaseKey.prototype,
496
497 /** @override */
498 makeDOM: function(mode) {
499 this.modeElements_[mode] = document.createElement('div');
500 this.modeElements_[mode].className = 'key';
501 this.modeElements_[mode].classList.add(this.className_);
502 addContent(this.modeElements_[mode], this.content_);
503
504 setupKeyEventHandlers(this, this.modeElements_[mode],
505 { 'up': sendKeyFunction(this.keyId_) });
506
507 return this.modeElements_[mode];
508 }
509 };
510
511 /**
512 * A shift key.
513 * @constructor
514 * @param {string} className The class name for the key.
515 * @extends {BaseKey}
516 */
517 function ShiftKey(className) {
518 this.modeElements_ = {};
519 this.cellType_ = 'nc';
520 this.className_ = className;
521 }
522
523 ShiftKey.prototype = {
524 __proto__: BaseKey.prototype,
525
526 /** @override */
527 makeDOM: function(mode) {
528 this.modeElements_[mode] = document.createElement('div');
529 this.modeElements_[mode].className = 'key shift';
530 this.modeElements_[mode].classList.add(this.className_);
531
532 if (mode == KEY_MODE || mode == SHIFT_MODE) {
533 addContent(this.modeElements_[mode]);
534 } else if (mode == NUMBER_MODE) {
535 addContent(this.modeElements_[mode], 'more');
536 } else if (mode == SYMBOL_MODE) {
537 addContent(this.modeElements_[mode], '#123');
538 }
539
540 if (mode == SHIFT_MODE || mode == SYMBOL_MODE) {
541 this.modeElements_[mode].classList.add('moddown');
542 } else {
543 this.modeElements_[mode].classList.remove('moddown');
544 }
545
546 setupKeyEventHandlers(this, this.modeElements_[mode],
547 { 'down': function() {
548 transitionMode(SHIFT_MODE);
549 }});
550
551 return this.modeElements_[mode];
552 },
553 };
554
555 /**
556 * The symbol key: switches the keyboard into symbol mode.
557 * @constructor
558 * @extends {BaseKey}
559 */
560 function SymbolKey() {
561 this.modeElements_ = {};
562 this.cellType_ = 'nc';
563 }
564
565 SymbolKey.prototype = {
566 __proto__: BaseKey.prototype,
567
568 /** @override */
569 makeDOM: function(mode, height) {
570 this.modeElements_[mode] = document.createElement('div');
571 this.modeElements_[mode].className = 'key symbol';
572
573 if (mode == KEY_MODE || mode == SHIFT_MODE) {
574 addContent(this.modeElements_[mode], '#123');
575 } else if (mode == NUMBER_MODE || mode == SYMBOL_MODE) {
576 addContent(this.modeElements_[mode], 'abc');
577 }
578
579 if (mode == NUMBER_MODE || mode == SYMBOL_MODE) {
580 this.modeElements_[mode].classList.add('moddown');
581 } else {
582 this.modeElements_[mode].classList.remove('moddown');
583 }
584
585 setupKeyEventHandlers(this, this.modeElements_[mode],
586 { 'down': function() {
587 transitionMode(NUMBER_MODE);
588 }});
589
590 return this.modeElements_[mode];
591 }
592 };
593
594 /**
595 * The ".com" key.
596 * @constructor
597 * @extends {BaseKey}
598 */
599 function DotComKey() {
600 this.modeElements_ = {};
601 this.cellType_ = 'nc';
602 }
603
604 DotComKey.prototype = {
605 __proto__: BaseKey.prototype,
606
607 /** @override */
608 makeDOM: function(mode) {
609 this.modeElements_[mode] = document.createElement('div');
610 this.modeElements_[mode].className = 'key com';
611 addContent(this.modeElements_[mode], '.com');
612
613 setupKeyEventHandlers(this, this.modeElements_[mode],
614 { 'up': function() {
615 sendKey('.');
616 sendKey('c');
617 sendKey('o');
618 sendKey('m');
619 }});
620
621 return this.modeElements_[mode];
622 }
623 };
624
625 /**
626 * The key that hides the keyboard.
627 * @constructor
628 * @extends {BaseKey}
629 */
630 function HideKeyboardKey() {
631 this.modeElements_ = {};
632 this.cellType_ = 'nc';
633 }
634
635 HideKeyboardKey.prototype = {
636 __proto__: BaseKey.prototype,
637
638 /** @override */
639 makeDOM: function(mode) {
640 this.modeElements_[mode] = document.createElement('div');
641 this.modeElements_[mode].className = 'key hide';
642 addContent(this.modeElements_[mode]);
643
644 setupKeyEventHandlers(this, this.modeElements_[mode],
645 { 'down': function() { console.log('Hide the keyboard!'); } });
646
647 return this.modeElements_[mode];
648 }
649 };
650
651 /**
652 * A container for keys.
653 * @param {number} position The position of the row (0-3).
654 * @param {Array.<BaseKey>} keys The keys in the row.
655 * @constructor
656 */
657 function Row(position, keys) {
658 this.position_ = position;
659 this.keys_ = keys;
660 this.element_ = null;
661 this.modeElements_ = {};
662 }
663
664 Row.prototype = {
665 /**
666 * Create the DOM elements for the row.
667 * @return {Element} The top-level DOM Element for the row.
668 */
669 makeDOM: function() {
670 this.element_ = document.createElement('div');
671 this.element_.className = 'row';
672 for (var i = 0; i < MODES.length; ++i) {
673 var mode = MODES[i];
674 this.modeElements_[mode] = document.createElement('div');
675 this.modeElements_[mode].style.display = 'none';
676 this.element_.appendChild(this.modeElements_[mode]);
677 }
678
679 for (var j = 0; j < this.keys_.length; ++j) {
680 var key = this.keys_[j];
681 for (var i = 0; i < MODES.length; ++i) {
682 var keyDom = key.makeDOM(MODES[i]);
683 if (keyDom) {
684 this.modeElements_[MODES[i]].appendChild(keyDom);
685 }
686 }
687 }
688
689 for (var i = 0; i < MODES.length; ++i) {
690 var clearingDiv = document.createElement('div');
691 clearingDiv.style.clear = 'both';
692 this.modeElements_[MODES[i]].appendChild(clearingDiv);
693 }
694
695 return this.element_;
696 },
697
698 /**
699 * Shows the given mode.
700 * @param {string} mode The mode to show.
701 */
702 showMode: function(mode) {
703 for (var i = 0; i < MODES.length; ++i) {
704 this.modeElements_[MODES[i]].style.display = 'none';
705 }
706 this.modeElements_[mode].style.display = '-webkit-box';
707 },
708
709 /**
710 * Returns the size of keys this row contains.
711 * @return {number} The size of keys.
712 */
713 get length() {
714 return this.keys_.length;
715 }
716 };
OLDNEW
« no previous file with comments | « ui/keyboard/keyboard_ui_controller.cc ('k') | ui/keyboard/resources/images/chevron.svg » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698