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 209 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
220 * @param {!AutomationNode} node | 220 * @param {!AutomationNode} node |
221 * @extends {AutomationEditableText} | 221 * @extends {AutomationEditableText} |
222 */ | 222 */ |
223 function AutomationRichEditableText(node) { | 223 function AutomationRichEditableText(node) { |
224 AutomationEditableText.call(this, node); | 224 AutomationEditableText.call(this, node); |
225 | 225 |
226 var root = this.node_.root; | 226 var root = this.node_.root; |
227 if (!root || !root.anchorObject || !root.focusObject) | 227 if (!root || !root.anchorObject || !root.focusObject) |
228 return; | 228 return; |
229 | 229 |
230 this.line_ = new editing.EditableLine( | 230 this.anchorLine_ = new editing.EditableLine(root.anchorObject, |
dmazzoni
2017/06/19 15:11:51
Is the plan to refactor EditableLine to only take
David Tseng
2017/06/19 18:27:47
No, added comments to clarify. An editable line co
| |
231 root.anchorObject, root.anchorOffset, root.focusObject, root.focusOffset); | 231 root.anchorOffset, |
232 root.anchorObject, | |
233 root.anchorOffset); | |
234 this.focusLine_ = new editing.EditableLine(root.focusObject, | |
235 root.focusOffset, | |
236 root.focusObject, | |
237 root.focusOffset); | |
232 } | 238 } |
233 | 239 |
234 AutomationRichEditableText.prototype = { | 240 AutomationRichEditableText.prototype = { |
235 __proto__: AutomationEditableText.prototype, | 241 __proto__: AutomationEditableText.prototype, |
236 | 242 |
237 /** @override */ | 243 /** @override */ |
238 onUpdate: function() { | 244 onUpdate: function() { |
239 var root = this.node_.root; | 245 var root = this.node_.root; |
240 if (!root.anchorObject || !root.focusObject) | 246 if (!root.anchorObject || !root.focusObject) |
241 return; | 247 return; |
242 | 248 |
249 var anchorLine = new editing.EditableLine(root.anchorObject, | |
250 root.anchorOffset, | |
251 root.anchorObject, | |
252 root.anchorOffset); | |
253 var focusLine = new editing.EditableLine(root.focusObject, | |
254 root.focusOffset, | |
255 root.focusObject, | |
256 root.focusOffset); | |
257 | |
258 var prevAnchorLine = this.anchorLine_; | |
259 var prevFocusLine = this.focusLine_; | |
260 this.anchorLine_ = anchorLine; | |
261 this.focusLine_ = focusLine; | |
262 | |
263 // Compute the current line based upon whether the current selection was | |
264 // extended from anchor or focus. The default behavior is to compute lines | |
265 // via focus. | |
266 var baseLineOnStart = prevFocusLine.strictEquals(focusLine); | |
267 | |
243 var cur = new editing.EditableLine( | 268 var cur = new editing.EditableLine( |
244 root.anchorObject, root.anchorOffset || 0, | 269 root.anchorObject, root.anchorOffset || 0, |
245 root.focusObject, root.focusOffset || 0); | 270 root.focusObject, root.focusOffset || 0, |
246 var prev = this.line_; | 271 baseLineOnStart); |
247 this.line_ = cur; | 272 this.line_ = cur; |
248 | 273 |
249 if (prev.equals(cur)) { | 274 // Selection stayed within the same line(s) and didn't cross into new lines. |
250 // Collapsed cursor. | 275 if (anchorLine.equals(prevAnchorLine) && focusLine.equals(prevFocusLine)) { |
276 // Intra-line changes. | |
251 this.changed(new cvox.TextChangeEvent( | 277 this.changed(new cvox.TextChangeEvent( |
252 cur.text || '', | 278 cur.text || '', |
253 cur.startOffset || 0, | 279 cur.startOffset || 0, |
254 cur.endOffset || 0, | 280 cur.endOffset || 0, |
255 true)); | 281 true)); |
256 this.brailleCurrentRichLine_(); | 282 this.brailleCurrentRichLine_(); |
257 | 283 |
258 // Finally, queue up any text markers/styles at bounds. | 284 // Finally, queue up any text markers/styles at bounds. |
259 var container = cur.startContainer_; | 285 var container = cur.startContainer_; |
260 if (!container) | 286 if (!container) |
(...skipping 27 matching lines...) Expand all Loading... | |
288 | 314 |
289 // Start of the container. | 315 // Start of the container. |
290 if (cur.containerStartOffset == cur.startOffset) | 316 if (cur.containerStartOffset == cur.startOffset) |
291 this.speakTextStyle_(container); | 317 this.speakTextStyle_(container); |
292 else if (cur.containerEndOffset == cur.endOffset) | 318 else if (cur.containerEndOffset == cur.endOffset) |
293 this.speakTextStyle_(container, true); | 319 this.speakTextStyle_(container, true); |
294 | 320 |
295 return; | 321 return; |
296 } | 322 } |
297 | 323 |
298 // Just output the current line. | 324 if (anchorLine.equals(focusLine)) { |
299 cvox.ChromeVox.tts.speak(cur.text, cvox.QueueMode.CATEGORY_FLUSH); | 325 // Current selection is on a new line not part of the previous selection. |
300 this.brailleCurrentRichLine_(); | 326 this.line_ = focusLine; |
327 cvox.ChromeVox.tts.speak(focusLine.text, cvox.QueueMode.CATEGORY_FLUSH); | |
328 this.brailleCurrentRichLine_(); | |
329 } if (prevAnchorLine.equals(anchorLine)) { | |
330 // The selection changed in focus position. | |
331 this.line_ = focusLine; | |
332 cvox.ChromeVox.tts.speak(focusLine.text, cvox.QueueMode.CATEGORY_FLUSH); | |
333 this.brailleCurrentRichLine_(); | |
334 } else if (prevFocusLine.equals(focusLine)) { | |
335 // The selection changed in anchor position. | |
336 this.line_ = anchorLine; | |
337 cvox.ChromeVox.tts.speak(anchorLine.text, cvox.QueueMode.CATEGORY_FLUSH); | |
338 this.brailleCurrentRichLine_(); | |
339 } else { | |
340 // Describe a generic DOM selection. | |
dmazzoni
2017/06/19 15:11:51
Want to put some sort of placeholder here?
Or may
David Tseng
2017/06/19 18:27:47
Removed. We don't want to favor focus but favor th
| |
341 } | |
301 | 342 |
302 // The state in EditableTextBase needs to get updated with the new line | 343 // The state in EditableTextBase needs to get updated with the new line |
303 // contents, so that subsequent intra-line changes get the right state | 344 // contents, so that subsequent intra-line changes get the right state |
304 // transitions. | 345 // transitions. |
305 this.value = cur.text; | 346 this.value = cur.text; |
306 this.start = cur.startOffset; | 347 this.start = cur.startOffset; |
307 this.end = cur.endOffset; | 348 this.end = cur.endOffset; |
308 }, | 349 }, |
309 | 350 |
310 /** | 351 /** |
(...skipping 137 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
448 }; | 489 }; |
449 | 490 |
450 /** | 491 /** |
451 * @private {ChromeVoxStateObserver} | 492 * @private {ChromeVoxStateObserver} |
452 */ | 493 */ |
453 editing.observer_ = new editing.EditingChromeVoxStateObserver(); | 494 editing.observer_ = new editing.EditingChromeVoxStateObserver(); |
454 | 495 |
455 /** | 496 /** |
456 * An EditableLine encapsulates all data concerning a line in the automation | 497 * An EditableLine encapsulates all data concerning a line in the automation |
457 * tree necessary to provide output. | 498 * tree necessary to provide output. |
499 * @param {AutomationNode} startNode | |
500 * @param {number} startIndex | |
501 * @param {AutomationNode} endNode | |
502 * @param {number} endIndex | |
503 * @param {boolean=} opt_baseLineOnStart | |
458 * @constructor | 504 * @constructor |
459 */ | 505 */ |
460 editing.EditableLine = function(startNode, startIndex, endNode, endIndex) { | 506 editing.EditableLine = |
507 function(startNode, startIndex, endNode, endIndex, opt_baseLineOnStart) { | |
461 /** @private {!Cursor} */ | 508 /** @private {!Cursor} */ |
462 this.start_ = new Cursor(startNode, startIndex); | 509 this.start_ = new Cursor(startNode, startIndex); |
463 this.start_ = this.start_.deepEquivalent || this.start_; | 510 this.start_ = this.start_.deepEquivalent || this.start_; |
464 | 511 |
465 /** @private {!Cursor} */ | 512 /** @private {!Cursor} */ |
466 this.end_ = new Cursor(endNode, endIndex); | 513 this.end_ = new Cursor(endNode, endIndex); |
467 this.end_ = this.end_.deepEquivalent || this.end_; | 514 this.end_ = this.end_.deepEquivalent || this.end_; |
468 /** @private {number} */ | 515 /** @private {number} */ |
469 this.localContainerStartOffset_ = startIndex; | 516 this.localContainerStartOffset_ = startIndex; |
470 | 517 |
471 // Computed members. | 518 // Computed members. |
472 /** @private {Spannable} */ | 519 /** @private {Spannable} */ |
473 this.value_; | 520 this.value_; |
474 /** @private {AutomationNode} */ | 521 /** @private {AutomationNode} */ |
475 this.lineStart_; | 522 this.lineStart_; |
476 /** @private {AutomationNode} */ | 523 /** @private {AutomationNode} */ |
477 this.lineEnd_; | 524 this.lineEnd_; |
478 /** @private {AutomationNode|undefined} */ | 525 /** @private {AutomationNode|undefined} */ |
479 this.startContainer_; | 526 this.startContainer_; |
480 /** @private {AutomationNode|undefined} */ | 527 /** @private {AutomationNode|undefined} */ |
481 this.lineStartContainer_; | 528 this.lineStartContainer_; |
482 /** @private {number} */ | 529 /** @private {number} */ |
483 this.localLineStartContainerOffset_ = 0; | 530 this.localLineStartContainerOffset_ = 0; |
531 /** @private {AutomationNode|undefined} */ | |
532 this.lineEndContainer_; | |
533 /** @private {number} */ | |
534 this.localLineEndContainerOffset_ = 0; | |
484 | 535 |
485 this.computeLineData_(); | 536 this.computeLineData_(opt_baseLineOnStart); |
486 }; | 537 }; |
487 | 538 |
488 editing.EditableLine.prototype = { | 539 editing.EditableLine.prototype = { |
489 /** @private */ | 540 /** @private */ |
490 computeLineData_: function() { | 541 computeLineData_: function(opt_baseLineOnStart) { |
542 // Note that we calculate the line based only upon anchor or focus even if | |
543 // they do not fall on the same line. It is up to the caller to specify | |
544 // which end to base this line upon since it requires reasoning about two | |
545 // lines. | |
491 var nameLen = 0; | 546 var nameLen = 0; |
492 if (this.start_.node.name) | 547 var lineBase = opt_baseLineOnStart ? this.start_ : this.end_; |
493 nameLen = this.start_.node.name.length; | 548 var lineExtend = opt_baseLineOnStart ? this.end_ : this.start_; |
494 | 549 |
495 this.value_ = new Spannable(this.start_.node.name || '', this.start_); | 550 if (lineBase.node.name) |
496 if (this.start_.node == this.end_.node) | 551 nameLen = lineBase.node.name.length; |
497 this.value_.setSpan(this.end_, 0, nameLen); | 552 |
553 this.value_ = new Spannable(lineBase.node.name || '', lineBase); | |
554 if (lineBase.node == lineExtend.node) | |
555 this.value_.setSpan(lineExtend, 0, nameLen); | |
498 | 556 |
499 // Initialize defaults. | 557 // Initialize defaults. |
500 this.lineStart_ = this.start_.node; | 558 this.lineStart_ = lineBase.node; |
501 this.lineEnd_ = this.lineStart_; | 559 this.lineEnd_ = this.lineStart_; |
502 this.startContainer_ = this.lineStart_.parent; | 560 this.startContainer_ = this.lineStart_.parent; |
503 this.lineStartContainer_ = this.lineStart_.parent; | 561 this.lineStartContainer_ = this.lineStart_.parent; |
562 this.lineEndContainer_ = this.lineStart_.parent; | |
504 | 563 |
505 // Annotate each chunk with its associated inline text box node. | 564 // Annotate each chunk with its associated inline text box node. |
506 this.value_.setSpan(this.lineStart_, 0, this.lineStart_.name.length); | 565 this.value_.setSpan(this.lineStart_, 0, this.lineStart_.name.length); |
507 | 566 |
508 // If the current selection is not on an inline text box (e.g. an image), | 567 // If the current selection is not on an inline text box (e.g. an image), |
509 // return early here so that the line contents are just the node. This is | 568 // return early here so that the line contents are just the node. This is |
510 // pending the ability to show non-text leaf inline objects. | 569 // pending the ability to show non-text leaf inline objects. |
511 if (this.lineStart_.role != RoleType.INLINE_TEXT_BOX) | 570 if (this.lineStart_.role != RoleType.INLINE_TEXT_BOX) |
512 return; | 571 return; |
513 | 572 |
(...skipping 26 matching lines...) Expand all Loading... | |
540 this.lineEnd_ = lineEnd; | 599 this.lineEnd_ = lineEnd; |
541 if (parents[parents.length - 1] != lineEnd.parent) | 600 if (parents[parents.length - 1] != lineEnd.parent) |
542 parents.push(this.lineEnd_.parent); | 601 parents.push(this.lineEnd_.parent); |
543 | 602 |
544 var annotation = lineEnd; | 603 var annotation = lineEnd; |
545 if (lineEnd == this.end_.node) | 604 if (lineEnd == this.end_.node) |
546 annotation = this.end_; | 605 annotation = this.end_; |
547 | 606 |
548 this.value_.append(new Spannable(lineEnd.name, annotation)); | 607 this.value_.append(new Spannable(lineEnd.name, annotation)); |
549 } | 608 } |
609 this.lineEndContainer_ = this.lineEnd_.parent; | |
550 | 610 |
551 // Finally, annotate with all parent static texts as NodeSpan's so that | 611 // Finally, annotate with all parent static texts as NodeSpan's so that |
552 // braille routing can key properly into the node with an offset. | 612 // braille routing can key properly into the node with an offset. |
553 // Note that both line start and end needs to account for | 613 // Note that both line start and end needs to account for |
554 // potential offsets into the static texts as follows. | 614 // potential offsets into the static texts as follows. |
555 var textCountBeforeLineStart = 0, textCountAfterLineEnd = 0; | 615 var textCountBeforeLineStart = 0, textCountAfterLineEnd = 0; |
556 var finder = this.lineStart_; | 616 var finder = this.lineStart_; |
557 while (finder.previousSibling) { | 617 while (finder.previousSibling) { |
558 finder = finder.previousSibling; | 618 finder = finder.previousSibling; |
559 textCountBeforeLineStart += finder.name.length; | 619 textCountBeforeLineStart += finder.name.length; |
560 } | 620 } |
561 this.localLineStartContainerOffset_ = textCountBeforeLineStart; | 621 this.localLineStartContainerOffset_ = textCountBeforeLineStart; |
562 | 622 |
563 finder = this.lineEnd_; | 623 finder = this.lineEnd_; |
564 while (finder.nextSibling) { | 624 while (finder.nextSibling) { |
565 finder = finder.nextSibling; | 625 finder = finder.nextSibling; |
566 textCountAfterLineEnd += finder.name.length; | 626 textCountAfterLineEnd += finder.name.length; |
567 } | 627 } |
568 | 628 |
629 if (this.lineEndContainer_.name) { | |
630 this.localLineEndContainerOffset_ = | |
631 this.lineEndContainer_.name.length - textCountAfterLineEnd; | |
632 } | |
633 | |
569 var len = 0; | 634 var len = 0; |
570 for (var i = 0; i < parents.length; i++) { | 635 for (var i = 0; i < parents.length; i++) { |
571 var parent = parents[i]; | 636 var parent = parents[i]; |
572 | 637 |
573 if (!parent.name) | 638 if (!parent.name) |
574 continue; | 639 continue; |
575 | 640 |
576 var prevLen = len; | 641 var prevLen = len; |
577 | 642 |
578 var currentLen = parent.name.length; | 643 var currentLen = parent.name.length; |
(...skipping 19 matching lines...) Expand all Loading... | |
598 this.value_.setSpan(parent, prevLen, len); | 663 this.value_.setSpan(parent, prevLen, len); |
599 } catch (e) {} | 664 } catch (e) {} |
600 } | 665 } |
601 }, | 666 }, |
602 | 667 |
603 /** | 668 /** |
604 * Gets the selection offset based on the text content of this line. | 669 * Gets the selection offset based on the text content of this line. |
605 * @return {number} | 670 * @return {number} |
606 */ | 671 */ |
607 get startOffset() { | 672 get startOffset() { |
608 return this.value_.getSpanStart(this.start_) + this.start_.index; | 673 // It is possible that the start cursor points to content before this line |
674 // (e.g. in a multi-line selection). | |
675 try { | |
676 return this.value_.getSpanStart(this.start_) + this.start_.index; | |
677 } catch (e) { | |
678 // When that happens, fall back to the start of this line. | |
679 return 0; | |
680 } | |
609 }, | 681 }, |
610 | 682 |
611 /** | 683 /** |
612 * Gets the selection offset based on the text content of this line. | 684 * Gets the selection offset based on the text content of this line. |
613 * @return {number} | 685 * @return {number} |
614 */ | 686 */ |
615 get endOffset() { | 687 get endOffset() { |
616 return this.value_.getSpanStart(this.end_) + this.end_.index; | 688 try { |
689 return this.value_.getSpanStart(this.end_) + this.end_.index; | |
690 } catch (e) { | |
691 return this.value_.length - 1; | |
692 } | |
617 }, | 693 }, |
618 | 694 |
619 /** | 695 /** |
620 * Gets the selection offset based on the parent's text. | 696 * Gets the selection offset based on the parent's text. |
621 * The parent is expected to be static text. | 697 * The parent is expected to be static text. |
622 * @return {number} | 698 * @return {number} |
623 */ | 699 */ |
624 get localStartOffset() { | 700 get localStartOffset() { |
625 return this.startOffset - this.containerStartOffset; | 701 return this.startOffset - this.containerStartOffset; |
626 }, | 702 }, |
(...skipping 27 matching lines...) Expand all Loading... | |
654 | 730 |
655 /** | 731 /** |
656 * The text content of this line. | 732 * The text content of this line. |
657 * @return {string} The text of this line. | 733 * @return {string} The text of this line. |
658 */ | 734 */ |
659 get text() { | 735 get text() { |
660 return this.value_.toString(); | 736 return this.value_.toString(); |
661 }, | 737 }, |
662 | 738 |
663 /** | 739 /** |
740 * | |
741 */ | |
742 equalsStart: function(otherLine) { | |
743 return otherLine.lineStartContainer_ == this.lineStartContainer_ && | |
744 otherLine.localLineStartContainerOffset_ == | |
745 this.localLineStartContainerOffset_; | |
746 }, | |
747 | |
748 equalsEnd: function(otherLine) { | |
749 return otherLine.lineEndContainer_ == this.lineEndContainer_ && | |
750 otherLine.localLineEndContainerOffset_ == | |
751 this.localLineEndContainerOffset_; | |
752 }, | |
753 | |
754 /** | |
664 * Returns true if |otherLine| surrounds the same line as |this|. Note that | 755 * Returns true if |otherLine| surrounds the same line as |this|. Note that |
665 * the contents of the line might be different. | 756 * the contents of the line might be different. |
666 * @return {boolean} | 757 * @return {boolean} |
667 */ | 758 */ |
668 equals: function(otherLine) { | 759 equals: function(otherLine) { |
669 // Equality is intentionally loose here as any of the state nodes can be | 760 // 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 | 761 // invalidated at any time. We rely upon the start/anchor of the line |
671 // staying the same. | 762 // staying the same. |
672 return otherLine.lineStartContainer_ == this.lineStartContainer_ && | 763 return this.equalsStart(otherLine) || this.equalsEnd(otherLine); |
673 otherLine.localLineStartContainerOffset_ == | 764 }, |
674 this.localLineStartContainerOffset_; | 765 |
766 strictEquals: function(otherLine) { | |
767 return this.equals(otherLine) && | |
768 this.startOffset == otherLine.startOffset && | |
769 this.endOffset == otherLine.endOffset; | |
675 } | 770 } |
676 }; | 771 }; |
677 | 772 |
678 }); | 773 }); |
OLD | NEW |