Home | History | Annotate | Download | only in walkers
      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 A class for walking tables.
      7  * NOTE: This class has a very different interface than the other walkers.
      8  * This means it does not lend itself easily to e.g. decorators.
      9  * TODO (stoarca): This might be able to be fixed by breaking it up into
     10  * separate walkers for cell, row and column.
     11  */
     12 
     13 
     14 goog.provide('cvox.TableWalker');
     15 
     16 goog.require('cvox.AbstractWalker');
     17 goog.require('cvox.BrailleUtil');
     18 goog.require('cvox.DescriptionUtil');
     19 goog.require('cvox.DomUtil');
     20 goog.require('cvox.NavDescription');
     21 goog.require('cvox.TraverseTable');
     22 
     23 /**
     24  * @constructor
     25  * @extends {cvox.AbstractWalker}
     26  */
     27 cvox.TableWalker = function() {
     28   cvox.AbstractWalker.call(this);
     29 
     30   /**
     31    * Only used as a cache for faster lookup.
     32    * @type {!cvox.TraverseTable}
     33    */
     34   this.tt = new cvox.TraverseTable(null);
     35 };
     36 goog.inherits(cvox.TableWalker, cvox.AbstractWalker);
     37 
     38 /**
     39  * @override
     40  */
     41 cvox.TableWalker.prototype.next = function(sel) {
     42   // TODO (stoarca): See bug 6677953
     43   return this.nextRow(sel);
     44 };
     45 
     46 /**
     47  * @override
     48  */
     49 cvox.TableWalker.prototype.sync = function(sel) {
     50   return this.goTo_(sel, goog.bind(function(position) {
     51       return this.tt.goToCell(position);
     52   }, this));
     53 };
     54 
     55 /**
     56  * @override
     57  * @suppress {checkTypes} actual parameter 2 of
     58  * cvox.AbstractMsgs.prototype.getMsg does not match formal parameter
     59  * found   : Array.<number>
     60  * required: (Array.<string>|null|undefined)
     61  */
     62 cvox.TableWalker.prototype.getDescription = function(prevSel, sel) {
     63   var position = this.syncPosition_(sel);
     64   if (!position) {
     65     return [];
     66   }
     67   this.tt.goToCell(position);
     68   var descs = cvox.DescriptionUtil.getCollectionDescription(prevSel, sel);
     69   if (descs.length == 0) {
     70     descs.push(new cvox.NavDescription({
     71       annotation: cvox.ChromeVox.msgs.getMsg('empty_cell')
     72     }));
     73   }
     74   return descs;
     75 };
     76 
     77 /**
     78  * @override
     79  */
     80 cvox.TableWalker.prototype.getBraille = function(prevSel, sel) {
     81   var ret = new cvox.NavBraille({});
     82   var position = this.syncPosition_(sel);
     83   if (position) {
     84     var text =
     85         cvox.BrailleUtil.getTemplated(prevSel.start.node, sel.start.node);
     86     text.append(' ' + ++position[0] + '/' + ++position[1]);
     87   }
     88   return new cvox.NavBraille({text: text});
     89 };
     90 
     91 /**
     92  * @override
     93  */
     94 cvox.TableWalker.prototype.getGranularityMsg = goog.abstractMethod;
     95 
     96 
     97 /** Table Actions. */
     98 
     99 
    100 /**
    101  * Returns the first cell of the table that this selection is inside.
    102  * @param {!cvox.CursorSelection} sel The selection.
    103  * @return {cvox.CursorSelection} The selection for first cell of the table.
    104  * @expose
    105  */
    106 cvox.TableWalker.prototype.goToFirstCell = function(sel) {
    107   return this.goTo_(sel, goog.bind(function(position) {
    108     return this.tt.goToCell([0, 0]);
    109   }, this));
    110 };
    111 
    112 /**
    113  * Returns the last cell of the table that this selection is inside.
    114  * @param {!cvox.CursorSelection} sel The selection.
    115  * @return {cvox.CursorSelection} The selection for the last cell of the table.
    116  * @expose
    117  */
    118 cvox.TableWalker.prototype.goToLastCell = function(sel) {
    119   return this.goTo_(sel, goog.bind(function(position) {
    120     return this.tt.goToLastCell();
    121   }, this));
    122 };
    123 
    124 /**
    125  * Returns the first cell of the row that the selection is in.
    126  * @param {!cvox.CursorSelection} sel The selection.
    127  * @return {cvox.CursorSelection} The selection for the first cell in the row.
    128  * @expose
    129  */
    130 cvox.TableWalker.prototype.goToRowFirstCell = function(sel) {
    131   return this.goTo_(sel, goog.bind(function(position) {
    132     return this.tt.goToCell([position[0], 0]);
    133   }, this));
    134 };
    135 
    136 /**
    137  * Returns the last cell of the row that the selection is in.
    138  * @param {!cvox.CursorSelection} sel The selection.
    139  * @return {cvox.CursorSelection} The selection for the last cell in the row.
    140  * @expose
    141  */
    142 cvox.TableWalker.prototype.goToRowLastCell = function(sel) {
    143   return this.goTo_(sel, goog.bind(function(position) {
    144     return this.tt.goToRowLastCell();
    145   }, this));
    146 };
    147 
    148 /**
    149  * Returns the first cell of the column that the selection is in.
    150  * @param {!cvox.CursorSelection} sel The selection.
    151  * @return {cvox.CursorSelection} The selection for the first cell in the col.
    152  * @expose
    153  */
    154 cvox.TableWalker.prototype.goToColFirstCell = function(sel) {
    155   return this.goTo_(sel, goog.bind(function(position) {
    156     return this.tt.goToCell([0, position[1]]);
    157   }, this));
    158 };
    159 
    160 /**
    161  * Returns the last cell of the column that the selection is in.
    162  * @param {!cvox.CursorSelection} sel The selection.
    163  * @return {cvox.CursorSelection} The selection for the last cell in the col.
    164  * @expose
    165  */
    166 cvox.TableWalker.prototype.goToColLastCell = function(sel) {
    167   return this.goTo_(sel, goog.bind(function(position) {
    168     return this.tt.goToColLastCell();
    169   }, this));
    170 };
    171 
    172 /**
    173  * Returns the first cell in the row after the current selection.
    174  * @param {!cvox.CursorSelection} sel The selection.
    175  * @return {cvox.CursorSelection} The selection for the first cell in the next
    176  * row.
    177  * @expose
    178  */
    179 cvox.TableWalker.prototype.nextRow = function(sel) {
    180   return this.goTo_(sel, goog.bind(function(position) {
    181     return this.tt.goToCell([position[0] + (sel.isReversed() ? -1 : 1),
    182                               position[1]]);
    183   }, this));
    184 };
    185 
    186 /**
    187  * Returns the first cell in the column after the current selection.
    188  * @param {!cvox.CursorSelection} sel The selection.
    189  * @return {cvox.CursorSelection} The selection for the first cell in the
    190  * next col.
    191  * @expose
    192  */
    193 cvox.TableWalker.prototype.nextCol = function(sel) {
    194   return this.goTo_(sel, goog.bind(function(position) {
    195     return this.tt.goToCell([position[0],
    196                               position[1] + (sel.isReversed() ? -1 : 1)]);
    197   }, this));
    198 };
    199 
    200 /**
    201  * @param {!cvox.CursorSelection} sel The current selection.
    202  * @return {cvox.CursorSelection} The resulting selection.
    203  * @expose
    204  */
    205 cvox.TableWalker.prototype.announceHeaders = function(sel) {
    206   cvox.ChromeVox.tts.speak(this.getHeaderText_(sel),
    207                            cvox.AbstractTts.QUEUE_MODE_FLUSH,
    208                            cvox.AbstractTts.PERSONALITY_ANNOTATION);
    209   return sel;
    210 };
    211 
    212 /**
    213  * @param {!cvox.CursorSelection} sel The current selection.
    214  * @return {cvox.CursorSelection} The resulting selection.
    215  * @expose
    216  */
    217 cvox.TableWalker.prototype.speakTableLocation = function(sel) {
    218   cvox.ChromeVox.navigationManager.speakDescriptionArray(
    219       this.getLocationDescription_(sel),
    220       cvox.AbstractTts.QUEUE_MODE_FLUSH,
    221       null);
    222   return sel;
    223 };
    224 
    225 
    226 /**
    227  * @param {!cvox.CursorSelection} sel The current selection.
    228  * @return {cvox.CursorSelection} The resulting selection.
    229  * @expose
    230  */
    231 cvox.TableWalker.prototype.exitShifterContent = function(sel) {
    232   var tableNode = this.getTableNode_(sel);
    233   if (!tableNode) {
    234     return null;
    235   }
    236   var nextNode = cvox.DomUtil.directedNextLeafNode(tableNode, false);
    237   return cvox.CursorSelection.fromNode(nextNode);
    238 };
    239 
    240 
    241 /** End of actions. */
    242 
    243 
    244 /**
    245  * Returns the text content of the header(s) of the cell that contains sel.
    246  * @param {!cvox.CursorSelection} sel The selection.
    247  * @return {!string} The header text.
    248  * @private
    249  */
    250 cvox.TableWalker.prototype.getHeaderText_ = function(sel) {
    251   this.tt.initialize(this.getTableNode_(sel));
    252   var position = this.tt.findNearestCursor(sel.start.node);
    253   if (!position) {
    254     return cvox.ChromeVox.msgs.getMsg('not_inside_table');
    255   }
    256   if (!this.tt.goToCell(position)) {
    257     return cvox.ChromeVox.msgs.getMsg('not_inside_table');
    258   }
    259   return (
    260       this.getRowHeaderText_(position) +
    261       ' ' +
    262       this.getColHeaderText_(position));
    263 };
    264 
    265 /**
    266  * Returns the location description.
    267  * @param {!cvox.CursorSelection} sel A valid selection.
    268  * @return {Array.<cvox.NavDescription>} The location description.
    269  * @suppress {checkTypes} actual parameter 2 of
    270  * cvox.AbstractMsgs.prototype.getMsg does not match
    271  * formal parameter
    272  * found   : Array.<number>
    273  * required: (Array.<string>|null|undefined)
    274  * @private
    275  */
    276 cvox.TableWalker.prototype.getLocationDescription_ = function(sel) {
    277   var locationInfo = this.getLocationInfo(sel);
    278   if (locationInfo == null) {
    279     return null;
    280   }
    281   return [new cvox.NavDescription({
    282     text: cvox.ChromeVox.msgs.getMsg('table_location', locationInfo)
    283   })];
    284 };
    285 
    286 /**
    287  * Returns the text content of the row header(s) of the cell that contains sel.
    288  * @param {!Array.<number>} position The selection.
    289  * @return {!string} The header text.
    290  * @private
    291  */
    292 cvox.TableWalker.prototype.getRowHeaderText_ = function(position) {
    293   // TODO(stoarca): OPTMZ Replace with join();
    294   var rowHeaderText = '';
    295 
    296   var rowHeaders = this.tt.getCellRowHeaders();
    297   if (rowHeaders.length == 0) {
    298     var firstCellInRow = this.tt.getCellAt([position[0], 0]);
    299     rowHeaderText += cvox.DomUtil.collapseWhitespace(
    300         cvox.DomUtil.getValue(firstCellInRow) + ' ' +
    301             cvox.DomUtil.getName(firstCellInRow));
    302     return cvox.ChromeVox.msgs.getMsg('row_header') + rowHeaderText;
    303   }
    304 
    305   for (var i = 0; i < rowHeaders.length; ++i) {
    306     rowHeaderText += cvox.DomUtil.collapseWhitespace(
    307         cvox.DomUtil.getValue(rowHeaders[i]) + ' ' +
    308             cvox.DomUtil.getName(rowHeaders[i]));
    309   }
    310   if (rowHeaderText == '') {
    311     return cvox.ChromeVox.msgs.getMsg('empty_row_header');
    312   }
    313   return cvox.ChromeVox.msgs.getMsg('row_header') + rowHeaderText;
    314 };
    315 
    316 /**
    317  * Returns the text content of the col header(s) of the cell that contains sel.
    318  * @param {!Array.<number>} position The selection.
    319  * @return {!string} The header text.
    320  * @private
    321  */
    322 cvox.TableWalker.prototype.getColHeaderText_ = function(position) {
    323   // TODO(stoarca): OPTMZ Replace with join();
    324   var colHeaderText = '';
    325 
    326   var colHeaders = this.tt.getCellColHeaders();
    327   if (colHeaders.length == 0) {
    328     var firstCellInCol = this.tt.getCellAt([0, position[1]]);
    329     colHeaderText += cvox.DomUtil.collapseWhitespace(
    330         cvox.DomUtil.getValue(firstCellInCol) + ' ' +
    331         cvox.DomUtil.getName(firstCellInCol));
    332     return cvox.ChromeVox.msgs.getMsg('column_header') + colHeaderText;
    333   }
    334 
    335   for (var i = 0; i < colHeaders.length; ++i) {
    336     colHeaderText += cvox.DomUtil.collapseWhitespace(
    337         cvox.DomUtil.getValue(colHeaders[i]) + ' ' +
    338             cvox.DomUtil.getName(colHeaders[i]));
    339   }
    340   if (colHeaderText == '') {
    341     return cvox.ChromeVox.msgs.getMsg('empty_row_header');
    342   }
    343   return cvox.ChromeVox.msgs.getMsg('column_header') + colHeaderText;
    344 };
    345 
    346 /**
    347  * Returns the location info of sel within the containing table.
    348  * @param {!cvox.CursorSelection} sel The selection.
    349  * @return {Array.<number>} The location info:
    350  *  [row index, row count, col index, col count].
    351  */
    352 cvox.TableWalker.prototype.getLocationInfo = function(sel) {
    353   this.tt.initialize(this.getTableNode_(sel));
    354   var position = this.tt.findNearestCursor(sel.start.node);
    355   if (!position) {
    356     return null;
    357   }
    358   // + 1 to account for 0-indexed
    359   return [
    360     position[0] + 1,
    361     this.tt.rowCount,
    362     position[1] + 1,
    363     this.tt.colCount
    364   ].map(function(x) {return cvox.ChromeVox.msgs.getNumber(x);});
    365 };
    366 
    367 /**
    368  * Returns true if sel is inside a table.
    369  * @param {!cvox.CursorSelection} sel The selection.
    370  * @return {boolean} True if inside a table node.
    371  */
    372 cvox.TableWalker.prototype.isInTable = function(sel) {
    373   return this.getTableNode_(sel) != null;
    374 };
    375 
    376 /**
    377  * Wrapper for going to somewhere so that boilerplate is not repeated.
    378  * @param {!cvox.CursorSelection} sel The selection from which to base the
    379  * movement.
    380  * @param {function(Array.<number>):boolean} f The function to use for moving.
    381  * Returns true on success and false on failure.
    382  * @return {cvox.CursorSelection} The resulting selection.
    383  * @private
    384  */
    385 cvox.TableWalker.prototype.goTo_ = function(sel, f) {
    386   this.tt.initialize(this.getTableNode_(sel));
    387   var position = this.tt.findNearestCursor(sel.end.node);
    388   if (!position) {
    389     return null;
    390   }
    391   this.tt.goToCell(position);
    392   if (!f(position)) {
    393     return null;
    394   }
    395   return cvox.CursorSelection.fromNode(this.tt.getCell()).
    396       setReversed(sel.isReversed());
    397 };
    398 
    399 /**
    400  * Returns the nearest table node containing the end of the selection
    401  * @param {!cvox.CursorSelection} sel The selection.
    402  * @return {Node} The table node containing sel. null if not in a table.
    403  * @private
    404  */
    405 cvox.TableWalker.prototype.getTableNode_ = function(sel) {
    406   return cvox.DomUtil.getContainingTable(sel.end.node);
    407 };
    408 
    409 /**
    410  * Sync the backing traversal utility to the given selection.
    411  * @param {!cvox.CursorSelection} sel The selection.
    412  * @return {Array.<number>} The position [x, y] of the selection.
    413  * @private
    414  */
    415 cvox.TableWalker.prototype.syncPosition_ = function(sel) {
    416   var tableNode = this.getTableNode_(sel);
    417   this.tt.initialize(tableNode);
    418   // we need to align the TraverseTable with our sel because our walker
    419   // uses parts of it (for example isSpanned relies on being at a specific cell)
    420   return this.tt.findNearestCursor(sel.end.node);
    421 };
    422