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 235 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
246 | 246 |
247 var prevAnchorLine = this.anchorLine_; | 247 var prevAnchorLine = this.anchorLine_; |
248 var prevFocusLine = this.focusLine_; | 248 var prevFocusLine = this.focusLine_; |
249 this.anchorLine_ = anchorLine; | 249 this.anchorLine_ = anchorLine; |
250 this.focusLine_ = focusLine; | 250 this.focusLine_ = focusLine; |
251 | 251 |
252 // Compute the current line based upon whether the current selection was | 252 // Compute the current line based upon whether the current selection was |
253 // extended from anchor or focus. The default behavior is to compute lines | 253 // extended from anchor or focus. The default behavior is to compute lines |
254 // via focus. | 254 // via focus. |
255 var baseLineOnStart = prevFocusLine.isSameLineAndSelection(focusLine); | 255 var baseLineOnStart = prevFocusLine.isSameLineAndSelection(focusLine); |
256 var isSameSelection = | |
257 baseLineOnStart && prevAnchorLine.isSameLineAndSelection(anchorLine); | |
256 | 258 |
257 var cur = new editing.EditableLine( | 259 var cur; |
258 root.anchorObject, root.anchorOffset, root.focusObject, | 260 if (isSameSelection && this.line_) { |
259 root.focusOffset, baseLineOnStart); | 261 // Nothing changed, return. |
262 return; | |
263 } else { | |
264 cur = new editing.EditableLine( | |
265 root.anchorObject, root.anchorOffset, root.focusObject, | |
266 root.focusOffset, baseLineOnStart); | |
267 } | |
260 var prev = this.line_; | 268 var prev = this.line_; |
261 this.line_ = cur; | 269 this.line_ = cur; |
262 | 270 |
263 // Selection stayed within the same line(s) and didn't cross into new lines. | 271 // Selection stayed within the same line(s) and didn't cross into new lines. |
264 if (anchorLine.isSameLine(prevAnchorLine) && | 272 if (anchorLine.isSameLine(prevAnchorLine) && |
265 focusLine.isSameLine(prevFocusLine)) { | 273 focusLine.isSameLine(prevFocusLine)) { |
266 // Intra-line changes. | 274 // Intra-line changes. |
267 this.changed(new cvox.TextChangeEvent( | 275 this.changed(new cvox.TextChangeEvent( |
268 cur.text || '', cur.startOffset, cur.endOffset, true)); | 276 cur.text || '', cur.startOffset, cur.endOffset, true)); |
269 this.brailleCurrentRichLine_(); | 277 this.brailleCurrentRichLine_(); |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
310 | 318 |
311 if (cur.text == '') { | 319 if (cur.text == '') { |
312 // This line has no text content. Describe the DOM selection. | 320 // This line has no text content. Describe the DOM selection. |
313 new Output() | 321 new Output() |
314 .withRichSpeechAndBraille( | 322 .withRichSpeechAndBraille( |
315 new Range(cur.start_, cur.end_), | 323 new Range(cur.start_, cur.end_), |
316 new Range(prev.start_, prev.end_), Output.EventType.NAVIGATE) | 324 new Range(prev.start_, prev.end_), Output.EventType.NAVIGATE) |
317 .go(); | 325 .go(); |
318 } else if (!cur.hasCollapsedSelection()) { | 326 } else if (!cur.hasCollapsedSelection()) { |
319 // This is a selection. | 327 // This is a selection. |
320 cvox.ChromeVox.tts.speak(cur.selectedText, cvox.QueueMode.CATEGORY_FLUSH); | 328 |
321 cvox.ChromeVox.tts.speak(Msgs.getMsg('selected'), cvox.QueueMode.QUEUE); | 329 // Speech requires many more states than braille. |
330 // Note that base and extend are used here rather than anchor | |
dmazzoni
2017/07/06 21:47:01
We should use terminology correctly. Currently Bli
David Tseng
2017/07/06 22:01:36
I agree but would also like to land this and the d
| |
331 // and focus as concepts because anchor and focus are absolute | |
332 // while base and extend are relative. | |
333 var curBase = baseLineOnStart ? focusLine : anchorLine; | |
334 var curExtend = baseLineOnStart ? anchorLine : focusLine; | |
335 var text = ''; | |
336 var suffixMsg = ''; | |
337 if (curBase.isBeforeLine(curExtend)) { | |
338 // Forward selection. | |
339 if (prev.isBeforeLine(curBase)) { | |
340 // Wrapped across the baseline. Read out the new selection. | |
341 suffixMsg = 'selected'; | |
342 text = this.getTextSelection_( | |
343 curBase.startContainer_, curBase.localStartOffset, | |
344 curExtend.endContainer_, curExtend.localEndOffset); | |
345 } else { | |
346 if (prev.isBeforeLine(curExtend)) { | |
347 // Grew. | |
348 suffixMsg = 'selected'; | |
349 text = this.getTextSelection_( | |
350 prev.endContainer_, prev.localEndOffset, | |
351 curExtend.endContainer_, curExtend.localEndOffset); | |
352 } else { | |
353 // Shrank. | |
354 suffixMsg = 'unselected'; | |
355 text = this.getTextSelection_( | |
356 curExtend.endContainer_, curExtend.localEndOffset, | |
357 prev.endContainer_, prev.localEndOffset); | |
358 } | |
359 } | |
360 } else { | |
361 // Backward selection. | |
362 if (curBase.isBeforeLine(prev)) { | |
363 // Wrapped across the baseline. Read out the new selection. | |
364 suffixMsg = 'selected'; | |
365 text = this.getTextSelection_( | |
366 curExtend.startContainer_, curExtend.localStartOffset, | |
367 curBase.endContainer_, curBase.localEndOffset); | |
368 } else { | |
369 if (curExtend.isBeforeLine(prev)) { | |
370 // Grew. | |
371 suffixMsg = 'selected'; | |
372 text = this.getTextSelection_( | |
373 curExtend.startContainer_, curExtend.localStartOffset, | |
374 prev.startContainer_, prev.localStartOffset); | |
375 } else { | |
376 // Shrank. | |
377 suffixMsg = 'unselected'; | |
378 text = this.getTextSelection_( | |
379 prev.startContainer_, prev.localStartOffset, | |
380 curExtend.startContainer_, curExtend.localStartOffset); | |
381 } | |
382 } | |
383 } | |
384 | |
385 cvox.ChromeVox.tts.speak(text, cvox.QueueMode.CATEGORY_FLUSH); | |
386 cvox.ChromeVox.tts.speak(Msgs.getMsg(suffixMsg), cvox.QueueMode.QUEUE); | |
322 this.brailleCurrentRichLine_(); | 387 this.brailleCurrentRichLine_(); |
323 } else { | 388 } else { |
324 // Describe the current line. This accounts for previous/current | 389 // Describe the current line. This accounts for previous/current |
325 // selections and picking the line edge boundary that changed (as computed | 390 // selections and picking the line edge boundary that changed (as computed |
326 // above). This is also the code path for describing paste. | 391 // above). This is also the code path for describing paste. |
327 this.speakCurrentRichLine_(prev); | 392 this.speakCurrentRichLine_(prev); |
328 this.brailleCurrentRichLine_(); | 393 this.brailleCurrentRichLine_(); |
329 } | 394 } |
330 | 395 |
331 // The state in EditableTextBase needs to get updated with the new line | 396 // The state in EditableTextBase needs to get updated with the new line |
332 // contents, so that subsequent intra-line changes get the right state | 397 // contents, so that subsequent intra-line changes get the right state |
333 // transitions. | 398 // transitions. |
334 this.value = cur.text; | 399 this.value = cur.text; |
335 this.start = cur.startOffset; | 400 this.start = cur.startOffset; |
336 this.end = cur.endOffset; | 401 this.end = cur.endOffset; |
337 }, | 402 }, |
338 | 403 |
339 /** | 404 /** |
405 * @param {AutomationNode|undefined} startNode | |
406 * @param {number} startOffset | |
407 * @param {AutomationNode|undefined} endNode | |
408 * @param {number} endOffset | |
409 * @return {string} | |
410 */ | |
411 getTextSelection_: function(startNode, startOffset, endNode, endOffset) { | |
412 if (!startNode || !endNode) | |
413 return ''; | |
414 | |
415 if (startNode == endNode) { | |
416 return startNode.name ? startNode.name.substring(startOffset, endOffset) : | |
417 ''; | |
418 } | |
419 | |
420 var text = ''; | |
421 if (startNode.name) | |
422 text = startNode.name.substring(startOffset); | |
423 | |
424 for (var node = startNode; | |
425 (node = AutomationUtil.findNextNode( | |
426 node, Dir.FORWARD, AutomationPredicate.leafOrStaticText)) && | |
427 node != endNode;) { | |
428 // Padding needs to get added to break up speech utterances. | |
429 if (node.name) | |
430 text += ' ' + node.name; | |
431 } | |
432 | |
433 if (endNode.name) | |
434 text += ' ' + endNode.name.substring(0, endOffset); | |
435 return text; | |
436 }, | |
437 | |
438 /** | |
340 * @param {number} markerType | 439 * @param {number} markerType |
341 * @param {boolean=} opt_end | 440 * @param {boolean=} opt_end |
342 * @private | 441 * @private |
343 */ | 442 */ |
344 speakTextMarker_: function(markerType, opt_end) { | 443 speakTextMarker_: function(markerType, opt_end) { |
345 // TODO(dtseng): Plumb through constants to automation. | 444 // TODO(dtseng): Plumb through constants to automation. |
346 var msgs = []; | 445 var msgs = []; |
347 if (markerType & 1) | 446 if (markerType & 1) |
348 msgs.push(opt_end ? 'misspelling_end' : 'misspelling_start'); | 447 msgs.push(opt_end ? 'misspelling_end' : 'misspelling_start'); |
349 if (markerType & 2) | 448 if (markerType & 2) |
(...skipping 186 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
536 startNode, startIndex, endNode, endIndex, opt_baseLineOnStart) { | 635 startNode, startIndex, endNode, endIndex, opt_baseLineOnStart) { |
537 /** @private {!Cursor} */ | 636 /** @private {!Cursor} */ |
538 this.start_ = new Cursor(startNode, startIndex); | 637 this.start_ = new Cursor(startNode, startIndex); |
539 this.start_ = this.start_.deepEquivalent || this.start_; | 638 this.start_ = this.start_.deepEquivalent || this.start_; |
540 | 639 |
541 /** @private {!Cursor} */ | 640 /** @private {!Cursor} */ |
542 this.end_ = new Cursor(endNode, endIndex); | 641 this.end_ = new Cursor(endNode, endIndex); |
543 this.end_ = this.end_.deepEquivalent || this.end_; | 642 this.end_ = this.end_.deepEquivalent || this.end_; |
544 /** @private {number} */ | 643 /** @private {number} */ |
545 this.localContainerStartOffset_ = startIndex; | 644 this.localContainerStartOffset_ = startIndex; |
645 /** @private {number} */ | |
646 this.localContainerEndOffset_ = endIndex; | |
546 | 647 |
547 // Computed members. | 648 // Computed members. |
548 /** @private {Spannable} */ | 649 /** @private {Spannable} */ |
549 this.value_; | 650 this.value_; |
550 /** @private {AutomationNode|undefined} */ | 651 /** @private {AutomationNode|undefined} */ |
551 this.lineStart_; | 652 this.lineStart_; |
552 /** @private {AutomationNode|undefined} */ | 653 /** @private {AutomationNode|undefined} */ |
553 this.lineEnd_; | 654 this.lineEnd_; |
554 /** @private {AutomationNode|undefined} */ | 655 /** @private {AutomationNode|undefined} */ |
555 this.startContainer_; | 656 this.startContainer_; |
(...skipping 20 matching lines...) Expand all Loading... | |
576 var lineBase = opt_baseLineOnStart ? this.start_ : this.end_; | 677 var lineBase = opt_baseLineOnStart ? this.start_ : this.end_; |
577 var lineExtend = opt_baseLineOnStart ? this.end_ : this.start_; | 678 var lineExtend = opt_baseLineOnStart ? this.end_ : this.start_; |
578 | 679 |
579 if (lineBase.node.name) | 680 if (lineBase.node.name) |
580 nameLen = lineBase.node.name.length; | 681 nameLen = lineBase.node.name.length; |
581 | 682 |
582 this.value_ = new Spannable(lineBase.node.name || '', lineBase); | 683 this.value_ = new Spannable(lineBase.node.name || '', lineBase); |
583 if (lineBase.node == lineExtend.node) | 684 if (lineBase.node == lineExtend.node) |
584 this.value_.setSpan(lineExtend, 0, nameLen); | 685 this.value_.setSpan(lineExtend, 0, nameLen); |
585 | 686 |
687 this.startContainer_ = this.start_.node; | |
688 if (this.startContainer_.role == RoleType.INLINE_TEXT_BOX) | |
689 this.startContainer_ = this.startContainer_.parent; | |
690 this.endContainer_ = this.end_.node; | |
691 if (this.endContainer_.role == RoleType.INLINE_TEXT_BOX) | |
692 this.endContainer_ = this.endContainer_.parent; | |
693 | |
586 // Initialize defaults. | 694 // Initialize defaults. |
587 this.lineStart_ = lineBase.node; | 695 this.lineStart_ = lineBase.node; |
588 this.lineEnd_ = this.lineStart_; | 696 this.lineEnd_ = this.lineStart_; |
589 this.startContainer_ = this.lineStart_.parent; | |
590 this.lineStartContainer_ = this.lineStart_.parent; | 697 this.lineStartContainer_ = this.lineStart_.parent; |
591 this.lineEndContainer_ = this.lineStart_.parent; | 698 this.lineEndContainer_ = this.lineStart_.parent; |
592 | 699 |
593 // Annotate each chunk with its associated inline text box node. | 700 // Annotate each chunk with its associated inline text box node. |
594 this.value_.setSpan(this.lineStart_, 0, nameLen); | 701 this.value_.setSpan(this.lineStart_, 0, nameLen); |
595 | 702 |
596 // Also, track the nodes necessary for selection (either their parents, in | 703 // Also, track the nodes necessary for selection (either their parents, in |
597 // the case of inline text boxes, or the node itself). | 704 // the case of inline text boxes, or the node itself). |
598 var parents = [this.startContainer_]; | 705 var parents = [this.startContainer_]; |
599 | 706 |
(...skipping 132 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
732 return this.value_.length; | 839 return this.value_.length; |
733 } | 840 } |
734 }, | 841 }, |
735 | 842 |
736 /** | 843 /** |
737 * Gets the selection offset based on the parent's text. | 844 * Gets the selection offset based on the parent's text. |
738 * The parent is expected to be static text. | 845 * The parent is expected to be static text. |
739 * @return {number} | 846 * @return {number} |
740 */ | 847 */ |
741 get localStartOffset() { | 848 get localStartOffset() { |
742 return this.startOffset - this.containerStartOffset; | 849 return this.localContainerStartOffset_; |
743 }, | 850 }, |
744 | 851 |
745 /** | 852 /** |
746 * Gets the selection offset based on the parent's text. | 853 * Gets the selection offset based on the parent's text. |
747 * The parent is expected to be static text. | 854 * The parent is expected to be static text. |
748 * @return {number} | 855 * @return {number} |
749 */ | 856 */ |
750 get localEndOffset() { | 857 get localEndOffset() { |
751 return this.endOffset - this.containerStartOffset; | 858 return this.localContainerEndOffset_; |
752 }, | 859 }, |
753 | 860 |
754 /** | 861 /** |
755 * Gets the start offset of the container, relative to the line text content. | 862 * Gets the start offset of the container, relative to the line text content. |
756 * The container refers to the static text parenting the inline text box. | 863 * The container refers to the static text parenting the inline text box. |
757 * @return {number} | 864 * @return {number} |
758 */ | 865 */ |
759 get containerStartOffset() { | 866 get containerStartOffset() { |
760 return this.value_.getSpanStart(this.startContainer_); | 867 return this.value_.getSpanStart(this.startContainer_); |
761 }, | 868 }, |
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
808 /** | 915 /** |
809 * Returns true if |otherLine| surrounds the same line as |this| and has the | 916 * Returns true if |otherLine| surrounds the same line as |this| and has the |
810 * same selection. | 917 * same selection. |
811 * @param {editing.EditableLine} otherLine | 918 * @param {editing.EditableLine} otherLine |
812 * @return {boolean} | 919 * @return {boolean} |
813 */ | 920 */ |
814 isSameLineAndSelection: function(otherLine) { | 921 isSameLineAndSelection: function(otherLine) { |
815 return this.isSameLine(otherLine) && | 922 return this.isSameLine(otherLine) && |
816 this.startOffset == otherLine.startOffset && | 923 this.startOffset == otherLine.startOffset && |
817 this.endOffset == otherLine.endOffset; | 924 this.endOffset == otherLine.endOffset; |
925 }, | |
926 | |
927 /** | |
928 * Returns whether this line comes before |otherLine| in document order. | |
929 * @return {boolean} | |
930 */ | |
931 isBeforeLine: function(otherLine) { | |
932 if (this.isSameLine(otherLine) || !this.lineStartContainer_ || | |
933 !otherLine.lineStartContainer_) | |
934 return false; | |
935 return AutomationUtil.getDirection( | |
936 this.lineStartContainer_, otherLine.lineStartContainer_) == | |
937 Dir.FORWARD; | |
818 } | 938 } |
819 }; | 939 }; |
820 | 940 |
821 }); | 941 }); |
OLD | NEW |