blob: 86b54b42701416802c57994596bc0ac8e230f686 [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 A class for walking tables.
* NOTE: This class has a very different interface than the other walkers.
* This means it does not lend itself easily to e.g. decorators.
* TODO (stoarca): This might be able to be fixed by breaking it up into
* separate walkers for cell, row and column.
*/
goog.provide('cvox.TableWalker');
goog.require('cvox.AbstractWalker');
goog.require('cvox.BrailleUtil');
goog.require('cvox.DescriptionUtil');
goog.require('cvox.DomUtil');
goog.require('cvox.NavDescription');
goog.require('cvox.TraverseTable');
/**
* @constructor
* @extends {cvox.AbstractWalker}
*/
cvox.TableWalker = function() {
cvox.AbstractWalker.call(this);
/**
* Only used as a cache for faster lookup.
* @type {!cvox.TraverseTable}
*/
this.tt = new cvox.TraverseTable(null);
};
goog.inherits(cvox.TableWalker, cvox.AbstractWalker);
/**
* @override
*/
cvox.TableWalker.prototype.next = function(sel) {
// TODO (stoarca): See bug 6677953
return this.nextRow(sel);
};
/**
* @override
*/
cvox.TableWalker.prototype.sync = function(sel) {
return this.goTo_(sel, goog.bind(function(position) {
return this.tt.goToCell(position);
}, this));
};
/**
* @override
* @suppress {checkTypes} actual parameter 2 of
* cvox.AbstractMsgs.prototype.getMsg does not match formal parameter
* found : Array.<number>
* required: (Array.<string>|null|undefined)
*/
cvox.TableWalker.prototype.getDescription = function(prevSel, sel) {
var position = this.syncPosition_(sel);
if (!position) {
return [];
}
this.tt.goToCell(position);
var descs = cvox.DescriptionUtil.getCollectionDescription(prevSel, sel);
if (descs.length == 0) {
descs.push(new cvox.NavDescription({
annotation: cvox.ChromeVox.msgs.getMsg('empty_cell')
}));
}
return descs;
};
/**
* @override
*/
cvox.TableWalker.prototype.getBraille = function(prevSel, sel) {
var ret = new cvox.NavBraille({});
var position = this.syncPosition_(sel);
if (position) {
var text =
cvox.BrailleUtil.getTemplated(prevSel.start.node, sel.start.node);
text.append(' ' + ++position[0] + '/' + ++position[1]);
}
return new cvox.NavBraille({text: text});
};
/**
* @override
*/
cvox.TableWalker.prototype.getGranularityMsg = goog.abstractMethod;
/** Table Actions. */
/**
* Returns the first cell of the table that this selection is inside.
* @param {!cvox.CursorSelection} sel The selection.
* @return {cvox.CursorSelection} The selection for first cell of the table.
* @expose
*/
cvox.TableWalker.prototype.goToFirstCell = function(sel) {
return this.goTo_(sel, goog.bind(function(position) {
return this.tt.goToCell([0, 0]);
}, this));
};
/**
* Returns the last cell of the table that this selection is inside.
* @param {!cvox.CursorSelection} sel The selection.
* @return {cvox.CursorSelection} The selection for the last cell of the table.
* @expose
*/
cvox.TableWalker.prototype.goToLastCell = function(sel) {
return this.goTo_(sel, goog.bind(function(position) {
return this.tt.goToLastCell();
}, this));
};
/**
* Returns the first cell of the row that the selection is in.
* @param {!cvox.CursorSelection} sel The selection.
* @return {cvox.CursorSelection} The selection for the first cell in the row.
* @expose
*/
cvox.TableWalker.prototype.goToRowFirstCell = function(sel) {
return this.goTo_(sel, goog.bind(function(position) {
return this.tt.goToCell([position[0], 0]);
}, this));
};
/**
* Returns the last cell of the row that the selection is in.
* @param {!cvox.CursorSelection} sel The selection.
* @return {cvox.CursorSelection} The selection for the last cell in the row.
* @expose
*/
cvox.TableWalker.prototype.goToRowLastCell = function(sel) {
return this.goTo_(sel, goog.bind(function(position) {
return this.tt.goToRowLastCell();
}, this));
};
/**
* Returns the first cell of the column that the selection is in.
* @param {!cvox.CursorSelection} sel The selection.
* @return {cvox.CursorSelection} The selection for the first cell in the col.
* @expose
*/
cvox.TableWalker.prototype.goToColFirstCell = function(sel) {
return this.goTo_(sel, goog.bind(function(position) {
return this.tt.goToCell([0, position[1]]);
}, this));
};
/**
* Returns the last cell of the column that the selection is in.
* @param {!cvox.CursorSelection} sel The selection.
* @return {cvox.CursorSelection} The selection for the last cell in the col.
* @expose
*/
cvox.TableWalker.prototype.goToColLastCell = function(sel) {
return this.goTo_(sel, goog.bind(function(position) {
return this.tt.goToColLastCell();
}, this));
};
/**
* Returns the first cell in the row after the current selection.
* @param {!cvox.CursorSelection} sel The selection.
* @return {cvox.CursorSelection} The selection for the first cell in the next
* row.
* @expose
*/
cvox.TableWalker.prototype.nextRow = function(sel) {
return this.goTo_(sel, goog.bind(function(position) {
return this.tt.goToCell([position[0] + (sel.isReversed() ? -1 : 1),
position[1]]);
}, this));
};
/**
* Returns the first cell in the column after the current selection.
* @param {!cvox.CursorSelection} sel The selection.
* @return {cvox.CursorSelection} The selection for the first cell in the
* next col.
* @expose
*/
cvox.TableWalker.prototype.nextCol = function(sel) {
return this.goTo_(sel, goog.bind(function(position) {
return this.tt.goToCell([position[0],
position[1] + (sel.isReversed() ? -1 : 1)]);
}, this));
};
/**
* @param {!cvox.CursorSelection} sel The current selection.
* @return {cvox.CursorSelection} The resulting selection.
* @expose
*/
cvox.TableWalker.prototype.announceHeaders = function(sel) {
cvox.ChromeVox.tts.speak(this.getHeaderText_(sel),
cvox.AbstractTts.QUEUE_MODE_FLUSH,
cvox.AbstractTts.PERSONALITY_ANNOTATION);
return sel;
};
/**
* @param {!cvox.CursorSelection} sel The current selection.
* @return {cvox.CursorSelection} The resulting selection.
* @expose
*/
cvox.TableWalker.prototype.speakTableLocation = function(sel) {
cvox.ChromeVox.navigationManager.speakDescriptionArray(
this.getLocationDescription_(sel),
cvox.AbstractTts.QUEUE_MODE_FLUSH,
null);
return sel;
};
/**
* @param {!cvox.CursorSelection} sel The current selection.
* @return {cvox.CursorSelection} The resulting selection.
* @expose
*/
cvox.TableWalker.prototype.exitShifterContent = function(sel) {
var tableNode = this.getTableNode_(sel);
if (!tableNode) {
return null;
}
var nextNode = cvox.DomUtil.directedNextLeafNode(tableNode, false);
return cvox.CursorSelection.fromNode(nextNode);
};
/** End of actions. */
/**
* Returns the text content of the header(s) of the cell that contains sel.
* @param {!cvox.CursorSelection} sel The selection.
* @return {!string} The header text.
* @private
*/
cvox.TableWalker.prototype.getHeaderText_ = function(sel) {
this.tt.initialize(this.getTableNode_(sel));
var position = this.tt.findNearestCursor(sel.start.node);
if (!position) {
return cvox.ChromeVox.msgs.getMsg('not_inside_table');
}
if (!this.tt.goToCell(position)) {
return cvox.ChromeVox.msgs.getMsg('not_inside_table');
}
return (
this.getRowHeaderText_(position) +
' ' +
this.getColHeaderText_(position));
};
/**
* Returns the location description.
* @param {!cvox.CursorSelection} sel A valid selection.
* @return {Array.<cvox.NavDescription>} The location description.
* @suppress {checkTypes} actual parameter 2 of
* cvox.AbstractMsgs.prototype.getMsg does not match
* formal parameter
* found : Array.<number>
* required: (Array.<string>|null|undefined)
* @private
*/
cvox.TableWalker.prototype.getLocationDescription_ = function(sel) {
var locationInfo = this.getLocationInfo(sel);
if (locationInfo == null) {
return null;
}
return [new cvox.NavDescription({
text: cvox.ChromeVox.msgs.getMsg('table_location', locationInfo)
})];
};
/**
* Returns the text content of the row header(s) of the cell that contains sel.
* @param {!Array.<number>} position The selection.
* @return {!string} The header text.
* @private
*/
cvox.TableWalker.prototype.getRowHeaderText_ = function(position) {
// TODO(stoarca): OPTMZ Replace with join();
var rowHeaderText = '';
var rowHeaders = this.tt.getCellRowHeaders();
if (rowHeaders.length == 0) {
var firstCellInRow = this.tt.getCellAt([position[0], 0]);
rowHeaderText += cvox.DomUtil.collapseWhitespace(
cvox.DomUtil.getValue(firstCellInRow) + ' ' +
cvox.DomUtil.getName(firstCellInRow));
return cvox.ChromeVox.msgs.getMsg('row_header') + rowHeaderText;
}
for (var i = 0; i < rowHeaders.length; ++i) {
rowHeaderText += cvox.DomUtil.collapseWhitespace(
cvox.DomUtil.getValue(rowHeaders[i]) + ' ' +
cvox.DomUtil.getName(rowHeaders[i]));
}
if (rowHeaderText == '') {
return cvox.ChromeVox.msgs.getMsg('empty_row_header');
}
return cvox.ChromeVox.msgs.getMsg('row_header') + rowHeaderText;
};
/**
* Returns the text content of the col header(s) of the cell that contains sel.
* @param {!Array.<number>} position The selection.
* @return {!string} The header text.
* @private
*/
cvox.TableWalker.prototype.getColHeaderText_ = function(position) {
// TODO(stoarca): OPTMZ Replace with join();
var colHeaderText = '';
var colHeaders = this.tt.getCellColHeaders();
if (colHeaders.length == 0) {
var firstCellInCol = this.tt.getCellAt([0, position[1]]);
colHeaderText += cvox.DomUtil.collapseWhitespace(
cvox.DomUtil.getValue(firstCellInCol) + ' ' +
cvox.DomUtil.getName(firstCellInCol));
return cvox.ChromeVox.msgs.getMsg('column_header') + colHeaderText;
}
for (var i = 0; i < colHeaders.length; ++i) {
colHeaderText += cvox.DomUtil.collapseWhitespace(
cvox.DomUtil.getValue(colHeaders[i]) + ' ' +
cvox.DomUtil.getName(colHeaders[i]));
}
if (colHeaderText == '') {
return cvox.ChromeVox.msgs.getMsg('empty_row_header');
}
return cvox.ChromeVox.msgs.getMsg('column_header') + colHeaderText;
};
/**
* Returns the location info of sel within the containing table.
* @param {!cvox.CursorSelection} sel The selection.
* @return {Array.<number>} The location info:
* [row index, row count, col index, col count].
*/
cvox.TableWalker.prototype.getLocationInfo = function(sel) {
this.tt.initialize(this.getTableNode_(sel));
var position = this.tt.findNearestCursor(sel.start.node);
if (!position) {
return null;
}
// + 1 to account for 0-indexed
return [
position[0] + 1,
this.tt.rowCount,
position[1] + 1,
this.tt.colCount
].map(function(x) {return cvox.ChromeVox.msgs.getNumber(x);});
};
/**
* Returns true if sel is inside a table.
* @param {!cvox.CursorSelection} sel The selection.
* @return {boolean} True if inside a table node.
*/
cvox.TableWalker.prototype.isInTable = function(sel) {
return this.getTableNode_(sel) != null;
};
/**
* Wrapper for going to somewhere so that boilerplate is not repeated.
* @param {!cvox.CursorSelection} sel The selection from which to base the
* movement.
* @param {function(Array.<number>):boolean} f The function to use for moving.
* Returns true on success and false on failure.
* @return {cvox.CursorSelection} The resulting selection.
* @private
*/
cvox.TableWalker.prototype.goTo_ = function(sel, f) {
this.tt.initialize(this.getTableNode_(sel));
var position = this.tt.findNearestCursor(sel.end.node);
if (!position) {
return null;
}
this.tt.goToCell(position);
if (!f(position)) {
return null;
}
return cvox.CursorSelection.fromNode(this.tt.getCell()).
setReversed(sel.isReversed());
};
/**
* Returns the nearest table node containing the end of the selection
* @param {!cvox.CursorSelection} sel The selection.
* @return {Node} The table node containing sel. null if not in a table.
* @private
*/
cvox.TableWalker.prototype.getTableNode_ = function(sel) {
return cvox.DomUtil.getContainingTable(sel.end.node);
};
/**
* Sync the backing traversal utility to the given selection.
* @param {!cvox.CursorSelection} sel The selection.
* @return {Array.<number>} The position [x, y] of the selection.
* @private
*/
cvox.TableWalker.prototype.syncPosition_ = function(sel) {
var tableNode = this.getTableNode_(sel);
this.tt.initialize(tableNode);
// we need to align the TraverseTable with our sel because our walker
// uses parts of it (for example isSpanned relies on being at a specific cell)
return this.tt.findNearestCursor(sel.end.node);
};