| 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 |