Allow only one bookmark to be added for multiple fast starring
[chromium-blink-merge.git] / chrome / browser / resources / chromeos / chromevox / braille / braille_display_manager.js
blobb94ef8674f5d2caf998d5930b55f05b39858492e
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 /**
6 * @fileoverview Puts text on a braille display.
8 */
10 goog.provide('cvox.BrailleDisplayManager');
12 goog.require('cvox.BrailleCaptionsBackground');
13 goog.require('cvox.BrailleDisplayState');
14 goog.require('cvox.ExpandingBrailleTranslator');
15 goog.require('cvox.LibLouis');
16 goog.require('cvox.NavBraille');
17 goog.require('cvox.PanStrategy');
20 /**
21 * @param {!cvox.BrailleTranslatorManager} translatorManager Keeps track
22 * of the current translator to use.
23 * @constructor
25 cvox.BrailleDisplayManager = function(translatorManager) {
26 /**
27 * @type {!cvox.BrailleTranslatorManager}
28 * @private
30 this.translatorManager_ = translatorManager;
31 /**
32 * @type {!cvox.NavBraille}
33 * @private
35 this.content_ = new cvox.NavBraille({});
36 /**
37 * @type {!cvox.ExpandingBrailleTranslator.ExpansionType} valueExpansion
38 * @private
40 this.expansionType_ =
41 cvox.ExpandingBrailleTranslator.ExpansionType.SELECTION;
42 /**
43 * @type {!ArrayBuffer}
44 * @private
46 this.translatedContent_ = new ArrayBuffer(0);
47 /**
48 * @type {!ArrayBuffer}
49 * @private
51 this.displayedContent_ = this.translatedContent_;
52 /**
53 * @type {cvox.PanStrategy}
54 * @private
56 this.panStrategy_ = new cvox.WrappingPanStrategy();
57 /**
58 * @type {function(!cvox.BrailleKeyEvent, cvox.NavBraille)}
59 * @private
61 this.commandListener_ = function() {};
62 /**
63 * Current display state used for width calculations. This is different from
64 * realDisplayState_ if the braille captions feature is enabled and there is
65 * no hardware display connected. Otherwise, it is the same object
66 * as realDisplayState_.
67 * @type {!cvox.BrailleDisplayState}
68 * @private
70 this.displayState_ = {available: false, textCellCount: undefined};
71 /**
72 * State reported from the chrome api, reflecting a real hardware
73 * display.
74 * @type {!cvox.BrailleDisplayState}
75 * @private
77 this.realDisplayState_ = this.displayState_;
78 /**
79 * @type {!Array<number>}
80 * @private
82 this.textToBraille_ = [];
83 /**
84 * @type {!Array<number>}
85 * @private
87 this.brailleToText_ = [];
89 translatorManager.addChangeListener(function() {
90 this.translateContent_(this.content_, this.expansionType_);
91 }.bind(this));
93 chrome.storage.onChanged.addListener(function(changes, area) {
94 if (area == 'local' && 'brailleWordWrap' in changes) {
95 this.updatePanStrategy_(changes.brailleWordWrap.newValue);
97 }.bind(this));
98 chrome.storage.local.get({brailleWordWrap: true}, function(items) {
99 this.updatePanStrategy_(items.brailleWordWrap);
100 }.bind(this));
102 cvox.BrailleCaptionsBackground.init(goog.bind(
103 this.onCaptionsStateChanged_, this));
104 if (goog.isDef(chrome.brailleDisplayPrivate)) {
105 var onDisplayStateChanged = goog.bind(this.refreshDisplayState_, this);
106 chrome.brailleDisplayPrivate.getDisplayState(onDisplayStateChanged);
107 chrome.brailleDisplayPrivate.onDisplayStateChanged.addListener(
108 onDisplayStateChanged);
109 chrome.brailleDisplayPrivate.onKeyEvent.addListener(
110 goog.bind(this.onKeyEvent_, this));
111 } else {
112 // Get the initial captions state since we won't refresh the display
113 // state in an API callback in this case.
114 this.onCaptionsStateChanged_();
120 * Dots representing a cursor.
121 * @const
122 * @private
124 cvox.BrailleDisplayManager.CURSOR_DOTS_ = 1 << 6 | 1 << 7;
128 * @param {!cvox.NavBraille} content Content to send to the braille display.
129 * @param {!cvox.ExpandingBrailleTranslator.ExpansionType} expansionType
130 * If the text has a {@code ValueSpan}, this indicates how that part
131 * of the display content is expanded when translating to braille.
132 * (See {@code cvox.ExpandingBrailleTranslator}).
134 cvox.BrailleDisplayManager.prototype.setContent = function(
135 content, expansionType) {
136 this.translateContent_(content, expansionType);
141 * Sets the command listener. When a command is invoked, the listener will be
142 * called with the BrailleKeyEvent corresponding to the command and the content
143 * that was present on the display when the command was invoked. The content
144 * is guaranteed to be identical to an object previously used as the parameter
145 * to cvox.BrailleDisplayManager.setContent, or null if no content was set.
146 * @param {function(!cvox.BrailleKeyEvent, cvox.NavBraille)} func The listener.
148 cvox.BrailleDisplayManager.prototype.setCommandListener = function(func) {
149 this.commandListener_ = func;
154 * @param {!cvox.BrailleDisplayState} newState Display state reported
155 * by the extension API.
156 * @private
158 cvox.BrailleDisplayManager.prototype.refreshDisplayState_ = function(
159 newState) {
160 var oldSize = this.displayState_.textCellCount || 0;
161 this.realDisplayState_ = newState;
162 if (newState.available) {
163 this.displayState_ = newState;
164 } else {
165 this.displayState_ =
166 cvox.BrailleCaptionsBackground.getVirtualDisplayState();
168 var newSize = this.displayState_.textCellCount || 0;
169 if (oldSize != newSize) {
170 this.panStrategy_.setDisplaySize(newSize);
172 this.refresh_();
177 * Called when the state of braille captions changes.
178 * @private
180 cvox.BrailleDisplayManager.prototype.onCaptionsStateChanged_ = function() {
181 // Force reevaluation of the display state based on our stored real
182 // hardware display state, meaning that if a real display is connected,
183 // that takes precedence over the state from the captions 'virtual' display.
184 this.refreshDisplayState_(this.realDisplayState_);
188 /** @private */
189 cvox.BrailleDisplayManager.prototype.refresh_ = function() {
190 if (!this.displayState_.available) {
191 return;
193 var viewPort = this.panStrategy_.viewPort;
194 var buf = this.displayedContent_.slice(viewPort.start, viewPort.end);
195 if (this.realDisplayState_.available) {
196 chrome.brailleDisplayPrivate.writeDots(buf);
198 if (cvox.BrailleCaptionsBackground.isEnabled()) {
199 var start = this.brailleToTextPosition_(viewPort.start);
200 var end = this.brailleToTextPosition_(viewPort.end);
201 cvox.BrailleCaptionsBackground.setContent(
202 this.content_.text.toString().substring(start, end), buf);
208 * @param {!cvox.NavBraille} newContent New display content.
209 * @param {cvox.ExpandingBrailleTranslator.ExpansionType} newExpansionType
210 * How the value part of of the new content should be expanded
211 * with regards to contractions.
212 * @private
214 cvox.BrailleDisplayManager.prototype.translateContent_ = function(
215 newContent, newExpansionType) {
216 var writeTranslatedContent = function(cells, textToBraille, brailleToText) {
217 this.content_ = newContent;
218 this.expansionType_ = newExpansionType;
219 this.textToBraille_ = textToBraille;
220 this.brailleToText_ = brailleToText;
221 var startIndex = this.content_.startIndex;
222 var endIndex = this.content_.endIndex;
223 var targetPosition;
224 if (startIndex >= 0) {
225 var translatedStartIndex;
226 var translatedEndIndex;
227 if (startIndex >= textToBraille.length) {
228 // Allow the cells to be extended with one extra cell for
229 // a carret after the last character.
230 var extCells = new ArrayBuffer(cells.byteLength + 1);
231 new Uint8Array(extCells).set(new Uint8Array(cells));
232 // Last byte is initialized to 0.
233 cells = extCells;
234 translatedStartIndex = cells.byteLength - 1;
235 } else {
236 translatedStartIndex = textToBraille[startIndex];
238 if (endIndex >= textToBraille.length) {
239 // endIndex can't be past-the-end of the last cell unless
240 // startIndex is too, so we don't have to do another
241 // extension here.
242 translatedEndIndex = cells.byteLength;
243 } else {
244 translatedEndIndex = textToBraille[endIndex];
246 this.translatedContent_ = cells;
247 // Copy the transalted content to a separate buffer and add the cursor
248 // to it.
249 this.displayedContent_ = new ArrayBuffer(cells.byteLength);
250 new Uint8Array(this.displayedContent_).set(new Uint8Array(cells));
251 this.writeCursor_(this.displayedContent_,
252 translatedStartIndex, translatedEndIndex);
253 targetPosition = translatedStartIndex;
254 } else {
255 this.translatedContent_ = this.displayedContent_ = cells;
256 targetPosition = 0;
258 this.panStrategy_.setContent(this.translatedContent_, targetPosition);
259 this.refresh_();
260 }.bind(this);
262 var translator = this.translatorManager_.getExpandingTranslator();
263 if (!translator) {
264 writeTranslatedContent(new ArrayBuffer(0), [], []);
265 } else {
266 translator.translate(
267 newContent.text,
268 newExpansionType,
269 writeTranslatedContent);
275 * @param {cvox.BrailleKeyEvent} event The key event.
276 * @private
278 cvox.BrailleDisplayManager.prototype.onKeyEvent_ = function(event) {
279 switch (event.command) {
280 case cvox.BrailleKeyCommand.PAN_LEFT:
281 this.panLeft_();
282 break;
283 case cvox.BrailleKeyCommand.PAN_RIGHT:
284 this.panRight_();
285 break;
286 case cvox.BrailleKeyCommand.ROUTING:
287 event.displayPosition = this.brailleToTextPosition_(
288 event.displayPosition + this.panStrategy_.viewPort.start);
289 // fall through
290 default:
291 this.commandListener_(event, this.content_);
292 break;
298 * Shift the display by one full display size and refresh the content.
299 * Sends the appropriate command if the display is already at the leftmost
300 * position.
301 * @private
303 cvox.BrailleDisplayManager.prototype.panLeft_ = function() {
304 if (this.panStrategy_.previous()) {
305 this.refresh_();
306 } else {
307 this.commandListener_({
308 command: cvox.BrailleKeyCommand.PAN_LEFT
309 }, this.content_);
315 * Shifts the display position to the right by one full display size and
316 * refreshes the content. Sends the appropriate command if the display is
317 * already at its rightmost position.
318 * @private
320 cvox.BrailleDisplayManager.prototype.panRight_ = function() {
321 if (this.panStrategy_.next()) {
322 this.refresh_();
323 } else {
324 this.commandListener_({
325 command: cvox.BrailleKeyCommand.PAN_RIGHT
326 }, this.content_);
332 * Writes a cursor in the specified range into translated content.
333 * @param {ArrayBuffer} buffer Buffer to add cursor to.
334 * @param {number} startIndex The start index to place the cursor.
335 * @param {number} endIndex The end index to place the cursor (exclusive).
336 * @private
338 cvox.BrailleDisplayManager.prototype.writeCursor_ = function(
339 buffer, startIndex, endIndex) {
340 if (startIndex < 0 || startIndex >= buffer.byteLength ||
341 endIndex < startIndex || endIndex > buffer.byteLength) {
342 return;
344 if (startIndex == endIndex) {
345 endIndex = startIndex + 1;
347 var dataView = new DataView(buffer);
348 while (startIndex < endIndex) {
349 var value = dataView.getUint8(startIndex);
350 value |= cvox.BrailleDisplayManager.CURSOR_DOTS_;
351 dataView.setUint8(startIndex, value);
352 startIndex++;
358 * Returns the text position corresponding to an absolute braille position,
359 * that is not accounting for the current pan position.
360 * @private
361 * @param {number} braillePosition Braille position relative to the startof
362 * the translated content.
363 * @return {number} The mapped position in code units.
365 cvox.BrailleDisplayManager.prototype.brailleToTextPosition_ =
366 function(braillePosition) {
367 var mapping = this.brailleToText_;
368 if (braillePosition < 0) {
369 // This shouldn't happen.
370 console.error('WARNING: Braille position < 0: ' + braillePosition);
371 return 0;
372 } else if (braillePosition >= mapping.length) {
373 // This happens when the user clicks on the right part of the display
374 // when it is not entirely filled with content. Allow addressing the
375 // position after the last character.
376 return this.content_.text.getLength();
377 } else {
378 return mapping[braillePosition];
384 * @param {boolean} wordWrap
385 * @private
387 cvox.BrailleDisplayManager.prototype.updatePanStrategy_ = function(wordWrap) {
388 var newStrategy = wordWrap ? new cvox.WrappingPanStrategy() :
389 new cvox.FixedPanStrategy();
390 newStrategy.setDisplaySize(this.displayState_.textCellCount || 0);
391 newStrategy.setContent(this.translatedContent_,
392 this.panStrategy_.viewPort.start);
393 this.panStrategy_ = newStrategy;
394 this.refresh_();