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 representing a DOM selection conveyed through
7 * CursorSelection idioms.
8 * A PageSelection is just a DOM selection. The class itself manages a single
9 * CursorSelection that surrounds a fragment on the page. It also provides an
10 * extend operation to either grow or shrink the selection given a
11 * CursorSelection. The class handles correctly moving the internal
12 * CursorSelection and providing immediate access to a full description of the
13 * selection at any time.
14 */
15
16goog.provide('cvox.PageSelection');
17
18goog.require('cvox.AbstractEarcons');
19goog.require('cvox.CursorSelection');
20goog.require('cvox.NavDescription');
21
22/**
23 * @constructor
24 * @param {!cvox.CursorSelection} sel The initial selection.
25 */
26cvox.PageSelection = function(sel) {
27  this.sel_ = sel.clone();
28  this.sel_.select();
29  this.wasBegin_ = true;
30};
31
32
33/**
34 * Gets a description for the DOM selection during the course of navigation.
35 * @param {cvox.AbstractShifter} navShifter Used to obtain walker-based
36 * descriptions.
37 * @param {!cvox.CursorSelection} prevSel Previous CursorSelection in
38 * navigation.
39 * @param {!cvox.CursorSelection} curSel Current CursorSelection in navigation.
40 * @return {Array.<cvox.NavDescription>} The new description.
41 */
42cvox.PageSelection.prototype.getDescription =
43    function(navShifter, prevSel, curSel) {
44  var desc = [];
45  if (this.sel_.isReversed() != curSel.isReversed()) {
46    // A shrinking selection.
47    desc = navShifter.getDescription(curSel, prevSel);
48    desc[0].annotation = cvox.ChromeVox.msgs.getMsg('describe_unselected');
49    desc[0].pushEarcon(cvox.AbstractEarcons.SELECTION_REVERSE);
50  } else {
51    // A growing selection.
52    desc = navShifter.getDescription(prevSel, curSel);
53    desc[0].annotation = cvox.ChromeVox.msgs.getMsg('describe_selected');
54    desc[0].pushEarcon(cvox.AbstractEarcons.SELECTION);
55    if (!this.wasBegin_ && this.sel_.absEquals(curSel.clone().normalize())) {
56      // A selection has inverted across the start cursor. Describe it.
57      var prevDesc = navShifter.getDescription(curSel, prevSel);
58      prevDesc[0].annotation =
59          cvox.ChromeVox.msgs.getMsg('describe_unselected');
60      prevDesc[0].pushEarcon(cvox.AbstractEarcons.SELECTION_REVERSE);
61      prevDesc[0].pushEarcon(cvox.AbstractEarcons.WRAP);
62      desc = prevDesc.concat(desc);
63    }
64  }
65  return desc;
66};
67
68
69/**
70 * Gets a full description for the entire DOM selection.
71 * Use this description when you want to describe the entire selection
72 * represented by this instance.
73 *
74 * @return {Array.<cvox.NavDescription>} The new description.
75 */
76cvox.PageSelection.prototype.getFullDescription = function() {
77  return [new cvox.NavDescription(
78      {text: window.getSelection().toString(),
79       context: cvox.ChromeVox.msgs.getMsg('selection_is')})];
80};
81
82
83/**
84 * Extends this selection.
85 * @param {!cvox.CursorSelection} sel Extend DOM selection to the selection.
86 * @return {boolean} True if the extension occurred, false if the PageSelection
87 * was reset to sel.
88 */
89cvox.PageSelection.prototype.extend = function(sel) {
90  if (!this.sel_.directedBefore(sel)) {
91    // Do not allow for crossed selections. This restarts a page selection that
92    // has been collapsed. This occurs when two CursorSelection's point away
93    // from one another.
94    this.sel_ = sel.clone();
95  } else {
96    // Otherwise, it is assumed that the CursorSelection's are in directed
97    // document order. The CursorSelection's are either pointing in the same
98    // direction or towards one another. In the first case, shrink/extend this
99    // PageSelection to the end of "sel". In the second case, shrink/extend this
100    // PageSelection to the start of "sel".
101    this.sel_.end = this.sel_.isReversed() == sel.isReversed() ?
102        sel.end.clone() : sel.start.clone();
103  }
104  this.sel_.select();
105  this.wasBegin_ = false;
106  return !this.sel_.absEquals(sel);
107};
108