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 Base class for all ChromeVox widgets.
7 *
8 * Widgets are keyboard driven and modal mediums for ChromeVox to expose
9 * additional features such as lists, interactive search, or grids.
10 */
11
12goog.provide('cvox.Widget');
13
14goog.require('cvox.AbstractEarcons');
15goog.require('cvox.ApiImplementation');
16goog.require('cvox.ChromeVox');
17goog.require('cvox.SpokenMessages');
18
19/**
20 * Keeps a reference to a currently or formerly active widget. This enforces
21 * the singleton nature of widgets.
22 * @type {cvox.Widget}
23 * @private
24 */
25cvox.Widget.ref_;
26
27
28/**
29 * @constructor
30 */
31cvox.Widget = function() {
32  /**
33   * @type {boolean}
34   * @protected
35   */
36  this.active = false;
37
38
39  /**
40   * Keeps a reference to a node which should receive focus once a widget hides.
41   * @type {Node}
42   * @protected
43   */
44  this.initialFocus = null;
45
46  /**
47   * Keeps a reference to a node which should receive selection once a widget
48   * hides.
49   * @type {Node}
50   * @protected
51   */
52  this.initialNode = null;
53
54  // Checks to see if there is a current widget in use.
55  if (!cvox.Widget.ref_ || !cvox.Widget.ref_.isActive()) {
56    cvox.Widget.ref_ = this;
57  }
58};
59
60
61/**
62 * Returns whether or not the widget is active.
63 * @return {boolean} Whether the widget is active.
64 */
65cvox.Widget.prototype.isActive = function() {
66  return this.active;
67};
68
69
70/**
71 * Visual/aural display of this widget.
72 */
73cvox.Widget.prototype.show = function() {
74  if (this.isActive()) {
75    // Only one widget should be shown at any given time.
76    this.hide(true);
77  }
78  this.onKeyDown = goog.bind(this.onKeyDown, this);
79  this.onKeyPress = goog.bind(this.onKeyPress, this);
80  window.addEventListener('keydown', this.onKeyDown, true);
81  window.addEventListener('keypress', this.onKeyPress, true);
82
83  this.initialNode =
84      cvox.ChromeVox.navigationManager.getCurrentNode();
85  this.initialFocus = document.activeElement;
86
87  // Widgets do not respond to sticky key.
88  cvox.ChromeVox.stickyOverride = false;
89
90  if (this.getNameMsg() && this.getHelpMsg()) {
91    cvox.$m(this.getNameMsg()).
92        andPause().
93        andMessage(this.getHelpMsg()).
94        speakFlush();
95  }
96  cvox.ChromeVox.earcons.playEarcon(cvox.AbstractEarcons.OBJECT_OPEN);
97
98  this.active = true;
99};
100
101
102/**
103 * Visual/aural hide of this widget.
104 * @param {boolean=} opt_noSync Whether to attempt to sync to the node before
105 * this widget was first shown. If left unspecified or false, an attempt to sync
106 * will be made.
107 */
108cvox.Widget.prototype.hide = function(opt_noSync) {
109  window.removeEventListener('keypress', this.onKeyPress, true);
110  window.removeEventListener('keydown', this.onKeyDown, true);
111  cvox.ChromeVox.stickyOverride = null;
112
113  cvox.ChromeVox.earcons.playEarcon(cvox.AbstractEarcons.OBJECT_CLOSE);
114  if (!opt_noSync) {
115    this.initialNode = this.initialNode.nodeType == 1 ?
116        this.initialNode : this.initialNode.parentNode;
117    cvox.ApiImplementation.syncToNode(this.initialNode,
118                                      true,
119                                      cvox.AbstractTts.QUEUE_MODE_QUEUE);
120  }
121
122  this.active = false;
123};
124
125
126/**
127 * Toggle between showing and hiding.
128 */
129cvox.Widget.prototype.toggle = function() {
130  if (this.isActive()) {
131    this.hide();
132  } else {
133    this.show();
134  }
135};
136
137
138/**
139 * The name of the widget.
140 * @return {!Array} The message id referencing the name of the widget in an
141 * array argument form passable to cvox.ChromeVox.msgs.getMsg.apply.
142 */
143cvox.Widget.prototype.getNameMsg = goog.abstractMethod;
144
145
146/**
147 * Gets the help message for the widget.
148 * The help message succintly describes how to use the widget.
149 * @return {string} The message id referencing the help for the widget.
150 */
151cvox.Widget.prototype.getHelpMsg = goog.abstractMethod;
152
153
154/**
155 * The default widget key down handler.
156 *
157 * @param {Event} evt The keyDown event.
158 * @return {boolean} Whether or not the event was handled.
159 *
160 * @protected
161 */
162cvox.Widget.prototype.onKeyDown = function(evt) {
163  if (evt.keyCode == 27) { // Escape
164    this.hide();
165    evt.preventDefault();
166    return true;
167  } else if (evt.keyCode == 9) { // Tab
168    this.hide();
169    return true;
170  } else if (evt.keyCode == 17) {
171    cvox.ChromeVox.tts.stop();
172  }
173
174  evt.stopPropagation();
175  return true;
176};
177
178
179/**
180 * The default widget key press handler.
181 *
182 * @param {Event} evt The keyPress event.
183 * @return {boolean} Whether or not the event was handled.
184 *
185 * @protected
186 */
187cvox.Widget.prototype.onKeyPress = function(evt) {
188  return false;
189};
190/**
191 * @return {boolean} True if any widget is currently active.
192 */
193cvox.Widget.isActive = function() {
194  return (cvox.Widget.ref_ && cvox.Widget.ref_.isActive()) || false;
195};
196