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 goog.provide('cvox.ChromeVoxEditableTextBase'); | 5 goog.provide('cvox.ChromeVoxEditableTextBase'); |
6 goog.provide('cvox.TextChangeEvent'); | 6 goog.provide('cvox.TextChangeEvent'); |
7 goog.provide('cvox.TypingEcho'); | 7 goog.provide('cvox.TypingEcho'); |
8 | 8 |
9 goog.require('cvox.AbstractTts'); | 9 goog.require('cvox.AbstractTts'); |
10 goog.require('cvox.ChromeVox'); | 10 goog.require('cvox.ChromeVox'); |
(...skipping 257 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
268 return !!ch.match(/^\W$/); | 268 return !!ch.match(/^\W$/); |
269 }; | 269 }; |
270 | 270 |
271 | 271 |
272 /** | 272 /** |
273 * @param {cvox.TextChangeEvent} evt The new text changed event to test. | 273 * @param {cvox.TextChangeEvent} evt The new text changed event to test. |
274 * @return {boolean} True if the event, when compared to the previous text, | 274 * @return {boolean} True if the event, when compared to the previous text, |
275 * should trigger description. | 275 * should trigger description. |
276 */ | 276 */ |
277 cvox.ChromeVoxEditableTextBase.prototype.shouldDescribeChange = function(evt) { | 277 cvox.ChromeVoxEditableTextBase.prototype.shouldDescribeChange = function(evt) { |
278 if (evt.value == this.value && | 278 if (evt.value == this.value && evt.start == this.start && |
279 evt.start == this.start && | |
280 evt.end == this.end) { | 279 evt.end == this.end) { |
281 return false; | 280 return false; |
282 } | 281 } |
283 return true; | 282 return true; |
284 }; | 283 }; |
285 | 284 |
286 | 285 |
287 /** | 286 /** |
288 * Speak text, but if it's a single character, describe the character. | 287 * Speak text, but if it's a single character, describe the character. |
289 * @param {string} str The string to speak. | 288 * @param {string} str The string to speak. |
290 * @param {boolean=} opt_triggeredByUser True if the speech was triggered by a | 289 * @param {boolean=} opt_triggeredByUser True if the speech was triggered by a |
291 * user action. | 290 * user action. |
292 * @param {Object=} opt_personality Personality used to speak text. | 291 * @param {Object=} opt_personality Personality used to speak text. |
293 */ | 292 */ |
294 cvox.ChromeVoxEditableTextBase.prototype.speak = | 293 cvox.ChromeVoxEditableTextBase.prototype.speak = function( |
295 function(str, opt_triggeredByUser, opt_personality) { | 294 str, opt_triggeredByUser, opt_personality) { |
296 if (!str) { | 295 if (!str) { |
297 return; | 296 return; |
298 } | 297 } |
299 var queueMode = cvox.QueueMode.QUEUE; | 298 var queueMode = cvox.QueueMode.QUEUE; |
300 if (opt_triggeredByUser === true) { | 299 if (opt_triggeredByUser === true) { |
301 queueMode = cvox.QueueMode.FLUSH; | 300 queueMode = cvox.QueueMode.FLUSH; |
302 } | 301 } |
303 this.tts.speak(str, queueMode, opt_personality || {}); | 302 this.tts.speak(str, queueMode, opt_personality || {}); |
304 }; | 303 }; |
305 | 304 |
(...skipping 23 matching lines...) Expand all Loading... |
329 this.start = evt.start; | 328 this.start = evt.start; |
330 this.end = evt.end; | 329 this.end = evt.end; |
331 }; | 330 }; |
332 | 331 |
333 | 332 |
334 /** | 333 /** |
335 * Describe a change in the selection or cursor position when the text | 334 * Describe a change in the selection or cursor position when the text |
336 * stays the same. | 335 * stays the same. |
337 * @param {cvox.TextChangeEvent} evt The text change event. | 336 * @param {cvox.TextChangeEvent} evt The text change event. |
338 */ | 337 */ |
339 cvox.ChromeVoxEditableTextBase.prototype.describeSelectionChanged = | 338 cvox.ChromeVoxEditableTextBase.prototype.describeSelectionChanged = function( |
340 function(evt) { | 339 evt) { |
341 // TODO(deboer): Factor this into two function: | 340 // TODO(deboer): Factor this into two function: |
342 // - one to determine the selection event | 341 // - one to determine the selection event |
343 // - one to speak | 342 // - one to speak |
344 | 343 |
345 if (this.isPassword) { | 344 if (this.isPassword) { |
346 this.speak((new goog.i18n.MessageFormat(Msgs.getMsg('bullet')) | 345 this.speak( |
347 .format({'COUNT': 1})), evt.triggeredByUser); | 346 (new goog.i18n.MessageFormat(Msgs.getMsg('bullet')).format({ |
| 347 'COUNT': 1 |
| 348 })), |
| 349 evt.triggeredByUser); |
348 return; | 350 return; |
349 } | 351 } |
350 if (evt.start == evt.end) { | 352 if (evt.start == evt.end) { |
351 // It's currently a cursor. | 353 // It's currently a cursor. |
352 if (this.start != this.end) { | 354 if (this.start != this.end) { |
353 // It was previously a selection, so just announce 'unselected'. | 355 // It was previously a selection, so just announce 'unselected'. |
354 this.speak(Msgs.getMsg('Unselected'), evt.triggeredByUser); | 356 this.speak(Msgs.getMsg('Unselected'), evt.triggeredByUser); |
355 } else if (this.getLineIndex(this.start) != | 357 } else if (this.getLineIndex(this.start) != this.getLineIndex(evt.start)) { |
356 this.getLineIndex(evt.start)) { | |
357 // Moved to a different line; read it. | 358 // Moved to a different line; read it. |
358 var lineValue = this.getLine(this.getLineIndex(evt.start)); | 359 var lineValue = this.getLine(this.getLineIndex(evt.start)); |
359 if (lineValue == '') { | 360 if (lineValue == '') { |
360 lineValue = Msgs.getMsg('text_box_blank'); | 361 lineValue = Msgs.getMsg('text_box_blank'); |
361 } else if (lineValue == '\n') { | 362 } else if (lineValue == '\n') { |
362 // Pass through the literal line value so character outputs 'new line'. | 363 // Pass through the literal line value so character outputs 'new line'. |
363 } else if (/^\s+$/.test(lineValue)) { | 364 } else if (/^\s+$/.test(lineValue)) { |
364 lineValue = Msgs.getMsg('text_box_whitespace'); | 365 lineValue = Msgs.getMsg('text_box_whitespace'); |
365 } | 366 } |
366 this.speak(lineValue, evt.triggeredByUser); | 367 this.speak(lineValue, evt.triggeredByUser); |
367 } else if (this.start == evt.start + 1 || | 368 } else if (this.start == evt.start + 1 || this.start == evt.start - 1) { |
368 this.start == evt.start - 1) { | |
369 // Moved by one character; read it. | 369 // Moved by one character; read it. |
370 if (!cvox.ChromeVoxEditableTextBase.useIBeamCursor) { | 370 if (!cvox.ChromeVoxEditableTextBase.useIBeamCursor) { |
371 if (evt.start == this.value.length) { | 371 if (evt.start == this.value.length) { |
372 if (cvox.ChromeVox.verbosity == cvox.VERBOSITY_VERBOSE) { | 372 if (cvox.ChromeVox.verbosity == cvox.VERBOSITY_VERBOSE) { |
373 this.speak(Msgs.getMsg('end_of_text_verbose'), | 373 this.speak(Msgs.getMsg('end_of_text_verbose'), evt.triggeredByUser); |
374 evt.triggeredByUser); | |
375 } else { | 374 } else { |
376 this.speak(Msgs.getMsg('end_of_text_brief'), | 375 this.speak(Msgs.getMsg('end_of_text_brief'), evt.triggeredByUser); |
377 evt.triggeredByUser); | |
378 } | 376 } |
379 } else { | 377 } else { |
380 this.speak(this.value.substr(evt.start, 1), | 378 this.speak( |
381 evt.triggeredByUser, | 379 this.value.substr(evt.start, 1), evt.triggeredByUser, |
382 {'phoneticCharacters': evt.triggeredByUser}); | 380 {'phoneticCharacters': evt.triggeredByUser}); |
383 } | 381 } |
384 } else { | 382 } else { |
385 this.speak(this.value.substr(Math.min(this.start, evt.start), 1), | 383 this.speak( |
386 evt.triggeredByUser, | 384 this.value.substr(Math.min(this.start, evt.start), 1), |
387 {'phoneticCharacters': evt.triggeredByUser}); | 385 evt.triggeredByUser, {'phoneticCharacters': evt.triggeredByUser}); |
388 } | 386 } |
389 } else { | 387 } else { |
390 // Moved by more than one character. Read all characters crossed. | 388 // Moved by more than one character. Read all characters crossed. |
391 this.speak(this.value.substr(Math.min(this.start, evt.start), | 389 this.speak( |
392 Math.abs(this.start - evt.start)), evt.triggeredByUser); | 390 this.value.substr( |
| 391 Math.min(this.start, evt.start), |
| 392 Math.abs(this.start - evt.start)), |
| 393 evt.triggeredByUser); |
393 } | 394 } |
394 } else { | 395 } else { |
395 // It's currently a selection. | 396 // It's currently a selection. |
396 if (this.start + 1 == evt.start && | 397 if (this.start + 1 == evt.start && this.end == this.value.length && |
397 this.end == this.value.length && | |
398 evt.end == this.value.length) { | 398 evt.end == this.value.length) { |
399 // Autocomplete: the user typed one character of autocompleted text. | 399 // Autocomplete: the user typed one character of autocompleted text. |
400 this.speak(this.value.substr(this.start, 1), evt.triggeredByUser); | 400 this.speak(this.value.substr(this.start, 1), evt.triggeredByUser); |
401 this.speak(this.value.substr(evt.start)); | 401 this.speak(this.value.substr(evt.start)); |
402 } else if (this.start == this.end) { | 402 } else if (this.start == this.end) { |
403 // It was previously a cursor. | 403 // It was previously a cursor. |
404 this.speak(this.value.substr(evt.start, evt.end - evt.start), | 404 this.speak( |
405 evt.triggeredByUser); | 405 this.value.substr(evt.start, evt.end - evt.start), |
| 406 evt.triggeredByUser); |
406 this.speak(Msgs.getMsg('selected')); | 407 this.speak(Msgs.getMsg('selected')); |
407 } else if (this.start == evt.start && this.end < evt.end) { | 408 } else if (this.start == evt.start && this.end < evt.end) { |
408 this.speak(this.value.substr(this.end, evt.end - this.end), | 409 this.speak( |
409 evt.triggeredByUser); | 410 this.value.substr(this.end, evt.end - this.end), evt.triggeredByUser); |
410 this.speak(Msgs.getMsg('added_to_selection')); | 411 this.speak(Msgs.getMsg('added_to_selection')); |
411 } else if (this.start == evt.start && this.end > evt.end) { | 412 } else if (this.start == evt.start && this.end > evt.end) { |
412 this.speak(this.value.substr(evt.end, this.end - evt.end), | 413 this.speak( |
413 evt.triggeredByUser); | 414 this.value.substr(evt.end, this.end - evt.end), evt.triggeredByUser); |
414 this.speak(Msgs.getMsg('removed_from_selection')); | 415 this.speak(Msgs.getMsg('removed_from_selection')); |
415 } else if (this.end == evt.end && this.start > evt.start) { | 416 } else if (this.end == evt.end && this.start > evt.start) { |
416 this.speak(this.value.substr(evt.start, this.start - evt.start), | 417 this.speak( |
417 evt.triggeredByUser); | 418 this.value.substr(evt.start, this.start - evt.start), |
| 419 evt.triggeredByUser); |
418 this.speak(Msgs.getMsg('added_to_selection')); | 420 this.speak(Msgs.getMsg('added_to_selection')); |
419 } else if (this.end == evt.end && this.start < evt.start) { | 421 } else if (this.end == evt.end && this.start < evt.start) { |
420 this.speak(this.value.substr(this.start, evt.start - this.start), | 422 this.speak( |
421 evt.triggeredByUser); | 423 this.value.substr(this.start, evt.start - this.start), |
| 424 evt.triggeredByUser); |
422 this.speak(Msgs.getMsg('removed_from_selection')); | 425 this.speak(Msgs.getMsg('removed_from_selection')); |
423 } else { | 426 } else { |
424 // The selection changed but it wasn't an obvious extension of | 427 // The selection changed but it wasn't an obvious extension of |
425 // a previous selection. Just read the new selection. | 428 // a previous selection. Just read the new selection. |
426 this.speak(this.value.substr(evt.start, evt.end - evt.start), | 429 this.speak( |
427 evt.triggeredByUser); | 430 this.value.substr(evt.start, evt.end - evt.start), |
| 431 evt.triggeredByUser); |
428 this.speak(Msgs.getMsg('selected')); | 432 this.speak(Msgs.getMsg('selected')); |
429 } | 433 } |
430 } | 434 } |
431 }; | 435 }; |
432 | 436 |
433 | 437 |
434 /** | 438 /** |
435 * Describe a change where the text changes. | 439 * Describe a change where the text changes. |
436 * @param {cvox.TextChangeEvent} evt The text change event. | 440 * @param {cvox.TextChangeEvent} evt The text change event. |
437 */ | 441 */ |
438 cvox.ChromeVoxEditableTextBase.prototype.describeTextChanged = function(evt) { | 442 cvox.ChromeVoxEditableTextBase.prototype.describeTextChanged = function(evt) { |
439 var personality = {}; | 443 var personality = {}; |
440 if (evt.value.length < this.value.length) { | 444 if (evt.value.length < this.value.length) { |
441 personality = cvox.AbstractTts.PERSONALITY_DELETED; | 445 personality = cvox.AbstractTts.PERSONALITY_DELETED; |
442 } | 446 } |
443 if (this.isPassword) { | 447 if (this.isPassword) { |
444 this.speak((new goog.i18n.MessageFormat(Msgs.getMsg('bullet')) | 448 this.speak( |
445 .format({'COUNT': 1})), evt.triggeredByUser, personality); | 449 (new goog.i18n.MessageFormat(Msgs.getMsg('bullet')).format({ |
| 450 'COUNT': 1 |
| 451 })), |
| 452 evt.triggeredByUser, personality); |
446 return; | 453 return; |
447 } | 454 } |
448 | 455 |
449 var value = this.value; | 456 var value = this.value; |
450 var len = value.length; | 457 var len = value.length; |
451 var newLen = evt.value.length; | 458 var newLen = evt.value.length; |
452 var autocompleteSuffix = ''; | 459 var autocompleteSuffix = ''; |
453 // Make a copy of evtValue and evtEnd to avoid changing anything in | 460 // Make a copy of evtValue and evtEnd to avoid changing anything in |
454 // the event itself. | 461 // the event itself. |
455 var evtValue = evt.value; | 462 var evtValue = evt.value; |
(...skipping 15 matching lines...) Expand all Loading... |
471 var suffixLen = len - this.end; | 478 var suffixLen = len - this.end; |
472 if (newLen >= prefixLen + suffixLen + (evtEnd - evt.start) && | 479 if (newLen >= prefixLen + suffixLen + (evtEnd - evt.start) && |
473 evtValue.substr(0, prefixLen) == value.substr(0, prefixLen) && | 480 evtValue.substr(0, prefixLen) == value.substr(0, prefixLen) && |
474 evtValue.substr(newLen - suffixLen) == value.substr(this.end)) { | 481 evtValue.substr(newLen - suffixLen) == value.substr(this.end)) { |
475 // However, in a dynamic content editable, defer to authoritative events | 482 // However, in a dynamic content editable, defer to authoritative events |
476 // (clipboard, key press) to reduce guess work when observing insertions. | 483 // (clipboard, key press) to reduce guess work when observing insertions. |
477 // Only use this logic when observing deletions (and insertion of word | 484 // Only use this logic when observing deletions (and insertion of word |
478 // breakers). | 485 // breakers). |
479 // TODO(dtseng): Think about a more reliable way to do this. | 486 // TODO(dtseng): Think about a more reliable way to do this. |
480 if (!(cvox.ChromeVoxEditableContentEditable && | 487 if (!(cvox.ChromeVoxEditableContentEditable && |
481 this instanceof cvox.ChromeVoxEditableContentEditable) || | 488 this instanceof cvox.ChromeVoxEditableContentEditable) || |
482 newLen < len || | 489 newLen < len || this.isWordBreakChar(evt.value[newLen - 1] || '')) { |
483 this.isWordBreakChar(evt.value[newLen - 1] || '')) { | |
484 this.describeTextChangedHelper( | 490 this.describeTextChangedHelper( |
485 evt, prefixLen, suffixLen, autocompleteSuffix, personality); | 491 evt, prefixLen, suffixLen, autocompleteSuffix, personality); |
486 } | 492 } |
487 return; | 493 return; |
488 } | 494 } |
489 | 495 |
490 // Next, see if one or more characters were deleted from the previous | 496 // Next, see if one or more characters were deleted from the previous |
491 // cursor position and the new cursor is in the expected place. This | 497 // cursor position and the new cursor is in the expected place. This |
492 // handles backspace, forward-delete, and similar shortcuts that delete | 498 // handles backspace, forward-delete, and similar shortcuts that delete |
493 // a word or line. | 499 // a word or line. |
494 prefixLen = evt.start; | 500 prefixLen = evt.start; |
495 suffixLen = newLen - evtEnd; | 501 suffixLen = newLen - evtEnd; |
496 if (this.start == this.end && | 502 if (this.start == this.end && evt.start == evtEnd && |
497 evt.start == evtEnd && | |
498 evtValue.substr(0, prefixLen) == value.substr(0, prefixLen) && | 503 evtValue.substr(0, prefixLen) == value.substr(0, prefixLen) && |
499 evtValue.substr(newLen - suffixLen) == | 504 evtValue.substr(newLen - suffixLen) == value.substr(len - suffixLen)) { |
500 value.substr(len - suffixLen)) { | |
501 // Forward deletions causes reading of the character immediately to the | 505 // Forward deletions causes reading of the character immediately to the |
502 // right of the caret or the deleted text depending on the iBeam cursor | 506 // right of the caret or the deleted text depending on the iBeam cursor |
503 // setting. | 507 // setting. |
504 if (this.start == evt.start && | 508 if (this.start == evt.start && this.end == evt.end && |
505 this.end == evt.end && | |
506 !cvox.ChromeVoxEditableTextBase.useIBeamCursor) { | 509 !cvox.ChromeVoxEditableTextBase.useIBeamCursor) { |
507 this.speak(evt.value[evt.start], evt.triggeredByUser); | 510 this.speak(evt.value[evt.start], evt.triggeredByUser); |
508 } else { | 511 } else { |
509 this.describeTextChangedHelper( | 512 this.describeTextChangedHelper( |
510 evt, prefixLen, suffixLen, autocompleteSuffix, personality); | 513 evt, prefixLen, suffixLen, autocompleteSuffix, personality); |
511 } | 514 } |
512 return; | 515 return; |
513 } | 516 } |
514 | 517 |
515 // If all else fails, we assume the change was not the result of a normal | 518 // If all else fails, we assume the change was not the result of a normal |
516 // user editing operation, so we'll have to speak feedback based only | 519 // user editing operation, so we'll have to speak feedback based only |
517 // on the changes to the text, not the cursor position / selection. | 520 // on the changes to the text, not the cursor position / selection. |
518 // First, restore the autocomplete text if any. | 521 // First, restore the autocomplete text if any. |
519 evtValue += autocompleteSuffix; | 522 evtValue += autocompleteSuffix; |
520 | 523 |
521 // Try to do a diff between the new and the old text. If it is a one character | 524 // Try to do a diff between the new and the old text. If it is a one character |
522 // insertion/deletion at the start or at the end, just speak that character. | 525 // insertion/deletion at the start or at the end, just speak that character. |
523 if ((evtValue.length == (value.length + 1)) || | 526 if ((evtValue.length == (value.length + 1)) || |
524 ((evtValue.length + 1) == value.length)) { | 527 ((evtValue.length + 1) == value.length)) { |
525 // The user added text either to the beginning or the end. | 528 // The user added text either to the beginning or the end. |
526 if (evtValue.length > value.length) { | 529 if (evtValue.length > value.length) { |
527 if (evtValue.startsWith(value)) { | 530 if (evtValue.startsWith(value)) { |
528 this.speak(evtValue[evtValue.length - 1], evt.triggeredByUser, | 531 this.speak( |
529 personality); | 532 evtValue[evtValue.length - 1], evt.triggeredByUser, personality); |
530 return; | 533 return; |
531 } else if (evtValue.indexOf(value) == 1) { | 534 } else if (evtValue.indexOf(value) == 1) { |
532 this.speak(evtValue[0], evt.triggeredByUser, personality); | 535 this.speak(evtValue[0], evt.triggeredByUser, personality); |
533 return; | 536 return; |
534 } | 537 } |
535 } | 538 } |
536 // The user deleted text either from the beginning or the end. | 539 // The user deleted text either from the beginning or the end. |
537 if (evtValue.length < value.length) { | 540 if (evtValue.length < value.length) { |
538 if (value.startsWith(evtValue)) { | 541 if (value.startsWith(evtValue)) { |
539 this.speak(value[value.length - 1], evt.triggeredByUser, personality); | 542 this.speak(value[value.length - 1], evt.triggeredByUser, personality); |
540 return; | 543 return; |
541 } else if (value.indexOf(evtValue) == 1) { | 544 } else if (value.indexOf(evtValue) == 1) { |
542 this.speak(value[0], evt.triggeredByUser, personality); | 545 this.speak(value[0], evt.triggeredByUser, personality); |
543 return; | 546 return; |
544 } | 547 } |
545 } | 548 } |
546 } | 549 } |
547 | 550 |
548 if (this.multiline) { | 551 if (this.multiline) { |
549 // Fall back to announce deleted but omit the text that was deleted. | 552 // Fall back to announce deleted but omit the text that was deleted. |
550 if (evt.value.length < this.value.length) { | 553 if (evt.value.length < this.value.length) { |
551 this.speak(Msgs.getMsg('text_deleted'), | 554 this.speak(Msgs.getMsg('text_deleted'), evt.triggeredByUser, personality); |
552 evt.triggeredByUser, personality); | |
553 } | 555 } |
554 // The below is a somewhat loose way to deal with non-standard | 556 // The below is a somewhat loose way to deal with non-standard |
555 // insertions/deletions. Intentionally skip for multiline since deletion | 557 // insertions/deletions. Intentionally skip for multiline since deletion |
556 // announcements are covered above and insertions are non-standard (possibly | 558 // announcements are covered above and insertions are non-standard (possibly |
557 // due to auto complete). Since content editable's often refresh content by | 559 // due to auto complete). Since content editable's often refresh content by |
558 // removing and inserting entire chunks of text, this type of logic often | 560 // removing and inserting entire chunks of text, this type of logic often |
559 // results in unintended consequences such as reading all text when only one | 561 // results in unintended consequences such as reading all text when only one |
560 // character has been entered. | 562 // character has been entered. |
561 return; | 563 return; |
562 } | 564 } |
563 | 565 |
564 // If the text is short, just speak the whole thing. | 566 // If the text is short, just speak the whole thing. |
565 if (newLen <= this.maxShortPhraseLen) { | 567 if (newLen <= this.maxShortPhraseLen) { |
566 this.describeTextChangedHelper(evt, 0, 0, '', personality); | 568 this.describeTextChangedHelper(evt, 0, 0, '', personality); |
567 return; | 569 return; |
568 } | 570 } |
569 | 571 |
570 // Otherwise, look for the common prefix and suffix, but back up so | 572 // Otherwise, look for the common prefix and suffix, but back up so |
571 // that we can speak complete words, to be minimally confusing. | 573 // that we can speak complete words, to be minimally confusing. |
572 prefixLen = 0; | 574 prefixLen = 0; |
573 while (prefixLen < len && | 575 while (prefixLen < len && prefixLen < newLen && |
574 prefixLen < newLen && | |
575 value[prefixLen] == evtValue[prefixLen]) { | 576 value[prefixLen] == evtValue[prefixLen]) { |
576 prefixLen++; | 577 prefixLen++; |
577 } | 578 } |
578 while (prefixLen > 0 && !this.isWordBreakChar(value[prefixLen - 1])) { | 579 while (prefixLen > 0 && !this.isWordBreakChar(value[prefixLen - 1])) { |
579 prefixLen--; | 580 prefixLen--; |
580 } | 581 } |
581 | 582 |
582 suffixLen = 0; | 583 suffixLen = 0; |
583 while (suffixLen < (len - prefixLen) && | 584 while (suffixLen < (len - prefixLen) && suffixLen < (newLen - prefixLen) && |
584 suffixLen < (newLen - prefixLen) && | |
585 value[len - suffixLen - 1] == evtValue[newLen - suffixLen - 1]) { | 585 value[len - suffixLen - 1] == evtValue[newLen - suffixLen - 1]) { |
586 suffixLen++; | 586 suffixLen++; |
587 } | 587 } |
588 while (suffixLen > 0 && !this.isWordBreakChar(value[len - suffixLen])) { | 588 while (suffixLen > 0 && !this.isWordBreakChar(value[len - suffixLen])) { |
589 suffixLen--; | 589 suffixLen--; |
590 } | 590 } |
591 | 591 |
592 this.describeTextChangedHelper(evt, prefixLen, suffixLen, '', personality); | 592 this.describeTextChangedHelper(evt, prefixLen, suffixLen, '', personality); |
593 }; | 593 }; |
594 | 594 |
(...skipping 20 matching lines...) Expand all Loading... |
615 var deleted = this.value.substr(prefixLen, deletedLen); | 615 var deleted = this.value.substr(prefixLen, deletedLen); |
616 var insertedLen = newLen - prefixLen - suffixLen; | 616 var insertedLen = newLen - prefixLen - suffixLen; |
617 var inserted = evt.value.substr(prefixLen, insertedLen); | 617 var inserted = evt.value.substr(prefixLen, insertedLen); |
618 var utterance = ''; | 618 var utterance = ''; |
619 var triggeredByUser = evt.triggeredByUser; | 619 var triggeredByUser = evt.triggeredByUser; |
620 | 620 |
621 if (insertedLen > 1) { | 621 if (insertedLen > 1) { |
622 utterance = inserted; | 622 utterance = inserted; |
623 } else if (insertedLen == 1) { | 623 } else if (insertedLen == 1) { |
624 if ((cvox.ChromeVox.typingEcho == cvox.TypingEcho.WORD || | 624 if ((cvox.ChromeVox.typingEcho == cvox.TypingEcho.WORD || |
625 cvox.ChromeVox.typingEcho == cvox.TypingEcho.CHARACTER_AND_WORD) && | 625 cvox.ChromeVox.typingEcho == cvox.TypingEcho.CHARACTER_AND_WORD) && |
626 this.isWordBreakChar(inserted) && | 626 this.isWordBreakChar(inserted) && prefixLen > 0 && |
627 prefixLen > 0 && | |
628 !this.isWordBreakChar(evt.value.substr(prefixLen - 1, 1))) { | 627 !this.isWordBreakChar(evt.value.substr(prefixLen - 1, 1))) { |
629 // Speak previous word. | 628 // Speak previous word. |
630 var index = prefixLen; | 629 var index = prefixLen; |
631 while (index > 0 && !this.isWordBreakChar(evt.value[index - 1])) { | 630 while (index > 0 && !this.isWordBreakChar(evt.value[index - 1])) { |
632 index--; | 631 index--; |
633 } | 632 } |
634 if (index < prefixLen) { | 633 if (index < prefixLen) { |
635 utterance = evt.value.substr(index, prefixLen + 1 - index); | 634 utterance = evt.value.substr(index, prefixLen + 1 - index); |
636 } else { | 635 } else { |
637 utterance = inserted; | 636 utterance = inserted; |
638 triggeredByUser = false; // Implies QUEUE_MODE_QUEUE. | 637 triggeredByUser = false; // Implies QUEUE_MODE_QUEUE. |
639 } | 638 } |
640 } else if (cvox.ChromeVox.typingEcho == cvox.TypingEcho.CHARACTER || | 639 } else if ( |
| 640 cvox.ChromeVox.typingEcho == cvox.TypingEcho.CHARACTER || |
641 cvox.ChromeVox.typingEcho == cvox.TypingEcho.CHARACTER_AND_WORD) { | 641 cvox.ChromeVox.typingEcho == cvox.TypingEcho.CHARACTER_AND_WORD) { |
642 // This particular case is handled in event watcher. See the key press | 642 // This particular case is handled in event watcher. See the key press |
643 // handler for more details. | 643 // handler for more details. |
644 utterance = cvox.ChromeVoxEditableTextBase.eventTypingEcho ? '' : | 644 utterance = |
645 inserted; | 645 cvox.ChromeVoxEditableTextBase.eventTypingEcho ? '' : inserted; |
646 } | 646 } |
647 } else if (deletedLen > 1 && !autocompleteSuffix) { | 647 } else if (deletedLen > 1 && !autocompleteSuffix) { |
648 utterance = deleted + ', deleted'; | 648 utterance = deleted + ', deleted'; |
649 } else if (deletedLen == 1) { | 649 } else if (deletedLen == 1) { |
650 utterance = deleted; | 650 utterance = deleted; |
651 } | 651 } |
652 | 652 |
653 if (autocompleteSuffix && utterance) { | 653 if (autocompleteSuffix && utterance) { |
654 utterance += ', ' + autocompleteSuffix; | 654 utterance += ', ' + autocompleteSuffix; |
655 } else if (autocompleteSuffix) { | 655 } else if (autocompleteSuffix) { |
656 utterance = autocompleteSuffix; | 656 utterance = autocompleteSuffix; |
657 } | 657 } |
658 | 658 |
659 if (utterance) { | 659 if (utterance) { |
660 this.speak(utterance, triggeredByUser, opt_personality); | 660 this.speak(utterance, triggeredByUser, opt_personality); |
661 } | 661 } |
662 }; | 662 }; |
663 | 663 |
664 | 664 |
665 /** | 665 /** |
666 * Moves the cursor forward by one character. | 666 * Moves the cursor forward by one character. |
667 * @return {boolean} True if the action was handled. | 667 * @return {boolean} True if the action was handled. |
668 */ | 668 */ |
669 cvox.ChromeVoxEditableTextBase.prototype.moveCursorToNextCharacter = | 669 cvox.ChromeVoxEditableTextBase.prototype.moveCursorToNextCharacter = |
670 function() { return false; }; | 670 function() { |
| 671 return false; |
| 672 }; |
671 | 673 |
672 | 674 |
673 /** | 675 /** |
674 * Moves the cursor backward by one character. | 676 * Moves the cursor backward by one character. |
675 * @return {boolean} True if the action was handled. | 677 * @return {boolean} True if the action was handled. |
676 */ | 678 */ |
677 cvox.ChromeVoxEditableTextBase.prototype.moveCursorToPreviousCharacter = | 679 cvox.ChromeVoxEditableTextBase.prototype.moveCursorToPreviousCharacter = |
678 function() { return false; }; | 680 function() { |
| 681 return false; |
| 682 }; |
679 | 683 |
680 | 684 |
681 /** | 685 /** |
682 * Moves the cursor forward by one word. | 686 * Moves the cursor forward by one word. |
683 * @return {boolean} True if the action was handled. | 687 * @return {boolean} True if the action was handled. |
684 */ | 688 */ |
685 cvox.ChromeVoxEditableTextBase.prototype.moveCursorToNextWord = | 689 cvox.ChromeVoxEditableTextBase.prototype.moveCursorToNextWord = function() { |
686 function() { return false; }; | 690 return false; |
| 691 }; |
687 | 692 |
688 | 693 |
689 /** | 694 /** |
690 * Moves the cursor backward by one word. | 695 * Moves the cursor backward by one word. |
691 * @return {boolean} True if the action was handled. | 696 * @return {boolean} True if the action was handled. |
692 */ | 697 */ |
693 cvox.ChromeVoxEditableTextBase.prototype.moveCursorToPreviousWord = | 698 cvox.ChromeVoxEditableTextBase.prototype.moveCursorToPreviousWord = function() { |
694 function() { return false; }; | 699 return false; |
| 700 }; |
695 | 701 |
696 | 702 |
697 /** | 703 /** |
698 * Moves the cursor forward by one line. | 704 * Moves the cursor forward by one line. |
699 * @return {boolean} True if the action was handled. | 705 * @return {boolean} True if the action was handled. |
700 */ | 706 */ |
701 cvox.ChromeVoxEditableTextBase.prototype.moveCursorToNextLine = | 707 cvox.ChromeVoxEditableTextBase.prototype.moveCursorToNextLine = function() { |
702 function() { return false; }; | 708 return false; |
| 709 }; |
703 | 710 |
704 | 711 |
705 /** | 712 /** |
706 * Moves the cursor backward by one line. | 713 * Moves the cursor backward by one line. |
707 * @return {boolean} True if the action was handled. | 714 * @return {boolean} True if the action was handled. |
708 */ | 715 */ |
709 cvox.ChromeVoxEditableTextBase.prototype.moveCursorToPreviousLine = | 716 cvox.ChromeVoxEditableTextBase.prototype.moveCursorToPreviousLine = function() { |
710 function() { return false; }; | 717 return false; |
| 718 }; |
711 | 719 |
712 | 720 |
713 /** | 721 /** |
714 * Moves the cursor forward by one paragraph. | 722 * Moves the cursor forward by one paragraph. |
715 * @return {boolean} True if the action was handled. | 723 * @return {boolean} True if the action was handled. |
716 */ | 724 */ |
717 cvox.ChromeVoxEditableTextBase.prototype.moveCursorToNextParagraph = | 725 cvox.ChromeVoxEditableTextBase.prototype.moveCursorToNextParagraph = |
718 function() { return false; }; | 726 function() { |
| 727 return false; |
| 728 }; |
719 | 729 |
720 | 730 |
721 /** | 731 /** |
722 * Moves the cursor backward by one paragraph. | 732 * Moves the cursor backward by one paragraph. |
723 * @return {boolean} True if the action was handled. | 733 * @return {boolean} True if the action was handled. |
724 */ | 734 */ |
725 cvox.ChromeVoxEditableTextBase.prototype.moveCursorToPreviousParagraph = | 735 cvox.ChromeVoxEditableTextBase.prototype.moveCursorToPreviousParagraph = |
726 function() { return false; }; | 736 function() { |
| 737 return false; |
| 738 }; |
727 | 739 |
728 | 740 |
729 /******************************************/ | 741 /******************************************/ |
OLD | NEW |