blob: 209388cc582ab827bbce03807a1c4c4a867881c9 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview The purpose of this class is to delegate to the correct walker
* based on the navigation state that it is in. The navigation state is a
* simplified view of the external environment; the smallest amount of knowledge
* needed to correctly delegate. One example is whether the user
* is subnavigating. Note that while this class does
* decide which walker to delegate to, it does NOT decide when its state
* should be changed. This is done by the layer above. The reason for this
* separation is that trying to make the decision here would require a lot
* of knowledge about the environment, making this class harder to
* test and maintain.
*
* This class knows about the public interfaces of all the walkers (rather
* than just of the abstract class) since there are currently walker operations
* which apply only to specific walkers.
*
* The navigation model is organized around having a chain of walkers with
* increasing "granularity". This means (with a few exceptions), that if
* walker A is more granular than walker B, then every valid selection in A
* is a subset of a valid selection in B. For example, characters are
* more granular than words, because every character is either a word or
* inside a word.
*
* Note that while any callers may assume the granularity chain exists (after
* all, there is a method makeMoreGranular()), they may not assume anything
* about the order in which the walkers occur in this chain. This is because
* the order may depend on the navigation state, and having external interaction
* would slow down the changes we could make to this class (which is a problem,
* since this is one of the core classes that impacts user-perceptible
* navigation).
*
* Thinking of adding something here? Ask these questions:
* Is it exposing functionality in some walker, the execution of which depends
* on navigation state?
* Then it is a good candidate.
* Does it require knowing more about the environment?
* If you are sure that it belongs here, then the minimum amount of knowledge
* to make the delegation decision should be added as state to this class.
* The decision for when this state changes should not be made in this class.
*
*/
goog.provide('cvox.NavigationShifter');
goog.require('cvox.AbstractShifter');
goog.require('cvox.CharacterWalker');
goog.require('cvox.GroupWalker');
goog.require('cvox.LayoutLineWalker');
goog.require('cvox.ObjectWalker');
goog.require('cvox.SentenceWalker');
goog.require('cvox.TraverseContent');
goog.require('cvox.WordWalker');
/**
* @constructor
* @extends {cvox.AbstractShifter}
*/
cvox.NavigationShifter = function() {
this.reset_();
goog.base(this);
};
goog.inherits(cvox.NavigationShifter, cvox.AbstractShifter);
// These "const" literals may be used, but no order may be assumed
// between them by any outside callers.
/**
* @type {Object<string, number>}
*/
cvox.NavigationShifter.GRANULARITIES = {
'CHARACTER': 0,
'WORD': 1,
'LINE': 2,
'SENTENCE': 3,
'OBJECT': 4,
'GROUP': 5
};
/**
* Stores state variables in a provided object.
*
* @param {Object} store The object.
*/
cvox.NavigationShifter.prototype.storeOn = function(store) {
store['granularity'] = this.getGranularity();
};
/**
* Updates the object with state variables from an earlier storeOn call.
*
* @param {Object} store The object.
*/
cvox.NavigationShifter.prototype.readFrom = function(store) {
this.setGranularity(store['granularity']);
};
/**
* @override
*/
cvox.NavigationShifter.prototype.next = function(sel) {
var ret = this.currentWalker_.next(sel);
if (this.currentWalkerIndex_ <= cvox.NavigationShifter.GRANULARITIES.LINE &&
ret) {
cvox.TraverseContent.getInstance().syncToCursorSelection(
ret.clone().setReversed(false));
cvox.TraverseContent.getInstance().updateSelection();
}
return ret;
};
/**
* @override
*/
cvox.NavigationShifter.prototype.sync = function(sel) {
return this.currentWalker_.sync(sel);
};
/**
* @override
*/
cvox.NavigationShifter.prototype.getName = function() {
return cvox.ChromeVox.msgs.getMsg('navigation_shifter');
};
/**
* @override
*/
cvox.NavigationShifter.prototype.getDescription = function(prevSel, sel) {
return this.currentWalker_.getDescription(prevSel, sel);
};
/**
* Gets the braille representation of a node-based selection.
* @override
*/
cvox.NavigationShifter.prototype.getBraille = function(prevSel, sel) {
return this.lineWalker_.getBraille(prevSel, sel);
};
/**
* Delegates to currentWalker_.
* @return {string} The message string.
*/
cvox.NavigationShifter.prototype.getGranularityMsg = function() {
return this.currentWalker_.getGranularityMsg();
};
/**
* @override
*/
cvox.NavigationShifter.prototype.makeMoreGranular = function() {
goog.base(this, 'makeMoreGranular');
this.currentWalkerIndex_ = Math.max(this.currentWalkerIndex_ - 1, 0);
if (!cvox.NavigationShifter.allowSentence && this.currentWalkerIndex_ ==
cvox.NavigationShifter.GRANULARITIES.SENTENCE) {
this.currentWalkerIndex_--;
}
this.currentWalker_ = this.walkers_[this.currentWalkerIndex_];
};
/**
* @override
*/
cvox.NavigationShifter.prototype.makeLessGranular = function() {
goog.base(this, 'makeLessGranular');
this.currentWalkerIndex_ =
Math.min(this.currentWalkerIndex_ + 1, this.walkers_.length - 1);
if (!cvox.NavigationShifter.allowSentence && this.currentWalkerIndex_ ==
cvox.NavigationShifter.GRANULARITIES.SENTENCE) {
this.currentWalkerIndex_++;
}
this.currentWalker_ = this.walkers_[this.currentWalkerIndex_];
};
/**
* Shift to a specified granularity.
* NOTE: after a shift, we are no longer subnavigating, if we were.
* @param {number} granularity The granularity to shift to.
*/
cvox.NavigationShifter.prototype.setGranularity = function(granularity) {
this.ensureNotSubnavigating();
this.currentWalkerIndex_ = granularity;
this.currentWalker_ = this.walkers_[this.currentWalkerIndex_];
};
/**
* Gets the granularity.
* @return {number} The current granularity.
*
*/
cvox.NavigationShifter.prototype.getGranularity = function() {
var wasSubnavigating = this.isSubnavigating();
this.ensureNotSubnavigating();
var ret = this.currentWalkerIndex_;
if (wasSubnavigating) {
this.ensureSubnavigating();
}
return ret;
};
/** Actions. */
/**
* @override
*/
cvox.NavigationShifter.prototype.hasAction = function(name) {
if (name == 'toggleLineType') {
return true;
}
return goog.base(this, 'hasAction', name);
};
/**
* @override
*/
cvox.NavigationShifter.create = function(sel) {
return new cvox.NavigationShifter();
};
/**
* Resets navigation shifter to a "new" state. Makes testing easier.
* @private
*/
cvox.NavigationShifter.prototype.reset_ = function() {
this.groupWalker_ = new cvox.GroupWalker();
this.objectWalker_ = new cvox.ObjectWalker();
this.sentenceWalker_ = new cvox.SentenceWalker();
this.lineWalker_ = new cvox.LayoutLineWalker();
this.wordWalker_ = new cvox.WordWalker();
this.characterWalker_ = new cvox.CharacterWalker();
this.walkers_ = [
this.characterWalker_,
this.wordWalker_,
this.lineWalker_,
this.sentenceWalker_,
this.objectWalker_,
this.groupWalker_
];
this.currentWalkerIndex_ = this.walkers_.indexOf(this.groupWalker_);
/**
* @type {cvox.AbstractWalker}
* @private
*/
this.currentWalker_ = this.walkers_[this.currentWalkerIndex_];
};
/**
* @type {boolean}
*/
cvox.NavigationShifter.allowSentence = false;