chromium/chrome/browser/resources/chromeos/accessibility/chromevox/background/braille/braille_translator_manager.js

// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview Keeps track of the current braille translators.
 */
import {TestImportManager} from '/common/testing/test_import_manager.js';

import {BrailleTable} from '../../common/braille/braille_table.js';
import {SettingsManager} from '../../common/settings_manager.js';
import {Output} from '../output/output.js';

import {ExpandingBrailleTranslator} from './expanding_braille_translator.js';
import {LibLouis} from './liblouis.js';

export class BrailleTranslatorManager {
  /**
   * @param {LibLouis=} opt_liblouisForTest Liblouis instance to use
   *     for testing.
   */
  constructor(opt_liblouisForTest) {
    /** @private {!LibLouis} */
    this.liblouis_ =
        opt_liblouisForTest ||
        new LibLouis(
            chrome.extension.getURL(
                'chromevox/third_party/liblouis/liblouis_wrapper.js'),
            chrome.extension.getURL('chromevox/background/braille/tables'),
            () => this.loadLiblouis_());

    /** @private {!Array<function()>} */
    this.changeListeners_ = [];
    /** @private {!Array<BrailleTable.Table>} */
    this.tables_ = [];
    /** @private {?ExpandingBrailleTranslator} */
    this.expandingTranslator_ = null;
    /** @private {?LibLouis.Translator} */
    this.defaultTranslator_ = null;
    /** @private {?string} */
    this.defaultTableId_ = null;
    /** @private {?LibLouis.Translator} */
    this.uncontractedTranslator_ = null;
    /** @private {?string} */
    this.uncontractedTableId_ = null;
  }

  static init() {
    if (BrailleTranslatorManager.instance) {
      throw new Error('\nCannot create two BrailleTranslatorManagers');
    }
    BrailleTranslatorManager.instance = new BrailleTranslatorManager();

    SettingsManager.addListenerForKey(
        'brailleTable',
        brailleTable =>
            BrailleTranslatorManager.instance.refresh(brailleTable));
  }

  /**
   * @param {!ArrayBuffer} cells
   * @return {!Promise<?string>}
   */
  static backTranslate(cells) {
    return new Promise(resolve => {
      BrailleTranslatorManager.instance.getDefaultTranslator().backTranslate(
          cells, resolve);
    });
  }

  /**
   * Adds a listener to be called whenever there is a change in the
   * translator(s) returned by other methods of this instance.
   * @param {function()} listener The listener.
   */
  addChangeListener(listener) {
    this.changeListeners_.push(listener);
  }

  /**
   * Refreshes the braille translator(s) used for input and output.  This
   * should be called when something has changed (such as a preference) to
   * make sure that the correct translator is used.
   * @param {string} brailleTable The table for this translator to use.
   * @param {string=} opt_brailleTable8 Optionally specify an uncontracted
   * table.
   * @param {function()=} opt_finishCallback Called when the refresh finishes.
   */
  async refresh(brailleTable, opt_brailleTable8, opt_finishCallback) {
    const finishCallback = opt_finishCallback || (() => {});
    if (brailleTable && brailleTable === this.defaultTableId_) {
      finishCallback();
      return;
    }

    const tables = this.tables_;
    if (tables.length === 0) {
      finishCallback();
      return;
    }

    // Look for the table requested.
    let table = BrailleTable.forId(tables, brailleTable);
    if (!table) {
      // Match table against current locale.
      const currentLocale = chrome.i18n.getMessage('@@ui_locale').split(/[_-]/);
      const major = currentLocale[0];
      const minor = currentLocale[1];
      const firstPass =
          tables.filter(table => table.locale.split(/[_-]/)[0] === major);
      if (firstPass.length > 0) {
        table = firstPass[0];
        if (minor) {
          const secondPass = firstPass.filter(
              table => table.locale.split(/[_-]/)[1] === minor);
          if (secondPass.length > 0) {
            table = secondPass[0];
          }
        }
      }
    }
    if (!table) {
      table = BrailleTable.forId(tables, 'en-nabcc');
    }

    // If the user explicitly set an 8 dot table, use that when looking
    // for an uncontracted table.  Otherwise, use the current table and let
    // getUncontracted find an appropriate corresponding table.
    const table8Dot = opt_brailleTable8 ?
        BrailleTable.forId(tables, opt_brailleTable8) :
        null;
    const uncontractedTable =
        BrailleTable.getUncontracted(tables, table8Dot || table);
    const newDefaultTableId = table.id;
    const newUncontractedTableId =
        table.id === uncontractedTable.id ? null : uncontractedTable.id;
    if (newDefaultTableId === this.defaultTableId_ &&
        newUncontractedTableId === this.uncontractedTableId_) {
      finishCallback();
      return;
    }

    const finishRefresh = (defaultTranslator, uncontractedTranslator) => {
      this.defaultTableId_ = newDefaultTableId;
      this.uncontractedTableId_ = newUncontractedTableId;
      this.expandingTranslator_ = new ExpandingBrailleTranslator(
          defaultTranslator, uncontractedTranslator);
      this.defaultTranslator_ = defaultTranslator;
      this.uncontractedTranslator_ = uncontractedTranslator;
      this.changeListeners_.forEach(listener => listener());
      finishCallback();
    };

    const translator = await this.liblouis_.getTranslator(table.fileNames);
    if (!newUncontractedTableId) {
      finishRefresh(translator, null);
    } else {
      const uncontractedTranslator =
          await this.liblouis_.getTranslator(uncontractedTable.fileNames);
      finishRefresh(translator, uncontractedTranslator);
    }
  }

