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.
4
5/**
6 * @fileoverview The purpose of this class is to delegate to the correct walker
7 * based on the navigation state that it is in. The navigation state is a
8 * simplified view of the external environment; the smallest amount of knowledge
9 * needed to correctly delegate. One example is whether the user
10 * is subnavigating. Note that while this class does
11 * decide which walker to delegate to, it does NOT decide when its state
12 * should be changed. This is done by the layer above. The reason for this
13 * separation is that trying to make the decision here would require a lot
14 * of knowledge about the environment, making this class harder to
15 * test and maintain.
16 *
17 * This class knows about the public interfaces of all the walkers (rather
18 * than just of the abstract class) since there are currently walker operations
19 * which apply only to specific walkers.
20 *
21 * The navigation model is organized around having a chain of walkers with
22 * increasing "granularity". This means (with a few exceptions), that if
23 * walker A is more granular than walker B, then every valid selection in A
24 * is a subset of a valid selection in B. For example, characters are
25 * more granular than words, because every character is either a word or
26 * inside a word.
27 *
28 * Note that while any callers may assume the granularity chain exists (after
29 * all, there is a method makeMoreGranular()), they may not assume anything
30 * about the order in which the walkers occur in this chain. This is because
31 * the order may depend on the navigation state, and having external interaction
32 * would slow down the changes we could make to this class (which is a problem,
33 * since this is one of the core classes that impacts user-perceptible
34 * navigation).
35 *
36 * Thinking of adding something here? Ask these questions:
37 * Is it exposing functionality in some walker, the execution of which depends
38 * on navigation state?
39 *  Then it is a good candidate.
40 * Does it require knowing more about the environment?
41 *  If you are sure that it belongs here, then the minimum amount of knowledge
42 *  to make the delegation decision should be added as state to this class.
43 *  The decision for when this state changes should not be made in this class.
44 *
45 */
46
47
48goog.provide('cvox.NavigationShifter');
49
50goog.require('cvox.AbstractShifter');
51goog.require('cvox.CharacterWalker');
52goog.require('cvox.GroupWalker');
53goog.require('cvox.LayoutLineWalker');
54goog.require('cvox.ObjectWalker');
55goog.require('cvox.SentenceWalker');
56goog.require('cvox.TraverseContent');
57goog.require('cvox.WordWalker');
58
59
60/**
61 * @constructor
62 * @extends {cvox.AbstractShifter}
63 */
64cvox.NavigationShifter = function() {
65  this.reset_();
66  goog.base(this);
67};
68goog.inherits(cvox.NavigationShifter, cvox.AbstractShifter);
69
70// These "const" literals may be used, but no order may be assumed
71// between them by any outside callers.
72/**
73 * @type {Object.<string, number>}
74 */
75cvox.NavigationShifter.GRANULARITIES = {
76  'CHARACTER': 0,
77  'WORD': 1,
78  'LINE': 2,
79  'SENTENCE': 3,
80  'OBJECT': 4,
81  'GROUP': 5
82};
83
84
85/**
86 * Stores state variables in a provided object.
87 *
88 * @param {Object} store The object.
89 */
90cvox.NavigationShifter.prototype.storeOn = function(store) {
91  store['granularity'] = this.getGranularity();
92};
93
94
95/**
96 * Updates the object with state variables from an earlier storeOn call.
97 *
98 * @param {Object} store The object.
99 */
100cvox.NavigationShifter.prototype.readFrom = function(store) {
101  this.setGranularity(store['granularity']);
102};
103
104
105/**
106 * @override
107 */
108cvox.NavigationShifter.prototype.next = function(sel) {
109  var ret = this.currentWalker_.next(sel);
110  if (this.currentWalkerIndex_ <= cvox.NavigationShifter.GRANULARITIES.LINE &&
111      ret) {
112    cvox.TraverseContent.getInstance().syncToCursorSelection(
113        ret.clone().setReversed(false));
114    cvox.TraverseContent.getInstance().updateSelection();
115  }
116  return ret;
117};
118
119
120/**
121 * @override
122 */
123cvox.NavigationShifter.prototype.sync = function(sel) {
124  return this.currentWalker_.sync(sel);
125};
126
127
128/**
129 * @override
130 */
131cvox.NavigationShifter.prototype.getName = function() {
132  return cvox.ChromeVox.msgs.getMsg('navigation_shifter');
133};
134
135
136/**
137 * @override
138 */
139cvox.NavigationShifter.prototype.getDescription = function(prevSel, sel) {
140  return this.currentWalker_.getDescription(prevSel, sel);
141};
142
143
144/**
145 * Gets the braille representation of a node-based selection.
146 * @override
147 */
148cvox.NavigationShifter.prototype.getBraille = function(prevSel, sel) {
149  return this.lineWalker_.getBraille(prevSel, sel);
150};
151
152
153/**
154 * Delegates to currentWalker_.
155 * @return {string} The message string.
156 */
157cvox.NavigationShifter.prototype.getGranularityMsg = function() {
158  return this.currentWalker_.getGranularityMsg();
159};
160
161
162/**
163 * @override
164 */
165cvox.NavigationShifter.prototype.makeMoreGranular = function() {
166  goog.base(this, 'makeMoreGranular');
167  this.currentWalkerIndex_ = Math.max(this.currentWalkerIndex_ - 1, 0);
168  if (!cvox.NavigationShifter.allowSentence && this.currentWalkerIndex_ ==
169      cvox.NavigationShifter.GRANULARITIES.SENTENCE) {
170    this.currentWalkerIndex_--;
171  }
172  this.currentWalker_ = this.walkers_[this.currentWalkerIndex_];
173};
174
175/**
176 * @override
177 */
178cvox.NavigationShifter.prototype.makeLessGranular = function() {
179  goog.base(this, 'makeLessGranular');
180  this.currentWalkerIndex_ =
181      Math.min(this.currentWalkerIndex_ + 1, this.walkers_.length - 1);
182  if (!cvox.NavigationShifter.allowSentence && this.currentWalkerIndex_ ==
183      cvox.NavigationShifter.GRANULARITIES.SENTENCE) {
184    this.currentWalkerIndex_++;
185  }
186  this.currentWalker_ = this.walkers_[this.currentWalkerIndex_];
187};
188
189/**
190 * Shift to a specified granularity.
191 * NOTE: after a shift, we are no longer subnavigating, if we were.
192 * @param {number} granularity The granularity to shift to.
193 */
194cvox.NavigationShifter.prototype.setGranularity = function(granularity) {
195  this.ensureNotSubnavigating();
196  this.currentWalkerIndex_ = granularity;
197  this.currentWalker_ = this.walkers_[this.currentWalkerIndex_];
198};
199
200/**
201 * Gets the granularity.
202 * @return {number} The current granularity.
203 *
204 */
205cvox.NavigationShifter.prototype.getGranularity = function() {
206  var wasSubnavigating = this.isSubnavigating();
207  this.ensureNotSubnavigating();
208  var ret = this.currentWalkerIndex_;
209  if (wasSubnavigating) {
210    this.ensureSubnavigating();
211  }
212  return ret;
213};
214
215
216/** Actions. */
217/**
218 * @override
219 */
220cvox.NavigationShifter.prototype.hasAction = function(name) {
221  if (name == 'toggleLineType') {
222    return true;
223  }
224  return goog.base(this, 'hasAction', name);
225};
226
227
228/**
229 * @override
230 */
231cvox.NavigationShifter.create = function(sel) {
232  return new cvox.NavigationShifter();
233};
234
235
236/**
237 * Resets navigation shifter to a "new" state. Makes testing easier.
238 * @private
239 */
240cvox.NavigationShifter.prototype.reset_ = function() {
241  this.groupWalker_ = new cvox.GroupWalker();
242  this.objectWalker_ = new cvox.ObjectWalker();
243  this.sentenceWalker_ = new cvox.SentenceWalker();
244  this.lineWalker_ = new cvox.LayoutLineWalker();
245  this.wordWalker_ = new cvox.WordWalker();
246  this.characterWalker_ = new cvox.CharacterWalker();
247
248  this.walkers_ = [
249      this.characterWalker_,
250      this.wordWalker_,
251      this.lineWalker_,
252      this.sentenceWalker_,
253      this.objectWalker_,
254      this.groupWalker_
255  ];
256  this.currentWalkerIndex_ = this.walkers_.indexOf(this.groupWalker_);
257
258  /**
259   * @type {cvox.AbstractWalker}
260   * @private
261   */
262  this.currentWalker_ = this.walkers_[this.currentWalkerIndex_];
263};
264
265
266/**
267 * @type {boolean}
268 */
269cvox.NavigationShifter.allowSentence = false;
270