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.
6 * @fileoverview Simple class to represent a cursor selection.
7 * A cursor selection is just two cursors; one for the start and one for
8 * the end of some interval in the document.
11 goog
.provide('cvox.CursorSelection');
13 goog
.require('cvox.Cursor');
14 goog
.require('cvox.SelectionUtil');
15 goog
.require('cvox.TraverseUtil');
19 * If the start node and end node are the same, and the indexes are the same,
20 * the selection is interpreted to be a node. Otherwise, it is interpreted
22 * @param {!cvox.Cursor} start The starting cursor.
23 * @param {!cvox.Cursor} end The ending cursor.
24 * @param {boolean=} opt_reverse Whether to make it a reversed selection or
25 * not. Default is selection is not reversed. If start and end are in the
26 * wrong order, they will be swapped automatically.
27 * NOTE: Can't infer automatically whether the selection is reversed because
28 * for a selection on a single node, the start and end are equal.
31 cvox
.CursorSelection = function(start
, end
, opt_reverse
) {
32 this.start
= start
.clone();
33 this.end
= end
.clone();
35 if (opt_reverse
== undefined) {
39 this.isReversed_
= opt_reverse
;
41 if ((this.isReversed_
&&
42 this.start
.node
.compareDocumentPosition(this.end
.node
) ==
43 cvox
.CursorSelection
.BEFORE
) ||
45 this.end
.node
.compareDocumentPosition(this.start
.node
) ==
46 cvox
.CursorSelection
.BEFORE
)) {
47 var oldStart
= this.start
;
48 this.start
= this.end
;
55 * From http://www.w3schools.com/jsref/met_node_comparedocumentposition.asp
57 cvox
.CursorSelection
.BEFORE
= 4;
61 * If true, ensures that this selection is reversed. Otherwise, ensures that
63 * @param {boolean} reversed True to reverse. False to nonreverse.
64 * @return {!cvox.CursorSelection} For chaining.
66 cvox
.CursorSelection
.prototype.setReversed = function(reversed
) {
67 if (reversed
== this.isReversed_
) {
70 var oldStart
= this.start
;
71 this.start
= this.end
;
73 this.isReversed_
= reversed
;
79 * Returns true if this selection is a reverse selection.
80 * @return {boolean} true if reversed.
82 cvox
.CursorSelection
.prototype.isReversed = function() {
83 return this.isReversed_
;
88 * Returns start if not reversed, end if reversed.
89 * @return {!cvox.Cursor} start if not reversed, end if reversed.
91 cvox
.CursorSelection
.prototype.absStart = function() {
92 return this.isReversed_
? this.end
: this.start
;
96 * Returns end if not reversed, start if reversed.
97 * @return {!cvox.Cursor} end if not reversed, start if reversed.
99 cvox
.CursorSelection
.prototype.absEnd = function() {
100 return this.isReversed_
? this.start
: this.end
;
105 * Clones the selection.
106 * @return {!cvox.CursorSelection} The cloned selection.
108 cvox
.CursorSelection
.prototype.clone = function() {
109 return new cvox
.CursorSelection(this.start
, this.end
, this.isReversed_
);
114 * Places a DOM selection around this CursorSelection.
116 cvox
.CursorSelection
.prototype.select = function() {
117 var sel
= window
.getSelection();
118 sel
.removeAllRanges();
120 sel
.addRange(this.getRange());
125 * Creates a new cursor selection that starts and ends at the node.
126 * Returns null if node is null.
127 * @param {Node} node The node.
128 * @return {cvox.CursorSelection} The selection.
130 cvox
.CursorSelection
.fromNode = function(node
) {
134 var text
= cvox
.TraverseUtil
.getNodeText(node
);
136 return new cvox
.CursorSelection(
137 new cvox
.Cursor(node
, 0, text
),
138 new cvox
.Cursor(node
, 0, text
));
143 * Creates a new cursor selection that starts and ends at document.body.
144 * @return {!cvox.CursorSelection} The selection.
146 cvox
.CursorSelection
.fromBody = function() {
147 return /** @type {!cvox.CursorSelection} */ (
148 cvox
.CursorSelection
.fromNode(document
.body
));
152 * Returns the text that the selection spans.
153 * @return {string} Text within the selection. '' if it is a node selection.
155 cvox
.CursorSelection
.prototype.getText = function() {
156 if (this.start
.equals(this.end
)) {
157 return cvox
.TraverseUtil
.getNodeText(this.start
.node
);
159 return cvox
.SelectionUtil
.getRangeText(this.getRange());
163 * Returns a range from the given selection.
164 * @return {Range} The range.
166 cvox
.CursorSelection
.prototype.getRange = function() {
167 var range
= document
.createRange();
168 if (this.isReversed_
) {
169 range
.setStart(this.end
.node
, this.end
.index
);
170 range
.setEnd(this.start
.node
, this.start
.index
);
172 range
.setStart(this.start
.node
, this.start
.index
);
173 range
.setEnd(this.end
.node
, this.end
.index
);
179 * Check for equality.
180 * @param {!cvox.CursorSelection} rhs The CursorSelection to compare against.
181 * @return {boolean} True if equal.
183 cvox
.CursorSelection
.prototype.equals = function(rhs
) {
184 return this.start
.equals(rhs
.start
) && this.end
.equals(rhs
.end
);
188 * Check for equality regardless of direction.
189 * @param {!cvox.CursorSelection} rhs The CursorSelection to compare against.
190 * @return {boolean} True if equal.
192 cvox
.CursorSelection
.prototype.absEquals = function(rhs
) {
193 return ((this.start
.equals(rhs
.start
) && this.end
.equals(rhs
.end
)) ||
194 (this.end
.equals(rhs
.start
) && this.start
.equals(rhs
.end
)));
198 * Determines if this starts before another CursorSelection in document order.
199 * If this is reversed, then a reversed document order is checked.
200 * In the case that this and rhs start at the same position, we return true.
201 * @param {!cvox.CursorSelection} rhs The selection to compare.
202 * @return {boolean} True if this is before rhs.
204 cvox
.CursorSelection
.prototype.directedBefore = function(rhs
) {
205 var leftToRight
= this.start
.node
.compareDocumentPosition(rhs
.start
.node
) ==
206 cvox
.CursorSelection
.BEFORE
;
207 return this.start
.node
== rhs
.start
.node
||
208 (this.isReversed() ? !leftToRight
: leftToRight
);
211 * Normalizes this selection.
212 * Use this routine to adjust CursorSelection's that have been collapsed due to
213 * convention such as when a CursorSelection references a node without attention
215 * The result is to surround the node with this cursor.
216 * @return {!cvox.CursorSelection} The normalized selection.
218 cvox
.CursorSelection
.prototype.normalize = function() {
219 if (this.absEnd().index
== 0 && this.absEnd().node
) {
220 var node
= this.absEnd().node
;
222 // DOM ranges use different conventions when surrounding a node. For
223 // instance, input nodes endOffset is always 0 while h1's endOffset is 1
224 //with both having no children. Use a range to compute the endOffset.
225 var testRange
= document
.createRange();
226 testRange
.selectNodeContents(node
);
227 this.absEnd().index
= testRange
.endOffset
;
233 * Collapses to the directed start of the selection.
234 * @return {!cvox.CursorSelection} For chaining.
236 cvox
.CursorSelection
.prototype.collapse = function() {
238 if (this.start
.equals(this.end
)) {
241 this.end
.copyFrom(this.start
);
242 if (this.start
.text
.length
== 0) {
245 if (this.isReversed()) {
246 if (this.end
.index
> 0) {
250 if (this.end
.index
< this.end
.text
.length
) {