| 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.EditableLine'); |
| 11 |
| 10 goog.provide('editing.TextEditHandler'); | 12 goog.provide('editing.TextEditHandler'); |
| 11 | 13 |
| 12 goog.require('AutomationTreeWalker'); | 14 goog.require('AutomationTreeWalker'); |
| 13 goog.require('AutomationUtil'); | 15 goog.require('AutomationUtil'); |
| 14 goog.require('Output'); | 16 goog.require('Output'); |
| 15 goog.require('Output.EventType'); | 17 goog.require('Output.EventType'); |
| 16 goog.require('cursors.Cursor'); | 18 goog.require('cursors.Cursor'); |
| 17 goog.require('cursors.Range'); | 19 goog.require('cursors.Range'); |
| 18 goog.require('cvox.BrailleBackground'); | 20 goog.require('cvox.BrailleBackground'); |
| 19 goog.require('cvox.ChromeVoxEditableTextBase'); | 21 goog.require('cvox.ChromeVoxEditableTextBase'); |
| (...skipping 200 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 220 * @param {!AutomationNode} node | 222 * @param {!AutomationNode} node |
| 221 * @extends {AutomationEditableText} | 223 * @extends {AutomationEditableText} |
| 222 */ | 224 */ |
| 223 function AutomationRichEditableText(node) { | 225 function AutomationRichEditableText(node) { |
| 224 AutomationEditableText.call(this, node); | 226 AutomationEditableText.call(this, node); |
| 225 | 227 |
| 226 var root = this.node_.root; | 228 var root = this.node_.root; |
| 227 if (!root || !root.anchorObject || !root.focusObject) | 229 if (!root || !root.anchorObject || !root.focusObject) |
| 228 return; | 230 return; |
| 229 | 231 |
| 230 this.range = new cursors.Range( | 232 this.line_ = new editing.EditableLine(root.anchorObject, root.anchorOffset, ro
ot.focusObject, root.focusOffset); |
| 231 new cursors.Cursor(root.anchorObject, root.anchorOffset || 0), | |
| 232 new cursors.Cursor(root.focusObject, root.focusOffset || 0)); | |
| 233 } | 233 } |
| 234 | 234 |
| 235 AutomationRichEditableText.prototype = { | 235 AutomationRichEditableText.prototype = { |
| 236 __proto__: AutomationEditableText.prototype, | 236 __proto__: AutomationEditableText.prototype, |
| 237 | 237 |
| 238 /** @override */ | 238 /** @override */ |
| 239 onUpdate: function() { | 239 onUpdate: function() { |
| 240 var root = this.node_.root; | 240 var root = this.node_.root; |
| 241 if (!root.anchorObject || !root.focusObject) | 241 if (!root.anchorObject || !root.focusObject) |
| 242 return; | 242 return; |
| 243 | 243 |
| 244 var cur = new cursors.Range( | 244 var cur = new editing.EditableLine( |
| 245 new cursors.Cursor(root.anchorObject, root.anchorOffset || 0), | 245 root.anchorObject, root.anchorOffset || 0, |
| 246 new cursors.Cursor(root.focusObject, root.focusOffset || 0)); | 246 root.focusObject, root.focusOffset || 0); |
| 247 var prev = this.range; | 247 var prev = this.line_; |
| 248 this.line_ = cur; |
| 248 | 249 |
| 249 this.range = cur; | 250 if (prev.equals(cur)) { |
| 250 | 251 // Collapsed cursor. |
| 251 if (prev.start.node == cur.start.node && | |
| 252 prev.end.node == cur.end.node && | |
| 253 cur.start.node == cur.end.node) { | |
| 254 // Plain text: diff the two positions. | |
| 255 this.changed(new cvox.TextChangeEvent( | 252 this.changed(new cvox.TextChangeEvent( |
| 256 root.anchorObject.name || '', | 253 cur.text || '', |
| 257 root.anchorOffset || 0, | 254 cur.startOffset || 0, |
| 258 root.focusOffset || 0, | 255 cur.endOffset || 0, |
| 259 true)); | 256 true)); |
| 260 | 257 |
| 261 var lineIndex = this.getLineIndex(this.start); | 258 cvox.ChromeVox.braille.write(new cvox.NavBraille({text: cur.value_, |
| 262 var brailleLineStart = this.getLineStart(lineIndex); | 259 startIndex: 0, |
| 263 var brailleLineEnd = this.getLineEnd(lineIndex); | 260 endIndex: 0})); |
| 264 var buff = new Spannable(this.value); | |
| 265 buff.setSpan(new cvox.ValueSpan(0), brailleLineStart, brailleLineEnd); | |
| 266 | |
| 267 var selStart = this.start - brailleLineStart; | |
| 268 var selEnd = this.end - brailleLineStart; | |
| 269 buff.setSpan(new cvox.ValueSelectionSpan(), selStart, selEnd); | |
| 270 cvox.ChromeVox.braille.write(new cvox.NavBraille({text: buff, | |
| 271 startIndex: selStart, | |
| 272 endIndex: selEnd})); | |
| 273 return; | 261 return; |
| 274 } else { | |
| 275 // Rich text: | |
| 276 // If the position is collapsed, expand to the current line. | |
| 277 var start = cur.start; | |
| 278 var end = cur.end; | |
| 279 if (start.equals(end)) { | |
| 280 start = start.move(Unit.LINE, Movement.BOUND, Dir.BACKWARD); | |
| 281 end = end.move(Unit.LINE, Movement.BOUND, Dir.FORWARD); | |
| 282 } | |
| 283 var range = new cursors.Range(start, end); | |
| 284 var output = new Output().withRichSpeechAndBraille( | |
| 285 range, prev, Output.EventType.NAVIGATE); | |
| 286 | |
| 287 // This position is not describable. | |
| 288 if (!output.hasSpeech) { | |
| 289 cvox.ChromeVox.tts.speak('blank', cvox.QueueMode.CATEGORY_FLUSH); | |
| 290 cvox.ChromeVox.braille.write( | |
| 291 new cvox.NavBraille({text: '', startIndex: 0, endIndex: 0})); | |
| 292 } else { | |
| 293 output.go(); | |
| 294 } | |
| 295 } | 262 } |
| 296 | 263 |
| 297 // Keep the other members in sync for any future editable text base state | 264 // Just output the current line. |
| 298 // machine changes. | 265 new Output().withString(cur.text).go(); |
| 299 this.value = cur.start.node.name || ''; | |
| 300 this.start = cur.start.index; | |
| 301 this.end = cur.start.index; | |
| 302 }, | 266 }, |
| 303 | 267 |
| 304 /** @override */ | 268 /** @override */ |
| 305 describeSelectionChanged: function(evt) { | 269 describeSelectionChanged: function(evt) { |
| 306 // Ignore end of text announcements. | 270 // Ignore end of text announcements. |
| 307 if ((this.start + 1) == evt.start && evt.start == this.value.length) | 271 if ((this.start + 1) == evt.start && evt.start == this.value.length) |
| 308 return; | 272 return; |
| 309 | 273 |
| 310 cvox.ChromeVoxEditableTextBase.prototype.describeSelectionChanged.call( | 274 cvox.ChromeVoxEditableTextBase.prototype.describeSelectionChanged.call( |
| 311 this, evt); | 275 this, evt); |
| 312 }, | 276 }, |
| 313 | 277 |
| 314 /** @override */ | 278 /** @override */ |
| 315 getLineIndex: function(charIndex) { | 279 getLineIndex: function(charIndex) { |
| 316 var breaks = this.getLineBreaks_(); | 280 return 0; |
| 317 var index = 0; | |
| 318 while (index < breaks.length && breaks[index] <= charIndex) | |
| 319 ++index; | |
| 320 return index; | |
| 321 }, | 281 }, |
| 322 | 282 |
| 323 /** @override */ | 283 /** @override */ |
| 324 getLineStart: function(lineIndex) { | 284 getLineStart: function(lineIndex) { |
| 325 if (lineIndex == 0) | 285 return 0; |
| 326 return 0; | |
| 327 var breaks = this.getLineBreaks_(); | |
| 328 return breaks[lineIndex - 1] || | |
| 329 this.node_.root.focusObject.value.length; | |
| 330 }, | 286 }, |
| 331 | 287 |
| 332 /** @override */ | 288 /** @override */ |
| 333 getLineEnd: function(lineIndex) { | 289 getLineEnd: function(lineIndex) { |
| 334 var breaks = this.getLineBreaks_(); | 290 return this.value.length; |
| 335 var value = this.node_.root.focusObject.name; | |
| 336 if (lineIndex >= breaks.length) | |
| 337 return value.length; | |
| 338 return breaks[lineIndex]; | |
| 339 }, | 291 }, |
| 340 | 292 |
| 341 /** @override */ | 293 /** @override */ |
| 342 getLineBreaks_: function() { | 294 getLineBreaks_: function() { |
| 343 return this.node_.root.focusObject.lineStartOffsets || []; | 295 return []; |
| 344 } | 296 } |
| 345 }; | 297 }; |
| 346 | 298 |
| 347 /** | 299 /** |
| 348 * @param {!AutomationNode} node The root editable node, i.e. the root of a | 300 * @param {!AutomationNode} node The root editable node, i.e. the root of a |
| 349 * contenteditable subtree or a text field. | 301 * contenteditable subtree or a text field. |
| 350 * @return {editing.TextEditHandler} | 302 * @return {editing.TextEditHandler} |
| 351 */ | 303 */ |
| 352 editing.TextEditHandler.createForNode = function(node) { | 304 editing.TextEditHandler.createForNode = function(node) { |
| 353 var rootFocusedEditable = null; | 305 var rootFocusedEditable = null; |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 389 cvox.BrailleBackground.getInstance().getTranslatorManager().refresh( | 341 cvox.BrailleBackground.getInstance().getTranslatorManager().refresh( |
| 390 localStorage['brailleTable']); | 342 localStorage['brailleTable']); |
| 391 } | 343 } |
| 392 }; | 344 }; |
| 393 | 345 |
| 394 /** | 346 /** |
| 395 * @private {ChromeVoxStateObserver} | 347 * @private {ChromeVoxStateObserver} |
| 396 */ | 348 */ |
| 397 editing.observer_ = new editing.EditingChromeVoxStateObserver(); | 349 editing.observer_ = new editing.EditingChromeVoxStateObserver(); |
| 398 | 350 |
| 351 /** |
| 352 * An EditableLine encapsulates all data concerning a line in the automation |
| 353 * tree necessary to provide output. |
| 354 * @constructor |
| 355 */ |
| 356 editing.EditableLine = function(startNode, startIndex, endNode, endIndex) { |
| 357 /** @private {!Cursor} */ |
| 358 this.start_ = new Cursor(startNode, startIndex).deepEquivalent; |
| 359 /** @private {!Cursor} */ |
| 360 this.end_ = new Cursor(endNode, endIndex).deepEquivalent; |
| 361 |
| 362 // Computed members. |
| 363 /** @private {Spannable} */ |
| 364 this.value_; |
| 365 /** @private {AutomationNode} */ |
| 366 this.lineStart_; |
| 367 /** @private {AutomationNode} */ |
| 368 this.lineEnd_; |
| 369 /** @private {AutomationNode} */ |
| 370 this.lineContainer_; |
| 371 |
| 372 this.computeLineData_(); |
| 373 }; |
| 374 |
| 375 editing.EditableLine.prototype = { |
| 376 /** @private */ |
| 377 computeLineData_: function() { |
| 378 this.value_ = new Spannable(this.start_.node.name, this.start_); |
| 379 if (this.start_.node == this.end_.node) |
| 380 this.value_.setSpan(this.end_, 0, this.start_.node.name.length); |
| 381 |
| 382 this.lineStart_ = this.start_.node; |
| 383 this.lineEnd_ = this.lineStart_; |
| 384 this.lineContainer_ = this.lineStart_.parent; |
| 385 |
| 386 // Annotate each chunk with its associated node. |
| 387 this.value_.setSpan(this.lineStart_, 0, this.lineStart_.name.length); |
| 388 |
| 389 while (this.lineStart_.previousOnLine) { |
| 390 this.lineStart_ = this.lineStart_.previousOnLine; |
| 391 var oldValue = this.value_; |
| 392 var prepend = new Spannable(this.lineStart_.name, this.lineStart_); |
| 393 prepend.append(this.value_); |
| 394 this.value_ = prepend; |
| 395 } |
| 396 |
| 397 while (this.lineEnd_.nextOnLine) { |
| 398 this.lineEnd_ = this.lineEnd_.nextOnLine; |
| 399 |
| 400 var annotation = this.lineEnd_; |
| 401 if (this.lineEnd_ == this.end_.node) |
| 402 annotation = this.end_; |
| 403 |
| 404 this.value_.append(new Spannable(this.lineEnd_.name, annotation)); |
| 405 } |
| 406 }, |
| 407 |
| 408 /** |
| 409 * Gets the selection offset based on the text content of this line. |
| 410 * @return {number} |
| 411 */ |
| 412 get startOffset() { |
| 413 return this.value_.getSpanStart(this.start_) + this.start_.index; |
| 414 }, |
| 415 |
| 416 /** |
| 417 * Gets the selection offset based on the text content of this line. |
| 418 * @return {number} |
| 419 */ |
| 420 get endOffset() { |
| 421 return this.value_.getSpanStart(this.end_) + this.end_.index; |
| 422 }, |
| 423 |
| 424 /** |
| 425 * Gets the selection offset based on the text content of this line. |
| 426 * @return {number} |
| 427 */ |
| 428 get lineStartOffset() { |
| 429 return this.value_.getSpanStart(this.lineStart_); |
| 430 }, |
| 431 |
| 432 /** |
| 433 * @return {string} The text of this line. |
| 434 */ |
| 435 get text() { |
| 436 return this.value_.toString(); |
| 437 }, |
| 438 |
| 439 /** |
| 440 * Returns true if |otherLine| surrounds the same line as |this|. Note that |
| 441 * the contents of the line might be different. |
| 442 */ |
| 443 equals: function(otherLine) { |
| 444 // Equality is intentionally loose here as any of the state nodes can be inv
alidated at any time. |
| 445 return (otherLine.lineStart_ == this.lineStart_ && otherLine.lineEnd_ == thi
s.lineEnd_) || |
| 446 (otherLine.lineContainer_ == this.lineContainer_ && |
| 447 otherLine.lineStartOffset == this.lineStartOffset); |
| 448 } |
| 449 }; |
| 450 |
| 399 }); | 451 }); |
| OLD | NEW |