OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * @fileoverview Handles braille input keys when the user is typing or editing | 6 * @fileoverview Handles braille input keys when the user is typing or editing |
7 * text in an input field. This class cooperates with the Braille IME | 7 * text in an input field. This class cooperates with the Braille IME |
8 * that is built into Chrome OS to do the actual text editing. | 8 * that is built into Chrome OS to do the actual text editing. |
9 */ | 9 */ |
10 | 10 |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
66 * the race, we store the cell entered until we can submit it to the IME. | 66 * the race, we store the cell entered until we can submit it to the IME. |
67 * @type {!Array<number>} | 67 * @type {!Array<number>} |
68 * @private | 68 * @private |
69 */ | 69 */ |
70 this.pendingCells_ = []; | 70 this.pendingCells_ = []; |
71 /** | 71 /** |
72 * @type {cvox.BrailleInputHandler.EntryState_} | 72 * @type {cvox.BrailleInputHandler.EntryState_} |
73 * @private | 73 * @private |
74 */ | 74 */ |
75 this.entryState_ = null; | 75 this.entryState_ = null; |
| 76 /** |
| 77 * @type {cvox.ExtraCellsSpan} |
| 78 * @private |
| 79 */ |
| 80 this.uncommittedCellsSpan_ = null; |
| 81 /** |
| 82 * @type {function()?} |
| 83 * @private |
| 84 */ |
| 85 this.uncommittedCellsChangedListener_ = null; |
76 | 86 |
77 this.translatorManager_.addChangeListener( | 87 this.translatorManager_.addChangeListener( |
78 this.clearEntryState_.bind(this)); | 88 this.commitAndClearEntryState_.bind(this)); |
79 }; | 89 }; |
80 | 90 |
81 /** | 91 /** |
82 * The ID of the Braille IME extension built into Chrome OS. | 92 * The ID of the Braille IME extension built into Chrome OS. |
83 * @const {string} | 93 * @const {string} |
84 * @private | 94 * @private |
85 */ | 95 */ |
86 cvox.BrailleInputHandler.IME_EXTENSION_ID_ = | 96 cvox.BrailleInputHandler.IME_EXTENSION_ID_ = |
87 'jddehjeebkoimngcbdkaahpobgicbffp'; | 97 'jddehjeebkoimngcbdkaahpobgicbffp'; |
88 | 98 |
(...skipping 26 matching lines...) Expand all Loading... |
115 */ | 125 */ |
116 init: function() { | 126 init: function() { |
117 chrome.runtime.onConnectExternal.addListener(this.onImeConnect_.bind(this)); | 127 chrome.runtime.onConnectExternal.addListener(this.onImeConnect_.bind(this)); |
118 }, | 128 }, |
119 | 129 |
120 /** | 130 /** |
121 * Called when the content on the braille display is updated. Modifies the | 131 * Called when the content on the braille display is updated. Modifies the |
122 * input state according to the new content. | 132 * input state according to the new content. |
123 * @param {cvox.Spannable} text Text, optionally with value and selection | 133 * @param {cvox.Spannable} text Text, optionally with value and selection |
124 * spans. | 134 * spans. |
| 135 * @param {function()} listener Called when the uncommitted cells |
| 136 * have changed. |
125 */ | 137 */ |
126 onDisplayContentChanged: function(text) { | 138 onDisplayContentChanged: function(text, listener) { |
127 var valueSpan = text.getSpanInstanceOf(cvox.ValueSpan); | 139 var valueSpan = text.getSpanInstanceOf(cvox.ValueSpan); |
128 var selectionSpan = text.getSpanInstanceOf(cvox.ValueSelectionSpan); | 140 var selectionSpan = text.getSpanInstanceOf(cvox.ValueSelectionSpan); |
129 if (!(valueSpan && selectionSpan)) | 141 if (!(valueSpan && selectionSpan)) |
130 return; | 142 return; |
| 143 // Don't call the old listener any further, since new content is being |
| 144 // set. If the old listener is not cleared here, it could be called |
| 145 // spuriously if the entry state is cleared below. |
| 146 this.uncommittedCellsChangedListener_ = null; |
131 // The type casts are ok because the spans are known to exist. | 147 // The type casts are ok because the spans are known to exist. |
132 var valueStart = /** @type {number} */ (text.getSpanStart(valueSpan)); | 148 var valueStart = /** @type {number} */ (text.getSpanStart(valueSpan)); |
133 var valueEnd = /** @type {number} */ (text.getSpanEnd(valueSpan)); | 149 var valueEnd = /** @type {number} */ (text.getSpanEnd(valueSpan)); |
134 var selectionStart = | 150 var selectionStart = |
135 /** @type {number} */ (text.getSpanStart(selectionSpan)); | 151 /** @type {number} */ (text.getSpanStart(selectionSpan)); |
136 var selectionEnd = /** @type {number} */ (text.getSpanEnd(selectionSpan)); | 152 var selectionEnd = /** @type {number} */ (text.getSpanEnd(selectionSpan)); |
137 if (selectionStart < valueStart || selectionEnd > valueEnd) { | 153 if (selectionStart < valueStart || selectionEnd > valueEnd) { |
138 console.error('Selection outside of value in braille content'); | 154 console.error('Selection outside of value in braille content'); |
139 this.clearEntryState_(); | 155 this.clearEntryState_(); |
140 return; | 156 return; |
141 } | 157 } |
142 var newTextBefore = text.toString().substring(valueStart, selectionStart); | 158 var newTextBefore = text.toString().substring(valueStart, selectionStart); |
143 if (this.currentTextBefore_ !== newTextBefore && this.entryState_) | 159 if (this.currentTextBefore_ !== newTextBefore && this.entryState_) |
144 this.entryState_.onTextBeforeChanged(newTextBefore); | 160 this.entryState_.onTextBeforeChanged(newTextBefore); |
145 this.currentTextBefore_ = newTextBefore; | 161 this.currentTextBefore_ = newTextBefore; |
146 this.currentTextAfter_ = text.toString().substring(selectionEnd, valueEnd); | 162 this.currentTextAfter_ = text.toString().substring(selectionEnd, valueEnd); |
| 163 this.uncommittedCellsSpan_ = new cvox.ExtraCellsSpan(); |
| 164 text.setSpan(this.uncommittedCellsSpan_, selectionStart, selectionStart); |
| 165 if (this.entryState_ && this.entryState_.usesUncommittedCells) { |
| 166 this.updateUncommittedCells_( |
| 167 new Uint8Array(this.entryState_.cells_).buffer); |
| 168 } |
| 169 this.uncommittedCellsChangedListener_ = listener; |
147 }, | 170 }, |
148 | 171 |
149 /** | 172 /** |
150 * Handles braille key events used for input by editing the current input | 173 * Handles braille key events used for input by editing the current input |
151 * field appropriately. | 174 * field appropriately. |
152 * @param {!cvox.BrailleKeyEvent} event The key event. | 175 * @param {!cvox.BrailleKeyEvent} event The key event. |
153 * @return {boolean} {@code true} if the event was handled, {@code false} | 176 * @return {boolean} {@code true} if the event was handled, {@code false} |
154 * if it should propagate further. | 177 * if it should propagate further. |
155 */ | 178 */ |
156 onBrailleKeyEvent: function(event) { | 179 onBrailleKeyEvent: function(event) { |
157 if (event.command === cvox.BrailleKeyCommand.DOTS) | 180 if (event.command === cvox.BrailleKeyCommand.DOTS) |
158 return this.onBrailleDots_(/** @type {number} */(event.brailleDots)); | 181 return this.onBrailleDots_(/** @type {number} */(event.brailleDots)); |
159 // Any other braille command cancels the pending cells. | 182 // Any other braille command cancels the pending cells. |
160 this.pendingCells_.length = 0; | 183 this.pendingCells_.length = 0; |
161 if (event.command === cvox.BrailleKeyCommand.STANDARD_KEY) { | 184 if (event.command === cvox.BrailleKeyCommand.STANDARD_KEY) { |
162 if (event.standardKeyCode === 'Backspace' && | 185 if (event.standardKeyCode === 'Backspace' && |
163 !event.altKey && !event.ctrlKey && !event.shiftKey && | 186 !event.altKey && !event.ctrlKey && !event.shiftKey && |
164 this.onBackspace_()) { | 187 this.onBackspace_()) { |
165 return true; | 188 return true; |
166 } else { | 189 } else { |
167 this.clearEntryState_(); | 190 this.commitAndClearEntryState_(); |
168 this.sendKeyEventPair_(event); | 191 this.sendKeyEventPair_(event); |
169 return true; | 192 return true; |
170 } | 193 } |
171 } | 194 } |
172 return false; | 195 return false; |
173 }, | 196 }, |
174 | 197 |
175 /** | 198 /** |
176 * Returns how the value of the currently displayed content should be | 199 * Returns how the value of the currently displayed content should be |
177 * expanded given the current input state. | 200 * expanded given the current input state. |
(...skipping 28 matching lines...) Expand all Loading... |
206 * propagate further. | 229 * propagate further. |
207 * @private | 230 * @private |
208 */ | 231 */ |
209 onBrailleDots_: function(dots) { | 232 onBrailleDots_: function(dots) { |
210 if (!this.imeActive_) { | 233 if (!this.imeActive_) { |
211 this.pendingCells_.push(dots); | 234 this.pendingCells_.push(dots); |
212 return true; | 235 return true; |
213 } | 236 } |
214 if (!this.inputContext_) | 237 if (!this.inputContext_) |
215 return false; | 238 return false; |
216 // Avoid accumulating cells forever when typing without moving the cursor | |
217 // by flushing the input when we see a blank cell. | |
218 // Note that this might switch to contracted if appropriate. | |
219 if (this.entryState_ && this.entryState_.lastCellIsBlank()) | |
220 this.clearEntryState_(); | |
221 if (!this.entryState_) { | 239 if (!this.entryState_) { |
222 this.entryState_ = this.createEntryState_(); | 240 if (!(this.entryState_ = this.createEntryState_())) |
223 if (!this.entryState_) | |
224 return false; | 241 return false; |
225 } | 242 } |
226 this.entryState_.appendCell(dots); | 243 this.entryState_.appendCell(dots); |
227 return true; | 244 return true; |
228 }, | 245 }, |
229 | 246 |
230 /** | 247 /** |
231 * Handles the backspace key by deleting the last typed cell if possible. | 248 * Handles the backspace key by deleting the last typed cell if possible. |
232 * @return {boolean} {@code true} if the event was handled, {@code false} | 249 * @return {boolean} {@code true} if the event was handled, {@code false} |
233 * if it wasn't and should propagate further. | 250 * if it wasn't and should propagate further. |
(...skipping 14 matching lines...) Expand all Loading... |
248 * object, or null if it couldn't be created (e.g. if there's no braille | 265 * object, or null if it couldn't be created (e.g. if there's no braille |
249 * translator available yet). | 266 * translator available yet). |
250 * @private | 267 * @private |
251 */ | 268 */ |
252 createEntryState_: function() { | 269 createEntryState_: function() { |
253 var translator = this.translatorManager_.getDefaultTranslator(); | 270 var translator = this.translatorManager_.getDefaultTranslator(); |
254 if (!translator) | 271 if (!translator) |
255 return null; | 272 return null; |
256 var uncontractedTranslator = | 273 var uncontractedTranslator = |
257 this.translatorManager_.getUncontractedTranslator(); | 274 this.translatorManager_.getUncontractedTranslator(); |
| 275 var constructor = cvox.BrailleInputHandler.EditsEntryState_; |
258 if (uncontractedTranslator) { | 276 if (uncontractedTranslator) { |
259 var textBefore = this.currentTextBefore_; | 277 var textBefore = this.currentTextBefore_; |
260 var textAfter = this.currentTextAfter_; | 278 var textAfter = this.currentTextAfter_; |
261 if (this.inAlwaysUncontractedContext_() || | 279 if (this.inAlwaysUncontractedContext_() || |
262 (cvox.BrailleInputHandler.ENDS_WITH_NON_WHITESPACE_RE_.test( | 280 (cvox.BrailleInputHandler.ENDS_WITH_NON_WHITESPACE_RE_.test( |
263 textBefore)) || | 281 textBefore)) || |
264 (cvox.BrailleInputHandler.STARTS_WITH_NON_WHITESPACE_RE_.test( | 282 (cvox.BrailleInputHandler.STARTS_WITH_NON_WHITESPACE_RE_.test( |
265 textAfter))) { | 283 textAfter))) { |
266 translator = uncontractedTranslator; | 284 translator = uncontractedTranslator; |
| 285 } else { |
| 286 constructor = cvox.BrailleInputHandler.LateCommitEntryState_; |
267 } | 287 } |
268 } | 288 } |
269 | 289 |
270 return new cvox.BrailleInputHandler.EditsEntryState_(this, translator); | 290 return new constructor(this, translator); |
271 }, | 291 }, |
272 | 292 |
273 /** | 293 /** |
| 294 * Commits the current entry state and clears it, if any. |
| 295 * @private |
| 296 */ |
| 297 commitAndClearEntryState_: function() { |
| 298 if (this.entryState_) { |
| 299 this.entryState_.commit(); |
| 300 this.clearEntryState_(); |
| 301 } |
| 302 }, |
| 303 |
| 304 /** |
274 * Clears the current entry state without committing it. | 305 * Clears the current entry state without committing it. |
275 * @private | 306 * @private |
276 */ | 307 */ |
277 clearEntryState_: function() { | 308 clearEntryState_: function() { |
278 if (this.entryState_) { | 309 if (this.entryState_) { |
| 310 if (this.entryState_.usesUncommittedCells) |
| 311 this.updateUncommittedCells_(new ArrayBuffer(0)); |
279 this.entryState_.inputHandler_ = null; | 312 this.entryState_.inputHandler_ = null; |
280 this.entryState_ = null; | 313 this.entryState_ = null; |
281 } | 314 } |
282 }, | 315 }, |
283 | 316 |
284 /** | 317 /** |
| 318 * @param {ArrayBuffer} cells |
| 319 * @private |
| 320 */ |
| 321 updateUncommittedCells_: function(cells) { |
| 322 if (this.uncommittedCellsSpan_) |
| 323 this.uncommittedCellsSpan_.cells = cells; |
| 324 if (this.uncommittedCellsChangedListener_) |
| 325 this.uncommittedCellsChangedListener_(); |
| 326 }, |
| 327 |
| 328 /** |
285 * Called when another extension connects to this extension. Accepts | 329 * Called when another extension connects to this extension. Accepts |
286 * connections from the ChromeOS builtin Braille IME and ignores connections | 330 * connections from the ChromeOS builtin Braille IME and ignores connections |
287 * from other extensions. | 331 * from other extensions. |
288 * @param {Port} port The port used to communicate with the other extension. | 332 * @param {Port} port The port used to communicate with the other extension. |
289 * @private | 333 * @private |
290 */ | 334 */ |
291 onImeConnect_: function(port) { | 335 onImeConnect_: function(port) { |
292 if (port.name !== cvox.BrailleInputHandler.IME_PORT_NAME_ || | 336 if (port.name !== cvox.BrailleInputHandler.IME_PORT_NAME_ || |
293 port.sender.id !== cvox.BrailleInputHandler.IME_EXTENSION_ID_) { | 337 port.sender.id !== cvox.BrailleInputHandler.IME_EXTENSION_ID_) { |
294 return; | 338 return; |
(...skipping 197 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
492 // Delete all previous expected changes and ignore this one. | 536 // Delete all previous expected changes and ignore this one. |
493 this.pendingTextsBefore_.splice(0, i + 1); | 537 this.pendingTextsBefore_.splice(0, i + 1); |
494 return; | 538 return; |
495 } | 539 } |
496 } | 540 } |
497 // There was an actual text change (or cursor movement) that we hadn't | 541 // There was an actual text change (or cursor movement) that we hadn't |
498 // caused ourselves, reset any pending input. | 542 // caused ourselves, reset any pending input. |
499 this.inputHandler_.clearEntryState_(); | 543 this.inputHandler_.clearEntryState_(); |
500 }, | 544 }, |
501 | 545 |
502 /** @return {boolean} */ | 546 /** |
503 lastCellIsBlank: function() { | 547 * Makes sure the current text is permanently added to the edit field. |
504 return this.cells_[this.cells_.length - 1] === 0; | 548 * After this call, this object should be abandoned. |
| 549 */ |
| 550 commit: function() { |
505 }, | 551 }, |
506 | 552 |
507 /** | 553 /** |
| 554 * @return {boolean} true if the entry state uses uncommitted cells. |
| 555 */ |
| 556 get usesUncommittedCells() { |
| 557 return false; |
| 558 }, |
| 559 |
| 560 /** |
508 * Updates the translated text based on the current cells and sends the | 561 * Updates the translated text based on the current cells and sends the |
509 * delta to the IME. | 562 * delta to the IME. |
510 * @private | 563 * @private |
511 */ | 564 */ |
512 updateText_: function() { | 565 updateText_: function() { |
513 var cellsBuffer = new Uint8Array(this.cells_).buffer; | 566 var cellsBuffer = new Uint8Array(this.cells_).buffer; |
| 567 var commit = this.lastCellIsBlank_; |
| 568 if (!commit && this.usesUncommittedCells) |
| 569 this.inputHandler_.updateUncommittedCells_(cellsBuffer); |
514 this.translator_.backTranslate(cellsBuffer, function(result) { | 570 this.translator_.backTranslate(cellsBuffer, function(result) { |
515 if (result === null) { | 571 if (result === null) { |
516 console.error('Error when backtranslating braille cells'); | 572 console.error('Error when backtranslating braille cells'); |
517 return; | 573 return; |
518 } | 574 } |
519 if (!this.inputHandler_) | 575 if (!this.inputHandler_) |
520 return; | 576 return; |
521 this.sendTextChange_(result); | 577 this.sendTextChange_(result); |
522 this.text_ = result; | 578 this.text_ = result; |
| 579 if (commit) |
| 580 this.inputHandler_.commitAndClearEntryState_(); |
523 }.bind(this)); | 581 }.bind(this)); |
524 }, | 582 }, |
525 | 583 |
526 /** | 584 /** |
| 585 * @return {boolean} |
| 586 * @private |
| 587 */ |
| 588 get lastCellIsBlank_() { |
| 589 return this.cells_[this.cells_.length - 1] === 0; |
| 590 }, |
| 591 |
| 592 /** |
527 * Sends new text to the IME. This dhould be overriden by subclasses. | 593 * Sends new text to the IME. This dhould be overriden by subclasses. |
528 * The old text is still available in the {@code text_} property. | 594 * The old text is still available in the {@code text_} property. |
529 * @param {string} newText Text to send. | 595 * @param {string} newText Text to send. |
530 * @private | 596 * @private |
531 */ | 597 */ |
532 sendTextChange_: function(newText) { | 598 sendTextChange_: function(newText) { |
533 } | 599 } |
534 }; | 600 }; |
535 | 601 |
536 /** | 602 /** |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
578 } | 644 } |
579 // Send the replace operation to be performed asynchronously by the IME. | 645 // Send the replace operation to be performed asynchronously by the IME. |
580 this.inputHandler_.postImeMessage_( | 646 this.inputHandler_.postImeMessage_( |
581 {type: 'replaceText', | 647 {type: 'replaceText', |
582 contextID: this.inputHandler_.inputContext_.contextID, | 648 contextID: this.inputHandler_.inputContext_.contextID, |
583 deleteBefore: deleteLength, | 649 deleteBefore: deleteLength, |
584 newText: toInsert}); | 650 newText: toInsert}); |
585 } | 651 } |
586 } | 652 } |
587 }; | 653 }; |
| 654 |
| 655 /** |
| 656 * Entry state that only updates the edit field when a blank cell is entered. |
| 657 * During the input of a single 'word', the uncommitted text is stored by the |
| 658 * IME. |
| 659 * @param {!cvox.BrailleInputHandler} inputHandler |
| 660 * @param {!cvox.LibLouis.Translator} translator |
| 661 * @constructor |
| 662 * @private |
| 663 * @extends {cvox.BrailleInputHandler.EntryState_} |
| 664 */ |
| 665 cvox.BrailleInputHandler.LateCommitEntryState_ = function( |
| 666 inputHandler, translator) { |
| 667 cvox.BrailleInputHandler.EntryState_.call(this, inputHandler, translator); |
| 668 }; |
| 669 |
| 670 cvox.BrailleInputHandler.LateCommitEntryState_.prototype = { |
| 671 __proto__: cvox.BrailleInputHandler.EntryState_.prototype, |
| 672 |
| 673 /** @override */ |
| 674 commit: function() { |
| 675 this.inputHandler_.postImeMessage_( |
| 676 {type: 'commitUncommitted', |
| 677 contextID: this.inputHandler_.inputContext_.contextID}); |
| 678 }, |
| 679 |
| 680 /** @override */ |
| 681 get usesUncommittedCells() { |
| 682 return true; |
| 683 }, |
| 684 |
| 685 /** @override */ |
| 686 sendTextChange_: function(newText) { |
| 687 this.inputHandler_.postImeMessage_( |
| 688 {type: 'setUncommitted', |
| 689 contextID: this.inputHandler_.inputContext_.contextID, |
| 690 text: newText}); |
| 691 } |
| 692 }; |
OLD | NEW |