| 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 | 244 |
| 257 var value = cur.value_; | 245 var value = cur.value_; |
| 258 value.setSpan(new cvox.ValueSpan(0), 0, cur.value_.length); | 246 value.setSpan(new cvox.ValueSpan(0), 0, cur.value_.length); |
| 259 value.setSpan( | 247 value.setSpan( |
| 260 new cvox.ValueSelectionSpan(), cur.startOffset, cur.endOffset); | 248 new cvox.ValueSelectionSpan(), cur.startOffset, cur.endOffset); |
| 261 cvox.ChromeVox.braille.write(new cvox.NavBraille({text: value, | 249 cvox.ChromeVox.braille.write(new cvox.NavBraille( |
| 262 startIndex: cur.startOffset, | 250 {text: value, startIndex: cur.startOffset, endIndex: cur.endOffset})); |
| 263 endIndex: cur.endOffset})); | |
| 264 | 251 |
| 265 // Finally, queue up any text markers/styles at bounds. | 252 // Finally, queue up any text markers/styles at bounds. |
| 266 var container = cur.lineContainer_; | 253 var container = cur.lineContainer_; |
| 267 if (!container) | 254 if (!container) |
| 268 return; | 255 return; |
| 269 | 256 |
| 270 if (container.markerTypes) { | 257 if (container.markerTypes) { |
| 271 // Only consider markers that start or end at the selection bounds. | 258 // Only consider markers that start or end at the selection bounds. |
| 272 var markerStartIndex = -1, markerEndIndex = -1; | 259 var markerStartIndex = -1, markerEndIndex = -1; |
| 273 var localStartOffset = cur.localStartOffset; | 260 var localStartOffset = cur.localStartOffset; |
| (...skipping 30 matching lines...) Expand all Loading... |
| 304 | 291 |
| 305 // Just output the current line. | 292 // Just output the current line. |
| 306 if (!cur.lineStart_ || !cur.lineEnd_) | 293 if (!cur.lineStart_ || !cur.lineEnd_) |
| 307 return; | 294 return; |
| 308 var prevRange = null; | 295 var prevRange = null; |
| 309 if (prev.lineStart_ && prev.lineEnd_) { | 296 if (prev.lineStart_ && prev.lineEnd_) { |
| 310 prevRange = new Range( | 297 prevRange = new Range( |
| 311 Cursor.fromNode(prev.lineStart_), Cursor.fromNode(prev.lineEnd_)); | 298 Cursor.fromNode(prev.lineStart_), Cursor.fromNode(prev.lineEnd_)); |
| 312 } | 299 } |
| 313 | 300 |
| 314 new Output().withRichSpeechAndBraille(new Range( | 301 new Output() |
| 315 Cursor.fromNode(cur.lineStart_), Cursor.fromNode(cur.lineEnd_)), | 302 .withRichSpeechAndBraille( |
| 316 prevRange, | 303 new Range( |
| 317 Output.EventType.NAVIGATE).go(); | 304 Cursor.fromNode(cur.lineStart_), Cursor.fromNode(cur.lineEnd_)), |
| 305 prevRange, Output.EventType.NAVIGATE) |
| 306 .go(); |
| 318 }, | 307 }, |
| 319 | 308 |
| 320 /** | 309 /** |
| 321 * @param {number} markerType | 310 * @param {number} markerType |
| 322 * @param {boolean=} opt_end | 311 * @param {boolean=} opt_end |
| 323 * @private | 312 * @private |
| 324 */ | 313 */ |
| 325 speakTextMarker_: function(markerType, opt_end) { | 314 speakTextMarker_: function(markerType, opt_end) { |
| 326 // TODO(dtseng): Plumb through constants to automation. | 315 // TODO(dtseng): Plumb through constants to automation. |
| 327 var msgs = []; | 316 var msgs = []; |
| 328 if (markerType & 1) | 317 if (markerType & 1) |
| 329 msgs.push(opt_end ? 'misspelling_end' : 'misspelling_start'); | 318 msgs.push(opt_end ? 'misspelling_end' : 'misspelling_start'); |
| 330 if (markerType & 2) | 319 if (markerType & 2) |
| 331 msgs.push(opt_end ? 'grammar_end' : 'grammar_start'); | 320 msgs.push(opt_end ? 'grammar_end' : 'grammar_start'); |
| 332 if (markerType & 4) | 321 if (markerType & 4) |
| 333 msgs.push(opt_end ? 'text_match_end' : 'text_match_start'); | 322 msgs.push(opt_end ? 'text_match_end' : 'text_match_start'); |
| 334 | 323 |
| 335 if (msgs.length) { | 324 if (msgs.length) { |
| 336 msgs.forEach(function(msg) { | 325 msgs.forEach(function(msg) { |
| 337 cvox.ChromeVox.tts.speak(Msgs.getMsg(msg), | 326 cvox.ChromeVox.tts.speak( |
| 338 cvox.QueueMode.QUEUE, | 327 Msgs.getMsg(msg), cvox.QueueMode.QUEUE, |
| 339 cvox.AbstractTts.PERSONALITY_ANNOTATION); | 328 cvox.AbstractTts.PERSONALITY_ANNOTATION); |
| 340 }); | 329 }); |
| 341 } | 330 } |
| 342 }, | 331 }, |
| 343 | 332 |
| 344 /** | 333 /** |
| 345 * @param {!AutomationNode} style | 334 * @param {!AutomationNode} style |
| 346 * @param {boolean=} opt_end | 335 * @param {boolean=} opt_end |
| 347 * @private | 336 * @private |
| 348 */ | 337 */ |
| 349 speakTextStyle_: function(style, opt_end) { | 338 speakTextStyle_: function(style, opt_end) { |
| 350 var msgs = []; | 339 var msgs = []; |
| 351 if (style.bold) | 340 if (style.bold) |
| 352 msgs.push(opt_end ? 'bold_end' : 'bold_start'); | 341 msgs.push(opt_end ? 'bold_end' : 'bold_start'); |
| 353 if (style.italic) | 342 if (style.italic) |
| 354 msgs.push(opt_end ? 'italic_end' : 'italic_start'); | 343 msgs.push(opt_end ? 'italic_end' : 'italic_start'); |
| 355 if (style.underline) | 344 if (style.underline) |
| 356 msgs.push(opt_end ? 'underline_end' : 'underline_start'); | 345 msgs.push(opt_end ? 'underline_end' : 'underline_start'); |
| 357 if (style.lineThrough) | 346 if (style.lineThrough) |
| 358 msgs.push(opt_end ? 'line_through_end' : 'line_through_start'); | 347 msgs.push(opt_end ? 'line_through_end' : 'line_through_start'); |
| 359 | 348 |
| 360 if (msgs.length) { | 349 if (msgs.length) { |
| 361 msgs.forEach(function(msg) { | 350 msgs.forEach(function(msg) { |
| 362 cvox.ChromeVox.tts.speak(Msgs.getMsg(msg), | 351 cvox.ChromeVox.tts.speak( |
| 363 cvox.QueueMode.QUEUE, | 352 Msgs.getMsg(msg), cvox.QueueMode.QUEUE, |
| 364 cvox.AbstractTts.PERSONALITY_ANNOTATION); | 353 cvox.AbstractTts.PERSONALITY_ANNOTATION); |
| 365 }); | 354 }); |
| 366 } | 355 } |
| 367 }, | 356 }, |
| 368 | 357 |
| 369 /** @override */ | 358 /** @override */ |
| 370 describeSelectionChanged: function(evt) { | 359 describeSelectionChanged: function(evt) { |
| 371 // Ignore end of text announcements. | 360 // Ignore end of text announcements. |
| 372 if ((this.start + 1) == evt.start && evt.start == this.value.length) | 361 if ((this.start + 1) == evt.start && evt.start == this.value.length) |
| 373 return; | 362 return; |
| 374 | 363 |
| (...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 446 | 435 |
| 447 /** | 436 /** |
| 448 * @private {ChromeVoxStateObserver} | 437 * @private {ChromeVoxStateObserver} |
| 449 */ | 438 */ |
| 450 editing.observer_ = new editing.EditingChromeVoxStateObserver(); | 439 editing.observer_ = new editing.EditingChromeVoxStateObserver(); |
| 451 | 440 |
| 452 /** | 441 /** |
| 453 * An EditableLine encapsulates all data concerning a line in the automation | 442 * An EditableLine encapsulates all data concerning a line in the automation |
| 454 * tree necessary to provide output. | 443 * tree necessary to provide output. |
| 455 * @constructor | 444 * @constructor |
| 456 */ | 445 */ |
| 457 editing.EditableLine = function(startNode, startIndex, endNode, endIndex) { | 446 editing.EditableLine = function(startNode, startIndex, endNode, endIndex) { |
| 458 /** @private {!Cursor} */ | 447 /** @private {!Cursor} */ |
| 459 this.start_ = new Cursor(startNode, startIndex); | 448 this.start_ = new Cursor(startNode, startIndex); |
| 460 this.start_ = this.start_.deepEquivalent || this.start_; | 449 this.start_ = this.start_.deepEquivalent || this.start_; |
| 461 | 450 |
| 462 /** @private {!Cursor} */ | 451 /** @private {!Cursor} */ |
| 463 this.end_ = new Cursor(endNode, endIndex); | 452 this.end_ = new Cursor(endNode, endIndex); |
| 464 this.end_ = this.end_.deepEquivalent || this.end_; | 453 this.end_ = this.end_.deepEquivalent || this.end_; |
| 465 /** @private {number} */ | 454 /** @private {number} */ |
| 466 this.localContainerStartOffset_ = startIndex; | 455 this.localContainerStartOffset_ = startIndex; |
| (...skipping 116 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 583 * Returns true if |otherLine| surrounds the same line as |this|. Note that | 572 * Returns true if |otherLine| surrounds the same line as |this|. Note that |
| 584 * the contents of the line might be different. | 573 * the contents of the line might be different. |
| 585 * @return {boolean} | 574 * @return {boolean} |
| 586 */ | 575 */ |
| 587 equals: function(otherLine) { | 576 equals: function(otherLine) { |
| 588 // Equality is intentionally loose here as any of the state nodes can be | 577 // Equality is intentionally loose here as any of the state nodes can be |
| 589 // invalidated at any time. | 578 // invalidated at any time. |
| 590 return (otherLine.lineStart_ == this.lineStart_ && | 579 return (otherLine.lineStart_ == this.lineStart_ && |
| 591 otherLine.lineEnd_ == this.lineEnd_) || | 580 otherLine.lineEnd_ == this.lineEnd_) || |
| 592 (otherLine.lineContainer_ == this.lineContainer_ && | 581 (otherLine.lineContainer_ == this.lineContainer_ && |
| 593 otherLine.containerLineStartOffset == this.containerLineStartOffset); | 582 otherLine.containerLineStartOffset == this.containerLineStartOffset); |
| 594 } | 583 } |
| 595 }; | 584 }; |
| 596 | 585 |
| 597 }); | 586 }); |
| OLD | NEW |