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 |