Home | History | Annotate | Download | only in common
      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 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.
      9  */
     10 
     11 goog.provide('cvox.CursorSelection');
     12 
     13 goog.require('cvox.Cursor');
     14 goog.require('cvox.SelectionUtil');
     15 goog.require('cvox.TraverseUtil');
     16 
     17 
     18 /**
     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
     21  * to be a range.
     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.
     29  * @constructor
     30  */
     31 cvox.CursorSelection = function(start, end, opt_reverse) {
     32   this.start = start.clone();
     33   this.end = end.clone();
     34 
     35   if (opt_reverse == undefined) {
     36     opt_reverse = false;
     37   }
     38   /** @private */
     39   this.isReversed_ = opt_reverse;
     40 
     41   if ((this.isReversed_ &&
     42        this.start.node.compareDocumentPosition(this.end.node) ==
     43        cvox.CursorSelection.BEFORE) ||
     44       (!this.isReversed_ &&
     45        this.end.node.compareDocumentPosition(this.start.node) ==
     46        cvox.CursorSelection.BEFORE)) {
     47     var oldStart = this.start;
     48     this.start = this.end;
     49     this.end = oldStart;
     50   }
     51 };
     52 
     53 
     54 /**
     55  * From http://www.w3schools.com/jsref/met_node_comparedocumentposition.asp
     56  */
     57 cvox.CursorSelection.BEFORE = 4;
     58 
     59 
     60 /**
     61  * If true, ensures that this selection is reversed. Otherwise, ensures that
     62  * it is not reversed.
     63  * @param {boolean} reversed True to reverse. False to nonreverse.
     64  * @return {!cvox.CursorSelection} For chaining.
     65  */
     66 cvox.CursorSelection.prototype.setReversed = function(reversed) {
     67   if (reversed == this.isReversed_) {
     68     return this;
     69   }
     70   var oldStart = this.start;
     71   this.start = this.end;
     72   this.end = oldStart;
     73   this.isReversed_ = reversed;
     74   return this;
     75 };
     76 
     77 
     78 /**
     79  * Returns true if this selection is a reverse selection.
     80  * @return {boolean} true if reversed.
     81  */
     82 cvox.CursorSelection.prototype.isReversed = function() {
     83   return this.isReversed_;
     84 };
     85 
     86 
     87 /**
     88  * Returns start if not reversed, end if reversed.
     89  * @return {!cvox.Cursor} start if not reversed, end if reversed.
     90  */
     91 cvox.CursorSelection.prototype.absStart = function() {
     92   return this.isReversed_ ? this.end : this.start;
     93 };
     94 
     95 /**
     96  * Returns end if not reversed, start if reversed.
     97  * @return {!cvox.Cursor} end if not reversed, start if reversed.
     98  */
     99 cvox.CursorSelection.prototype.absEnd = function() {
    100   return this.isReversed_ ? this.start : this.end;
    101 };
    102 
    103 
    104 /**
    105  * Clones the selection.
    106  * @return {!cvox.CursorSelection} The cloned selection.
    107  */
    108 cvox.CursorSelection.prototype.clone = function() {
    109   return new cvox.CursorSelection(this.start, this.end, this.isReversed_);
    110 };
    111 
    112 
    113 /**
    114  * Places a DOM selection around this CursorSelection.
    115  */
    116 cvox.CursorSelection.prototype.select = function() {
    117   var sel = window.getSelection();
    118   sel.removeAllRanges();
    119   this.normalize();
    120   sel.addRange(this.getRange());
    121 };
    122 
    123 
    124 /**
    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.
    129  */
    130 cvox.CursorSelection.fromNode = function(node) {
    131   if (!node) {
    132     return null;
    133   }
    134   var text = cvox.TraverseUtil.getNodeText(node);
    135 
    136   return new cvox.CursorSelection(
    137       new cvox.Cursor(node, 0, text),
    138       new cvox.Cursor(node, 0, text));
    139 };
    140 
    141 
    142 /**
    143  * Creates a new cursor selection that starts and ends at document.body.
    144  * @return {!cvox.CursorSelection} The selection.
    145  */
    146 cvox.CursorSelection.fromBody = function() {
    147     return /** @type {!cvox.CursorSelection} */ (
    148         cvox.CursorSelection.fromNode(document.body));
    149 };
    150 
    151 /**
    152  * Returns the text that the selection spans.
    153  * @return {string} Text within the selection. '' if it is a node selection.
    154  */
    155 cvox.CursorSelection.prototype.getText = function() {
    156   if (this.start.equals(this.end)) {
    157     return cvox.TraverseUtil.getNodeText(this.start.node);
    158   }
    159   return cvox.SelectionUtil.getRangeText(this.getRange());
    160 };
    161 
    162 /**
    163  * Returns a range from the given selection.
    164  * @return {Range} The range.
    165  */
    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);
    171   } else {
    172     range.setStart(this.start.node, this.start.index);
    173     range.setEnd(this.end.node, this.end.index);
    174   }
    175   return range;
    176 };
    177 
    178 /**
    179  * Check for equality.
    180  * @param {!cvox.CursorSelection} rhs The CursorSelection to compare against.
    181  * @return {boolean} True if equal.
    182  */
    183 cvox.CursorSelection.prototype.equals = function(rhs) {
    184   return this.start.equals(rhs.start) && this.end.equals(rhs.end);
    185 };
    186 
    187 /**
    188  * Check for equality regardless of direction.
    189  * @param {!cvox.CursorSelection} rhs The CursorSelection to compare against.
    190  * @return {boolean} True if equal.
    191  */
    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)));
    195 };
    196 
    197 /**
    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.
    203  */
    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);
    209 };
    210 /**
    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
    214  * to its endpoints.
    215  * The result is to surround the node with this cursor.
    216  * @return {!cvox.CursorSelection} The normalized selection.
    217  */
    218 cvox.CursorSelection.prototype.normalize = function() {
    219   if (this.absEnd().index == 0 && this.absEnd().node) {
    220     var node = this.absEnd().node;
    221 
    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;
    228   }
    229   return this;
    230 };
    231 
    232 /**
    233  * Collapses to the directed start of the selection.
    234  * @return {!cvox.CursorSelection} For chaining.
    235  */
    236 cvox.CursorSelection.prototype.collapse = function() {
    237   // Not a selection.
    238   if (this.start.equals(this.end)) {
    239     return this;
    240   }
    241   this.end.copyFrom(this.start);
    242   if (this.start.text.length == 0) {
    243     return this;
    244   }
    245   if (this.isReversed()) {
    246     if (this.end.index > 0) {
    247       this.end.index--;
    248     }
    249   } else {
    250     if (this.end.index < this.end.text.length) {
    251       this.end.index++;
    252     }
    253   }
    254   return this;
    255 };
    256