Chromium Code Reviews| 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 |