  /**
   * @return {ExpandingBrailleTranslator} The current expanding braille
   *     translator, or {@code null} if none is available.
   */
  getExpandingTranslator() {
    return this.expandingTranslator_;
  }

  /**
   * @return {LibLouis.Translator} The current braille translator to use
   *     by default, or {@code null} if none is available.
   */
  getDefaultTranslator() {
    return this.defaultTranslator_;
  }

  /**
   * @return {LibLouis.Translator} The current uncontracted braille
   *     translator, or {@code null} if it is the same as the default
   *     translator.
   */
  getUncontractedTranslator() {
    return this.uncontractedTranslator_;
  }

  /** Toggles the braille table type. */
  toggleBrailleTable() {
    let brailleTableType = SettingsManager.getString('brailleTableType');
    let output = '';
    if (brailleTableType === 'brailleTable6') {
      brailleTableType = 'brailleTable8';

      // This label reads "switch to 8 dot braille".
      output = '@OPTIONS_BRAILLE_TABLE_TYPE_6';
    } else {
      brailleTableType = 'brailleTable6';

      // This label reads "switch to 6 dot braille".
      output = '@OPTIONS_BRAILLE_TABLE_TYPE_8';
    }

    const brailleTable = SettingsManager.getString(brailleTableType);
    SettingsManager.set('brailleTable', brailleTable);
    SettingsManager.set('brailleTableType', brailleTableType);
    this.refresh(brailleTable);
    new Output().format(output).go();
  }

  /**
   * Asynchronously fetches the list of braille tables and refreshes the
   * translators when done.
   * @return {!Promise} Resolves when tables are loaded.
   * @private
   */
  async fetchTables_() {
    return new Promise(r => {
      BrailleTable.getAll(tables => {
        this.tables_ = tables;

        // Initial refresh; set options from user preferences.
        this.refresh(SettingsManager.getString('brailleTable'), undefined, r);
      });
    });
  }

  /**
   * Loads the liblouis instance by attaching it to the document.
   * @private
   */
  loadLiblouis_() {
    this.fetchTables_();
  }

  /**
   * @return {!LibLouis} The liblouis instance used by this object.
   */
  getLibLouisForTest() {
    return this.liblouis_;
  }

  /**
   * @return {!Array<BrailleTable.Table>} The currently loaded braille
   *     tables, or an empty array if they are not yet loaded.
   */
  getTablesForTest() {
    return this.tables_;
  }

  /**
   * Loads liblouis tables and returns a promise resolved when loaded.
   * @return {!Promise}
   */
  async loadTablesForTest() {
    await this.fetchTables_();
  }
}

/** @type {BrailleTranslatorManager} */
BrailleTranslatorManager.instance;

TestImportManager.exportForTesting(BrailleTranslatorManager);