| OLD | NEW |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 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 Processes events related to editing text and emits the | 6 * @fileoverview Processes events related to editing text and emits the |
| 7 * appropriate spoken and braille feedback. | 7 * appropriate spoken and braille feedback. |
| 8 */ | 8 */ |
| 9 | 9 |
| 10 goog.provide('editing.TextEditHandler'); | 10 goog.provide('editing.TextEditHandler'); |
| (...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 67 /** | 67 /** |
| 68 * A |TextEditHandler| suitable for text fields. | 68 * A |TextEditHandler| suitable for text fields. |
| 69 * @constructor | 69 * @constructor |
| 70 * @param {!AutomationNode} node A node with the role of |textField| | 70 * @param {!AutomationNode} node A node with the role of |textField| |
| 71 * @extends {editing.TextEditHandler} | 71 * @extends {editing.TextEditHandler} |
| 72 */ | 72 */ |
| 73 function TextFieldTextEditHandler(node) { | 73 function TextFieldTextEditHandler(node) { |
| 74 editing.TextEditHandler.call(this, node); | 74 editing.TextEditHandler.call(this, node); |
| 75 | 75 |
| 76 chrome.automation.getDesktop(function(desktop) { | 76 chrome.automation.getDesktop(function(desktop) { |
| 77 var useRichText = editing.useRichText && | 77 var useRichText = editing.useRichText && node.state.richlyEditable; |
| 78 node.state.richlyEditable; | |
| 79 | 78 |
| 80 /** @private {!AutomationEditableText} */ | 79 /** @private {!AutomationEditableText} */ |
| 81 this.editableText_ = useRichText ? | 80 this.editableText_ = useRichText ? new AutomationRichEditableText(node) : |
| 82 new AutomationRichEditableText(node) : new AutomationEditableText(node); | 81 new AutomationEditableText(node); |
| 83 }.bind(this)); | 82 }.bind(this)); |
| 84 } | 83 } |
| 85 | 84 |
| 86 TextFieldTextEditHandler.prototype = { | 85 TextFieldTextEditHandler.prototype = { |
| 87 __proto__: editing.TextEditHandler.prototype, | 86 __proto__: editing.TextEditHandler.prototype, |
| 88 | 87 |
| 89 /** @override */ | 88 /** @override */ |
| 90 onEvent: function(evt) { | 89 onEvent: function(evt) { |
| 91 if (evt.type !== EventType.TEXT_CHANGED && | 90 if (evt.type !== EventType.TEXT_CHANGED && |
| 92 evt.type !== EventType.TEXT_SELECTION_CHANGED && | 91 evt.type !== EventType.TEXT_SELECTION_CHANGED && |
| 93 evt.type !== EventType.VALUE_CHANGED && | 92 evt.type !== EventType.VALUE_CHANGED && evt.type !== EventType.FOCUS) |
| 94 evt.type !== EventType.FOCUS) | |
| 95 return; | 93 return; |
| 96 if (!evt.target.state.focused || | 94 if (!evt.target.state.focused || !evt.target.state.editable || |
| 97 !evt.target.state.editable || | |
| 98 evt.target != this.node_) | 95 evt.target != this.node_) |
| 99 return; | 96 return; |
| 100 | 97 |
| 101 this.editableText_.onUpdate(); | 98 this.editableText_.onUpdate(); |
| 102 }, | 99 }, |
| 103 }; | 100 }; |
| 104 | 101 |
| 105 /** | 102 /** |
| 106 * A |ChromeVoxEditableTextBase| that implements text editing feedback | 103 * A |ChromeVoxEditableTextBase| that implements text editing feedback |
| 107 * for automation tree text fields. | 104 * for automation tree text fields. |
| 108 * @constructor | 105 * @constructor |
| 109 * @param {!AutomationNode} node | 106 * @param {!AutomationNode} node |
| 110 * @extends {cvox.ChromeVoxEditableTextBase} | 107 * @extends {cvox.ChromeVoxEditableTextBase} |
| 111 */ | 108 */ |
| 112 function AutomationEditableText(node) { | 109 function AutomationEditableText(node) { |
| 113 if (!node.state.editable) | 110 if (!node.state.editable) |
| 114 throw Error('Node must have editable state set to true.'); | 111 throw Error('Node must have editable state set to true.'); |
| 115 var start = node.textSelStart; | 112 var start = node.textSelStart; |
| 116 var end = node.textSelEnd; | 113 var end = node.textSelEnd; |
| 117 cvox.ChromeVoxEditableTextBase.call( | 114 cvox.ChromeVoxEditableTextBase.call( |
| 118 this, | 115 this, node.value || '', Math.min(start, end), Math.max(start, end), |
| 119 node.value || '', | 116 node.state[StateType.PROTECTED] /**password*/, cvox.ChromeVox.tts); |
| 120 Math.min(start, end), | |
| 121 Math.max(start, end), | |
| 122 node.state[StateType.PROTECTED] /**password*/, | |
| 123 cvox.ChromeVox.tts); | |
| 124 /** @override */ | 117 /** @override */ |
| 125 this.multiline = node.state[StateType.MULTILINE] || false; | 118 this.multiline = node.state[StateType.MULTILINE] || false; |
| 126 /** @type {!AutomationNode} @private */ | 119 /** @type {!AutomationNode} @private */ |
| 127 this.node_ = node; | 120 this.node_ = node; |
| 128 /** @type {Array<number>} @private */ | 121 /** @type {Array<number>} @private */ |
| 129 this.lineBreaks_ = []; | 122 this.lineBreaks_ = []; |
| 130 } | 123 } |
| 131 | 124 |
| 132 AutomationEditableText.prototype = { | 125 AutomationEditableText.prototype = { |
| 133 __proto__: cvox.ChromeVoxEditableTextBase.prototype, | 126 __proto__: cvox.ChromeVoxEditableTextBase.prototype, |
| 134 | 127 |
| 135 /** | 128 /** |
| 136 * Called when the text field has been updated. | 129 * Called when the text field has been updated. |
| 137 */ | 130 */ |
| 138 onUpdate: function() { | 131 onUpdate: function() { |
| 139 var newValue = this.node_.value || ''; | 132 var newValue = this.node_.value || ''; |
| 140 | 133 |
| 141 if (this.value != newValue) | 134 if (this.value != newValue) |
| 142 this.lineBreaks_ = []; | 135 this.lineBreaks_ = []; |
| 143 | 136 |
| 144 var textChangeEvent = new cvox.TextChangeEvent( | 137 var textChangeEvent = new cvox.TextChangeEvent( |
| 145 newValue, | 138 newValue, this.node_.textSelStart || 0, this.node_.textSelEnd || 0, |
| 146 this.node_.textSelStart || 0, | |
| 147 this.node_.textSelEnd || 0, | |
| 148 true /* triggered by user */); | 139 true /* triggered by user */); |
| 149 this.changed(textChangeEvent); | 140 this.changed(textChangeEvent); |
| 150 this.outputBraille_(); | 141 this.outputBraille_(); |
| 151 }, | 142 }, |
| 152 | 143 |
| 153 /** @override */ | 144 /** @override */ |
| 154 getLineIndex: function(charIndex) { | 145 getLineIndex: function(charIndex) { |
| 155 if (!this.multiline) | 146 if (!this.multiline) |
| 156 return 0; | 147 return 0; |
| 157 var breaks = this.node_.lineBreaks || []; | 148 var breaks = this.node_.lineBreaks || []; |
| (...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 234 AutomationRichEditableText.prototype = { | 225 AutomationRichEditableText.prototype = { |
| 235 __proto__: AutomationEditableText.prototype, | 226 __proto__: AutomationEditableText.prototype, |
| 236 | 227 |
| 237 /** @override */ | 228 /** @override */ |
| 238 onUpdate: function() { | 229 onUpdate: function() { |
| 239 var root = this.node_.root; | 230 var root = this.node_.root; |
| 240 if (!root.anchorObject || !root.focusObject) | 231 if (!root.anchorObject || !root.focusObject) |
| 241 return; | 232 return; |
| 242 | 233 |
| 243 var cur = new editing.EditableLine( | 234 var cur = new editing.EditableLine( |
| 244 root.anchorObject, root.anchorOffset || 0, | 235 root.anchorObject, root.anchorOffset || 0, root.focusObject, |
| 245 root.focusObject, root.focusOffset || 0); | 236 root.focusOffset || 0); |
| 246 var prev = this.line_; | 237 var prev = this.line_; |
| 247 this.line_ = cur; | 238 this.line_ = cur; |
| 248 | 239 |
| 249 if (prev.equals(cur)) { | 240 if (prev.equals(cur)) { |
| 250 // Collapsed cursor. | 241 // Collapsed cursor. |
| 251 this.changed(new cvox.TextChangeEvent( | 242 this.changed(new cvox.TextChangeEvent( |
| 252 cur.text || '', | 243 cur.text || '', cur.startOffset || 0, cur.endOffset || 0, true)); |
| 253 cur.startOffset || 0, | |
| 254 cur.endOffset || 0, | |
| 255 true)); | |
| 256 this.brailleCurrentRichLine_(); | 244 this.brailleCurrentRichLine_(); |
| 257 | 245 |
| 258 // Finally, queue up any text markers/styles at bounds. | 246 // Finally, queue up any text markers/styles at bounds. |
| 259 var container = cur.startContainer_; | 247 var container = cur.startContainer_; |
| 260 if (!container) | 248 if (!container) |
| 261 return; | 249 return; |
| 262 | 250 |
| 263 if (container.markerTypes) { | 251 if (container.markerTypes) { |
| 264 // Only consider markers that start or end at the selection bounds. | 252 // Only consider markers that start or end at the selection bounds. |
| 265 var markerStartIndex = -1, markerEndIndex = -1; | 253 var markerStartIndex = -1, markerEndIndex = -1; |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 317 var msgs = []; | 305 var msgs = []; |
| 318 if (markerType & 1) | 306 if (markerType & 1) |
| 319 msgs.push(opt_end ? 'misspelling_end' : 'misspelling_start'); | 307 msgs.push(opt_end ? 'misspelling_end' : 'misspelling_start'); |
| 320 if (markerType & 2) | 308 if (markerType & 2) |
| 321 msgs.push(opt_end ? 'grammar_end' : 'grammar_start'); | 309 msgs.push(opt_end ? 'grammar_end' : 'grammar_start'); |
| 322 if (markerType & 4) | 310 if (markerType & 4) |
| 323 msgs.push(opt_end ? 'text_match_end' : 'text_match_start'); | 311 msgs.push(opt_end ? 'text_match_end' : 'text_match_start'); |
| 324 | 312 |
| 325 if (msgs.length) { | 313 if (msgs.length) { |
| 326 msgs.forEach(function(msg) { | 314 msgs.forEach(function(msg) { |
| 327 cvox.ChromeVox.tts.speak(Msgs.getMsg(msg), | 315 cvox.ChromeVox.tts.speak( |
| 328 cvox.QueueMode.QUEUE, | 316 Msgs.getMsg(msg), cvox.QueueMode.QUEUE, |
| 329 cvox.AbstractTts.PERSONALITY_ANNOTATION); | 317 cvox.AbstractTts.PERSONALITY_ANNOTATION); |
| 330 }); | 318 }); |
| 331 } | 319 } |
| 332 }, | 320 }, |
| 333 | 321 |
| 334 /** | 322 /** |
| 335 * @param {!AutomationNode} style | 323 * @param {!AutomationNode} style |
| 336 * @param {boolean=} opt_end | 324 * @param {boolean=} opt_end |
| 337 * @private | 325 * @private |
| 338 */ | 326 */ |
| 339 speakTextStyle_: function(style, opt_end) { | 327 speakTextStyle_: function(style, opt_end) { |
| 340 var msgs = []; | 328 var msgs = []; |
| 341 if (style.bold) | 329 if (style.bold) |
| 342 msgs.push(opt_end ? 'bold_end' : 'bold_start'); | 330 msgs.push(opt_end ? 'bold_end' : 'bold_start'); |
| 343 if (style.italic) | 331 if (style.italic) |
| 344 msgs.push(opt_end ? 'italic_end' : 'italic_start'); | 332 msgs.push(opt_end ? 'italic_end' : 'italic_start'); |
| 345 if (style.underline) | 333 if (style.underline) |
| 346 msgs.push(opt_end ? 'underline_end' : 'underline_start'); | 334 msgs.push(opt_end ? 'underline_end' : 'underline_start'); |
| 347 if (style.lineThrough) | 335 if (style.lineThrough) |
| 348 msgs.push(opt_end ? 'line_through_end' : 'line_through_start'); | 336 msgs.push(opt_end ? 'line_through_end' : 'line_through_start'); |
| 349 | 337 |
| 350 if (msgs.length) { | 338 if (msgs.length) { |
| 351 msgs.forEach(function(msg) { | 339 msgs.forEach(function(msg) { |
| 352 cvox.ChromeVox.tts.speak(Msgs.getMsg(msg), | 340 cvox.ChromeVox.tts.speak( |
| 353 cvox.QueueMode.QUEUE, | 341 Msgs.getMsg(msg), cvox.QueueMode.QUEUE, |
| 354 cvox.AbstractTts.PERSONALITY_ANNOTATION); | 342 cvox.AbstractTts.PERSONALITY_ANNOTATION); |
| 355 }); | 343 }); |
| 356 } | 344 } |
| 357 }, | 345 }, |
| 358 | 346 |
| 359 /** @private */ | 347 /** @private */ |
| 360 brailleCurrentRichLine_: function() { | 348 brailleCurrentRichLine_: function() { |
| 361 var cur = this.line_; | 349 var cur = this.line_; |
| 362 var value = cur.value_; | 350 var value = cur.value_; |
| 363 value.setSpan(new cvox.ValueSpan(0), 0, cur.value_.length); | 351 value.setSpan(new cvox.ValueSpan(0), 0, cur.value_.length); |
| 364 value.setSpan( | 352 value.setSpan( |
| 365 new cvox.ValueSelectionSpan(), cur.startOffset, cur.endOffset); | 353 new cvox.ValueSelectionSpan(), cur.startOffset, cur.endOffset); |
| 366 cvox.ChromeVox.braille.write(new cvox.NavBraille( | 354 cvox.ChromeVox.braille.write(new cvox.NavBraille( |
| 367 {text: value, | 355 {text: value, startIndex: cur.startOffset, endIndex: cur.endOffset})); |
| 368 startIndex: cur.startOffset, | |
| 369 endIndex: cur.endOffset})); | |
| 370 }, | 356 }, |
| 371 | 357 |
| 372 /** @override */ | 358 /** @override */ |
| 373 describeSelectionChanged: function(evt) { | 359 describeSelectionChanged: function(evt) { |
| 374 // Ignore end of text announcements. | 360 // Ignore end of text announcements. |
| 375 if ((this.start + 1) == evt.start && evt.start == this.value.length) | 361 if ((this.start + 1) == evt.start && evt.start == this.value.length) |
| 376 return; | 362 return; |
| 377 | 363 |
| 378 cvox.ChromeVoxEditableTextBase.prototype.describeSelectionChanged.call( | 364 cvox.ChromeVoxEditableTextBase.prototype.describeSelectionChanged.call( |
| 379 this, evt); | 365 this, evt); |
| (...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 449 | 435 |
| 450 /** | 436 /** |
| 451 * @private {ChromeVoxStateObserver} | 437 * @private {ChromeVoxStateObserver} |
| 452 */ | 438 */ |
| 453 editing.observer_ = new editing.EditingChromeVoxStateObserver(); | 439 editing.observer_ = new editing.EditingChromeVoxStateObserver(); |
| 454 | 440 |
| 455 /** | 441 /** |
| 456 * An EditableLine encapsulates all data concerning a line in the automation | 442 * An EditableLine encapsulates all data concerning a line in the automation |
| 457 * tree necessary to provide output. | 443 * tree necessary to provide output. |
| 458 * @constructor | 444 * @constructor |
| 459 */ | 445 */ |
| 460 editing.EditableLine = function(startNode, startIndex, endNode, endIndex) { | 446 editing.EditableLine = function(startNode, startIndex, endNode, endIndex) { |
| 461 /** @private {!Cursor} */ | 447 /** @private {!Cursor} */ |
| 462 this.start_ = new Cursor(startNode, startIndex); | 448 this.start_ = new Cursor(startNode, startIndex); |
| 463 this.start_ = this.start_.deepEquivalent || this.start_; | 449 this.start_ = this.start_.deepEquivalent || this.start_; |
| 464 | 450 |
| 465 /** @private {!Cursor} */ | 451 /** @private {!Cursor} */ |
| 466 this.end_ = new Cursor(endNode, endIndex); | 452 this.end_ = new Cursor(endNode, endIndex); |
| 467 this.end_ = this.end_.deepEquivalent || this.end_; | 453 this.end_ = this.end_.deepEquivalent || this.end_; |
| 468 /** @private {number} */ | 454 /** @private {number} */ |
| 469 this.localContainerStartOffset_ = startIndex; | 455 this.localContainerStartOffset_ = startIndex; |
| (...skipping 119 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 589 currentLen -= textCountAfterLineEnd; | 575 currentLen -= textCountAfterLineEnd; |
| 590 | 576 |
| 591 len += currentLen; | 577 len += currentLen; |
| 592 | 578 |
| 593 try { | 579 try { |
| 594 this.value_.setSpan(new Output.NodeSpan(parent, offset), prevLen, len); | 580 this.value_.setSpan(new Output.NodeSpan(parent, offset), prevLen, len); |
| 595 | 581 |
| 596 // Also, annotate this span if it is associated with line containre. | 582 // Also, annotate this span if it is associated with line containre. |
| 597 if (parent == this.startContainer_) | 583 if (parent == this.startContainer_) |
| 598 this.value_.setSpan(parent, prevLen, len); | 584 this.value_.setSpan(parent, prevLen, len); |
| 599 } catch (e) {} | 585 } catch (e) { |
| 586 } |
| 600 } | 587 } |
| 601 }, | 588 }, |
| 602 | 589 |
| 603 /** | 590 /** |
| 604 * Gets the selection offset based on the text content of this line. | 591 * Gets the selection offset based on the text content of this line. |
| 605 * @return {number} | 592 * @return {number} |
| 606 */ | 593 */ |
| 607 get startOffset() { | 594 get startOffset() { |
| 608 return this.value_.getSpanStart(this.start_) + this.start_.index; | 595 return this.value_.getSpanStart(this.start_) + this.start_.index; |
| 609 }, | 596 }, |
| (...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 664 * Returns true if |otherLine| surrounds the same line as |this|. Note that | 651 * Returns true if |otherLine| surrounds the same line as |this|. Note that |
| 665 * the contents of the line might be different. | 652 * the contents of the line might be different. |
| 666 * @return {boolean} | 653 * @return {boolean} |
| 667 */ | 654 */ |
| 668 equals: function(otherLine) { | 655 equals: function(otherLine) { |
| 669 // Equality is intentionally loose here as any of the state nodes can be | 656 // Equality is intentionally loose here as any of the state nodes can be |
| 670 // invalidated at any time. We rely upon the start/anchor of the line | 657 // invalidated at any time. We rely upon the start/anchor of the line |
| 671 // staying the same. | 658 // staying the same. |
| 672 return otherLine.lineStartContainer_ == this.lineStartContainer_ && | 659 return otherLine.lineStartContainer_ == this.lineStartContainer_ && |
| 673 otherLine.localLineStartContainerOffset_ == | 660 otherLine.localLineStartContainerOffset_ == |
| 674 this.localLineStartContainerOffset_; | 661 this.localLineStartContainerOffset_; |
| 675 } | 662 } |
| 676 }; | 663 }; |
| 677 | 664 |
| 678 }); | 665 }); |
| OLD | NEW |