| OLD | NEW |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 part of html; | 5 part of html; |
| 6 | 6 |
| 7 /** | 7 /** |
| 8 * Works with KeyboardEvent and KeyEvent to determine how to expose information | 8 * Internal class that does the actual calculations to determine keyCode and |
| 9 * about Key(board)Events. This class functions like an EventListenerList, and | 9 * charCode for keydown, keypress, and keyup events for all browsers. |
| 10 * provides a consistent interface for the Dart | |
| 11 * user, despite the fact that a multitude of browsers that have varying | |
| 12 * keyboard default behavior. | |
| 13 * | |
| 14 * This class is very much a work in progress, and we'd love to get information | |
| 15 * on how we can make this class work with as many international keyboards as | |
| 16 * possible. Bugs welcome! | |
| 17 */ | 10 */ |
| 18 class KeyboardEventController { | 11 class _KeyboardEventHandler implements EventStreamProvider<KeyEvent> { |
| 19 // This code inspired by Closure's KeyHandling library. | 12 // This code inspired by Closure's KeyHandling library. |
| 20 // http://closure-library.googlecode.com/svn/docs/closure_goog_events_keyhandl
er.js.source.html | 13 // http://closure-library.googlecode.com/svn/docs/closure_goog_events_keyhandl
er.js.source.html |
| 21 | 14 |
| 22 /** | 15 /** |
| 23 * The set of keys that have been pressed down without seeing their | 16 * The set of keys that have been pressed down without seeing their |
| 24 * corresponding keyup event. | 17 * corresponding keyup event. |
| 25 */ | 18 */ |
| 26 List<KeyboardEvent> _keyDownList; | 19 List<KeyboardEvent> _keyDownList; |
| 27 | 20 |
| 28 /** The set of functions that wish to be notified when a KeyEvent happens. */ | 21 /** The set of functions that wish to be notified when a KeyEvent happens. */ |
| 29 List<Function> _callbacks; | 22 List<Function> _callbacks; |
| 30 | 23 |
| 31 /** The type of KeyEvent we are tracking (keyup, keydown, keypress). */ | 24 /** The type of KeyEvent we are tracking (keyup, keydown, keypress). */ |
| 32 String _type; | 25 String _type; |
| 33 | 26 |
| 34 /** The element we are watching for events to happen on. */ | 27 /** The element we are watching for events to happen on. */ |
| 35 EventTarget _target; | 28 EventTarget _target; |
| 36 | 29 |
| 37 // The distance to shift from upper case alphabet Roman letters to lower case. | 30 // The distance to shift from upper case alphabet Roman letters to lower case. |
| 38 final int _ROMAN_ALPHABET_OFFSET = "a".codeUnits[0] - "A".codeUnits[0]; | 31 final int _ROMAN_ALPHABET_OFFSET = "a".codeUnits[0] - "A".codeUnits[0]; |
| 39 | 32 |
| 40 StreamSubscription _keyUpSubscription, _keyDownSubscription, | 33 /** Controller to produce KeyEvents for the stream. */ |
| 41 _keyPressSubscription; | 34 StreamController _controller; |
| 42 | 35 |
| 43 /** | 36 /** |
| 44 * An enumeration of key identifiers currently part of the W3C draft for DOM3 | 37 * An enumeration of key identifiers currently part of the W3C draft for DOM3 |
| 45 * and their mappings to keyCodes. | 38 * and their mappings to keyCodes. |
| 46 * http://www.w3.org/TR/DOM-Level-3-Events/keyset.html#KeySet-Set | 39 * http://www.w3.org/TR/DOM-Level-3-Events/keyset.html#KeySet-Set |
| 47 */ | 40 */ |
| 48 static Map<String, int> _keyIdentifier = { | 41 static Map<String, int> _keyIdentifier = { |
| 49 'Up': KeyCode.UP, | 42 'Up': KeyCode.UP, |
| 50 'Down': KeyCode.DOWN, | 43 'Down': KeyCode.DOWN, |
| 51 'Left': KeyCode.LEFT, | 44 'Left': KeyCode.LEFT, |
| (...skipping 12 matching lines...) Expand all Loading... |
| 64 'F11': KeyCode.F11, | 57 'F11': KeyCode.F11, |
| 65 'F12': KeyCode.F12, | 58 'F12': KeyCode.F12, |
| 66 'U+007F': KeyCode.DELETE, | 59 'U+007F': KeyCode.DELETE, |
| 67 'Home': KeyCode.HOME, | 60 'Home': KeyCode.HOME, |
| 68 'End': KeyCode.END, | 61 'End': KeyCode.END, |
| 69 'PageUp': KeyCode.PAGE_UP, | 62 'PageUp': KeyCode.PAGE_UP, |
| 70 'PageDown': KeyCode.PAGE_DOWN, | 63 'PageDown': KeyCode.PAGE_DOWN, |
| 71 'Insert': KeyCode.INSERT | 64 'Insert': KeyCode.INSERT |
| 72 }; | 65 }; |
| 73 | 66 |
| 74 /** Named constructor to add an onKeyPress event listener to our handler. */ | 67 /** |
| 75 KeyboardEventController.keypress(EventTarget target) { | 68 * Gets the type of the event which this would listen for on the specified |
| 76 _KeyboardEventController(target, 'keypress'); | 69 * event target. |
| 77 } | 70 */ |
| 71 String getEventType(EventTarget target) => 'KeyEvent'; |
| 78 | 72 |
| 79 /** Named constructor to add an onKeyUp event listener to our handler. */ | 73 /** Return a stream for KeyEvents for the specified target. */ |
| 80 KeyboardEventController.keyup(EventTarget target) { | 74 Stream<KeyEvent> forTarget(EventTarget e, {bool useCapture: false}) { |
| 81 _KeyboardEventController(target, 'keyup'); | 75 _initializeAllEventListeners(e); |
| 82 } | 76 return _controller.stream; |
| 83 | |
| 84 /** Named constructor to add an onKeyDown event listener to our handler. */ | |
| 85 KeyboardEventController.keydown(EventTarget target) { | |
| 86 _KeyboardEventController(target, 'keydown'); | |
| 87 } | 77 } |
| 88 | 78 |
| 89 /** | 79 /** |
| 90 * General constructor, performs basic initialization for our improved | 80 * General constructor, performs basic initialization for our improved |
| 91 * KeyboardEvent controller. | 81 * KeyboardEvent controller. |
| 92 */ | 82 */ |
| 93 _KeyboardEventController(EventTarget target, String type) { | 83 _KeyboardEventHandler(String type) { |
| 84 _type = type; |
| 85 _controller = new StreamController.broadcast(); |
| 94 _callbacks = []; | 86 _callbacks = []; |
| 95 _type = type; | |
| 96 _target = target; | |
| 97 } | 87 } |
| 98 | 88 |
| 99 /** | 89 /** |
| 100 * Hook up all event listeners under the covers so we can estimate keycodes | 90 * Hook up all event listeners under the covers so we can estimate keycodes |
| 101 * and charcodes when they are not provided. | 91 * and charcodes when they are not provided. |
| 102 */ | 92 */ |
| 103 void _initializeAllEventListeners() { | 93 _initializeAllEventListeners(EventTarget target) { |
| 94 _target = target; |
| 104 _keyDownList = []; | 95 _keyDownList = []; |
| 105 if (_keyDownSubscription == null) { | 96 Element.keyDownEvent.forTarget(_target, useCapture: true).listen( |
| 106 _keyDownSubscription = Element.keyDownEvent.forTarget( | 97 processKeyDown); |
| 107 _target, useCapture: true).listen(processKeyDown); | 98 Element.keyPressEvent.forTarget(_target, useCapture: true).listen( |
| 108 _keyPressSubscription = Element.keyPressEvent.forTarget( | 99 processKeyPress); |
| 109 _target, useCapture: true).listen(processKeyUp); | 100 Element.keyUpEvent.forTarget(_target, useCapture: true).listen( |
| 110 _keyUpSubscription = Element.keyUpEvent.forTarget( | 101 processKeyUp); |
| 111 _target, useCapture: true).listen(processKeyPress); | |
| 112 } | |
| 113 } | |
| 114 | |
| 115 /** Add a callback that wishes to be notified when a KeyEvent occurs. */ | |
| 116 void add(void callback(KeyEvent)) { | |
| 117 if (_callbacks.length == 0) { | |
| 118 _initializeAllEventListeners(); | |
| 119 } | |
| 120 _callbacks.add(callback); | |
| 121 } | 102 } |
| 122 | 103 |
| 123 /** | 104 /** |
| 124 * Notify all callback listeners that a KeyEvent of the relevant type has | 105 * Notify all callback listeners that a KeyEvent of the relevant type has |
| 125 * occurred. | 106 * occurred. |
| 126 */ | 107 */ |
| 127 bool _dispatch(KeyEvent event) { | 108 bool _dispatch(KeyEvent event) { |
| 128 if (event.type == _type) { | 109 if (event.type == _type) |
| 129 // Make a copy of the listeners in case a callback gets removed while | 110 _controller.add(event); |
| 130 // dispatching from the list. | |
| 131 List callbacksCopy = new List.from(_callbacks); | |
| 132 for(var callback in callbacksCopy) { | |
| 133 callback(event); | |
| 134 } | |
| 135 } | |
| 136 } | |
| 137 | |
| 138 /** Remove the given callback from the listeners list. */ | |
| 139 void remove(void callback(KeyEvent)) { | |
| 140 var index = _callbacks.indexOf(callback); | |
| 141 if (index != -1) { | |
| 142 _callbacks.removeAt(index); | |
| 143 } | |
| 144 if (_callbacks.length == 0) { | |
| 145 // If we have no listeners, don't bother keeping track of keypresses. | |
| 146 _keyDownSubscription.cancel(); | |
| 147 _keyDownSubscription = null; | |
| 148 _keyPressSubscription.cancel(); | |
| 149 _keyPressSubscription = null; | |
| 150 _keyUpSubscription.cancel(); | |
| 151 _keyUpSubscription = null; | |
| 152 } | |
| 153 } | 111 } |
| 154 | 112 |
| 155 /** Determine if caps lock is one of the currently depressed keys. */ | 113 /** Determine if caps lock is one of the currently depressed keys. */ |
| 156 bool get _capsLockOn => | 114 bool get _capsLockOn => |
| 157 _keyDownList.any((var element) => element.keyCode == KeyCode.CAPS_LOCK); | 115 _keyDownList.any((var element) => element.keyCode == KeyCode.CAPS_LOCK); |
| 158 | 116 |
| 159 /** | 117 /** |
| 160 * Given the previously recorded keydown key codes, see if we can determine | 118 * Given the previously recorded keydown key codes, see if we can determine |
| 161 * the keycode of this keypress [event]. (Generally browsers only provide | 119 * the keycode of this keypress [event]. (Generally browsers only provide |
| 162 * charCode information for keypress events, but with a little | 120 * charCode information for keypress events, but with a little |
| (...skipping 238 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 401 _keyDownList.where((element) => element != toRemove).toList(); | 359 _keyDownList.where((element) => element != toRemove).toList(); |
| 402 } else if (_keyDownList.length > 0) { | 360 } else if (_keyDownList.length > 0) { |
| 403 // This happens when we've reached some international keyboard case we | 361 // This happens when we've reached some international keyboard case we |
| 404 // haven't accounted for or we haven't correctly eliminated all browser | 362 // haven't accounted for or we haven't correctly eliminated all browser |
| 405 // inconsistencies. Filing bugs on when this is reached is welcome! | 363 // inconsistencies. Filing bugs on when this is reached is welcome! |
| 406 _keyDownList.removeLast(); | 364 _keyDownList.removeLast(); |
| 407 } | 365 } |
| 408 _dispatch(e); | 366 _dispatch(e); |
| 409 } | 367 } |
| 410 } | 368 } |
| 369 |
| 370 |
| 371 /** |
| 372 * Records KeyboardEvents that occur on a particular element, and provides a |
| 373 * stream of outgoing KeyEvents with cross-browser consistent keyCode and |
| 374 * charCode values despite the fact that a multitude of browsers that have |
| 375 * varying keyboard default behavior. |
| 376 * |
| 377 * Example usage: |
| 378 * |
| 379 * new KeyboardEventStream.onKeyDown(document.body).listen( |
| 380 * keydownHandlerTest); |
| 381 * |
| 382 * This class is very much a work in progress, and we'd love to get information |
| 383 * on how we can make this class work with as many international keyboards as |
| 384 * possible. Bugs welcome! |
| 385 */ |
| 386 class KeyboardEventStream extends Stream<KeyEvent> { |
| 387 |
| 388 /** Named constructor to produce a stream for onKeyPress events. */ |
| 389 factory KeyboardEventStream.onKeyPress(EventTarget target) => |
| 390 new _KeyboardEventHandler('keypress').forTarget(target); |
| 391 |
| 392 /** Named constructor to produce a stream for onKeyUp events. */ |
| 393 factory KeyboardEventStream.onKeyUp(EventTarget target) => |
| 394 new _KeyboardEventHandler('keyup').forTarget(target); |
| 395 |
| 396 /** Named constructor to produce a stream for onKeyDown events. */ |
| 397 factory KeyboardEventStream.onKeyDown(EventTarget target) => |
| 398 new _KeyboardEventHandler('keydown').forTarget(target); |
| 399 } |
| OLD | NEW |