| 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 // TODO(dtseng): base/extent and anchor/focus are mismatched in AX. Swap |
| 331 // once this works. |
| 332 var curBase = baseLineOnStart ? focusLine : anchorLine; |
| 333 var curExtent = baseLineOnStart ? anchorLine : focusLine; |
| 334 var text = ''; |
| 335 var suffixMsg = ''; |
| 336 if (curBase.isBeforeLine(curExtent)) { |
| 337 // Forward selection. |
| 338 if (prev.isBeforeLine(curBase)) { |
| 339 // Wrapped across the baseline. Read out the new selection. |
| 340 suffixMsg = 'selected'; |
| 341 text = this.getTextSelection_( |
| 342 curBase.startContainer_, curBase.localStartOffset, |
| 343 curExtent.endContainer_, curExtent.localEndOffset); |
| 344 } else { |
| 345 if (prev.isBeforeLine(curExtent)) { |
| 346 // Grew. |
| 347 suffixMsg = 'selected'; |
| 348 text = this.getTextSelection_( |
| 349 prev.endContainer_, prev.localEndOffset, |
| 350 curExtent.endContainer_, curExtent.localEndOffset); |
| 351 } else { |
| 352 // Shrank. |
| 353 suffixMsg = 'unselected'; |
| 354 text = this.getTextSelection_( |
| 355 curExtent.endContainer_, curExtent.localEndOffset, |
| 356 prev.endContainer_, prev.localEndOffset); |
| 357 } |
| 358 } |
| 359 } else { |
| 360 // Backward selection. |
| 361 if (curBase.isBeforeLine(prev)) { |
| 362 // Wrapped across the baseline. Read out the new selection. |
| 363 suffixMsg = 'selected'; |
| 364 text = this.getTextSelection_( |
| 365 curExtent.startContainer_, curExtent.localStartOffset, |
| 366 curBase.endContainer_, curBase.localEndOffset); |
| 367 } else { |
| 368 if (curExtent.isBeforeLine(prev)) { |
| 369 // Grew. |
| 370 suffixMsg = 'selected'; |
| 371 text = this.getTextSelection_( |
| 372 curExtent.startContainer_, curExtent.localStartOffset, |
| 373 prev.startContainer_, prev.localStartOffset); |
| 374 } else { |
| 375 // Shrank. |
| 376 suffixMsg = 'unselected'; |
| 377 text = this.getTextSelection_( |
| 378 prev.startContainer_, prev.localStartOffset, |
| 379 curExtent.startContainer_, curExtent.localStartOffset); |
| 380 } |
| 381 } |
| 382 } |
| 383 |
| 384 cvox.ChromeVox.tts.speak(text, cvox.QueueMode.CATEGORY_FLUSH); |
| 385 cvox.ChromeVox.tts.speak(Msgs.getMsg(suffixMsg), cvox.QueueMode.QUEUE); |
| 322 this.brailleCurrentRichLine_(); | 386 this.brailleCurrentRichLine_(); |
| 323 } else { | 387 } else { |
| 324 // Describe the current line. This accounts for previous/current | 388 // Describe the current line. This accounts for previous/current |
| 325 // selections and picking the line edge boundary that changed (as computed | 389 // selections and picking the line edge boundary that changed (as computed |
| 326 // above). This is also the code path for describing paste. | 390 // above). This is also the code path for describing paste. |
| 327 this.speakCurrentRichLine_(prev); | 391 this.speakCurrentRichLine_(prev); |
| 328 this.brailleCurrentRichLine_(); | 392 this.brailleCurrentRichLine_(); |
| 329 } | 393 } |
| 330 | 394 |
| 331 // The state in EditableTextBase needs to get updated with the new line | 395 // The state in EditableTextBase needs to get updated with the new line |
| 332 // contents, so that subsequent intra-line changes get the right state | 396 // contents, so that subsequent intra-line changes get the right state |
| 333 // transitions. | 397 // transitions. |
| 334 this.value = cur.text; | 398 this.value = cur.text; |
| 335 this.start = cur.startOffset; | 399 this.start = cur.startOffset; |
| 336 this.end = cur.endOffset; | 400 this.end = cur.endOffset; |
| 337 }, | 401 }, |
| 338 | 402 |
| 339 /** | 403 /** |
| 404 * @param {AutomationNode|undefined} startNode |
| 405 * @param {number} startOffset |
| 406 * @param {AutomationNode|undefined} endNode |
| 407 * @param {number} endOffset |
| 408 * @return {string} |
| 409 */ |
| 410 getTextSelection_: function(startNode, startOffset, endNode, endOffset) { |
| 411 if (!startNode || !endNode) |
| 412 return ''; |
| 413 |
| 414 if (startNode == endNode) { |
| 415 return startNode.name ? startNode.name.substring(startOffset, endOffset) : |
| 416 ''; |
| 417 } |
| 418 |
| 419 var text = ''; |
| 420 if (startNode.name) |
| 421 text = startNode.name.substring(startOffset); |
| 422 |
| 423 for (var node = startNode; |
| 424 (node = AutomationUtil.findNextNode( |
| 425 node, Dir.FORWARD, AutomationPredicate.leafOrStaticText)) && |
| 426 node != endNode;) { |
| 427 // Padding needs to get added to break up speech utterances. |
| 428 if (node.name) |
| 429 text += ' ' + node.name; |
| 430 } |
| 431 |
| 432 if (endNode.name) |
| 433 text += ' ' + endNode.name.substring(0, endOffset); |
| 434 return text; |
| 435 }, |
| 436 |
| 437 /** |
| 340 * @param {number} markerType | 438 * @param {number} markerType |
| 341 * @param {boolean=} opt_end | 439 * @param {boolean=} opt_end |
| 342 * @private | 440 * @private |
| 343 */ | 441 */ |
| 344 speakTextMarker_: function(markerType, opt_end) { | 442 speakTextMarker_: function(markerType, opt_end) { |
| 345 // TODO(dtseng): Plumb through constants to automation. | 443 // TODO(dtseng): Plumb through constants to automation. |
| 346 var msgs = []; | 444 var msgs = []; |
| 347 if (markerType & 1) | 445 if (markerType & 1) |
| 348 msgs.push(opt_end ? 'misspelling_end' : 'misspelling_start'); | 446 msgs.push(opt_end ? 'misspelling_end' : 'misspelling_start'); |
| 349 if (markerType & 2) | 447 if (markerType & 2) |
| (...skipping 186 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 536 startNode, startIndex, endNode, endIndex, opt_baseLineOnStart) { | 634 startNode, startIndex, endNode, endIndex, opt_baseLineOnStart) { |
| 537 /** @private {!Cursor} */ | 635 /** @private {!Cursor} */ |
| 538 this.start_ = new Cursor(startNode, startIndex); | 636 this.start_ = new Cursor(startNode, startIndex); |
| 539 this.start_ = this.start_.deepEquivalent || this.start_; | 637 this.start_ = this.start_.deepEquivalent || this.start_; |
| 540 | 638 |
| 541 /** @private {!Cursor} */ | 639 /** @private {!Cursor} */ |
| 542 this.end_ = new Cursor(endNode, endIndex); | 640 this.end_ = new Cursor(endNode, endIndex); |
| 543 this.end_ = this.end_.deepEquivalent || this.end_; | 641 this.end_ = this.end_.deepEquivalent || this.end_; |
| 544 /** @private {number} */ | 642 /** @private {number} */ |
| 545 this.localContainerStartOffset_ = startIndex; | 643 this.localContainerStartOffset_ = startIndex; |
| 644 /** @private {number} */ |
| 645 this.localContainerEndOffset_ = endIndex; |
| 546 | 646 |
| 547 // Computed members. | 647 // Computed members. |
| 548 /** @private {Spannable} */ | 648 /** @private {Spannable} */ |
| 549 this.value_; | 649 this.value_; |
| 550 /** @private {AutomationNode|undefined} */ | 650 /** @private {AutomationNode|undefined} */ |
| 551 this.lineStart_; | 651 this.lineStart_; |
| 552 /** @private {AutomationNode|undefined} */ | 652 /** @private {AutomationNode|undefined} */ |
| 553 this.lineEnd_; | 653 this.lineEnd_; |
| 554 /** @private {AutomationNode|undefined} */ | 654 /** @private {AutomationNode|undefined} */ |
| 555 this.startContainer_; | 655 this.startContainer_; |
| (...skipping 20 matching lines...) Expand all Loading... |
| 576 var lineBase = opt_baseLineOnStart ? this.start_ : this.end_; | 676 var lineBase = opt_baseLineOnStart ? this.start_ : this.end_; |
| 577 var lineExtend = opt_baseLineOnStart ? this.end_ : this.start_; | 677 var lineExtend = opt_baseLineOnStart ? this.end_ : this.start_; |
| 578 | 678 |
| 579 if (lineBase.node.name) | 679 if (lineBase.node.name) |
| 580 nameLen = lineBase.node.name.length; | 680 nameLen = lineBase.node.name.length; |
| 581 | 681 |
| 582 this.value_ = new Spannable(lineBase.node.name || '', lineBase); | 682 this.value_ = new Spannable(lineBase.node.name || '', lineBase); |
| 583 if (lineBase.node == lineExtend.node) | 683 if (lineBase.node == lineExtend.node) |
| 584 this.value_.setSpan(lineExtend, 0, nameLen); | 684 this.value_.setSpan(lineExtend, 0, nameLen); |
| 585 | 685 |
| 686 this.startContainer_ = this.start_.node; |
| 687 if (this.startContainer_.role == RoleType.INLINE_TEXT_BOX) |
| 688 this.startContainer_ = this.startContainer_.parent; |
| 689 this.endContainer_ = this.end_.node; |
| 690 if (this.endContainer_.role == RoleType.INLINE_TEXT_BOX) |
| 691 this.endContainer_ = this.endContainer_.parent; |
| 692 |
| 586 // Initialize defaults. | 693 // Initialize defaults. |
| 587 this.lineStart_ = lineBase.node; | 694 this.lineStart_ = lineBase.node; |
| 588 this.lineEnd_ = this.lineStart_; | 695 this.lineEnd_ = this.lineStart_; |
| 589 this.startContainer_ = this.lineStart_.parent; | |
| 590 this.lineStartContainer_ = this.lineStart_.parent; | 696 this.lineStartContainer_ = this.lineStart_.parent; |
| 591 this.lineEndContainer_ = this.lineStart_.parent; | 697 this.lineEndContainer_ = this.lineStart_.parent; |
| 592 | 698 |
| 593 // Annotate each chunk with its associated inline text box node. | 699 // Annotate each chunk with its associated inline text box node. |
| 594 this.value_.setSpan(this.lineStart_, 0, nameLen); | 700 this.value_.setSpan(this.lineStart_, 0, nameLen); |
| 595 | 701 |
| 596 // Also, track the nodes necessary for selection (either their parents, in | 702 // Also, track the nodes necessary for selection (either their parents, in |
| 597 // the case of inline text boxes, or the node itself). | 703 // the case of inline text boxes, or the node itself). |
| 598 var parents = [this.startContainer_]; | 704 var parents = [this.startContainer_]; |
| 599 | 705 |
| (...skipping 132 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 732 return this.value_.length; | 838 return this.value_.length; |
| 733 } | 839 } |
| 734 }, | 840 }, |
| 735 | 841 |
| 736 /** | 842 /** |
| 737 * Gets the selection offset based on the parent's text. | 843 * Gets the selection offset based on the parent's text. |
| 738 * The parent is expected to be static text. | 844 * The parent is expected to be static text. |
| 739 * @return {number} | 845 * @return {number} |
| 740 */ | 846 */ |
| 741 get localStartOffset() { | 847 get localStartOffset() { |
| 742 return this.startOffset - this.containerStartOffset; | 848 return this.localContainerStartOffset_; |
| 743 }, | 849 }, |
| 744 | 850 |
| 745 /** | 851 /** |
| 746 * Gets the selection offset based on the parent's text. | 852 * Gets the selection offset based on the parent's text. |
| 747 * The parent is expected to be static text. | 853 * The parent is expected to be static text. |
| 748 * @return {number} | 854 * @return {number} |
| 749 */ | 855 */ |
| 750 get localEndOffset() { | 856 get localEndOffset() { |
| 751 return this.endOffset - this.containerStartOffset; | 857 return this.localContainerEndOffset_; |
| 752 }, | 858 }, |
| 753 | 859 |
| 754 /** | 860 /** |
| 755 * Gets the start offset of the container, relative to the line text content. | 861 * 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. | 862 * The container refers to the static text parenting the inline text box. |
| 757 * @return {number} | 863 * @return {number} |
| 758 */ | 864 */ |
| 759 get containerStartOffset() { | 865 get containerStartOffset() { |
| 760 return this.value_.getSpanStart(this.startContainer_); | 866 return this.value_.getSpanStart(this.startContainer_); |
| 761 }, | 867 }, |
| (...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 808 /** | 914 /** |
| 809 * Returns true if |otherLine| surrounds the same line as |this| and has the | 915 * Returns true if |otherLine| surrounds the same line as |this| and has the |
| 810 * same selection. | 916 * same selection. |
| 811 * @param {editing.EditableLine} otherLine | 917 * @param {editing.EditableLine} otherLine |
| 812 * @return {boolean} | 918 * @return {boolean} |
| 813 */ | 919 */ |
| 814 isSameLineAndSelection: function(otherLine) { | 920 isSameLineAndSelection: function(otherLine) { |
| 815 return this.isSameLine(otherLine) && | 921 return this.isSameLine(otherLine) && |
| 816 this.startOffset == otherLine.startOffset && | 922 this.startOffset == otherLine.startOffset && |
| 817 this.endOffset == otherLine.endOffset; | 923 this.endOffset == otherLine.endOffset; |
| 924 }, |
| 925 |
| 926 /** |
| 927 * Returns whether this line comes before |otherLine| in document order. |
| 928 * @return {boolean} |
| 929 */ |
| 930 isBeforeLine: function(otherLine) { |
| 931 if (this.isSameLine(otherLine) || !this.lineStartContainer_ || |
| 932 !otherLine.lineStartContainer_) |
| 933 return false; |
| 934 return AutomationUtil.getDirection( |
| 935 this.lineStartContainer_, otherLine.lineStartContainer_) == |
| 936 Dir.FORWARD; |
| 818 } | 937 } |
| 819 }; | 938 }; |
| 820 | 939 |
| 821 }); | 940 }); |
| OLD | NEW |