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