OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * @fileoverview Classes related to cursors that point to and select parts of | 6 * @fileoverview Classes related to cursors that point to and select parts of |
7 * the automation tree. | 7 * the automation tree. |
8 */ | 8 */ |
9 | 9 |
10 goog.provide('cursors.Cursor'); | 10 goog.provide('cursors.Cursor'); |
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
59 /** | 59 /** |
60 * Represents a position within the automation tree. | 60 * Represents a position within the automation tree. |
61 * @constructor | 61 * @constructor |
62 * @param {!AutomationNode} node | 62 * @param {!AutomationNode} node |
63 * @param {number} index A 0-based index into either this cursor's name or value | 63 * @param {number} index A 0-based index into either this cursor's name or value |
64 * attribute. Relies on the fact that a node has either a name or a value but | 64 * attribute. Relies on the fact that a node has either a name or a value but |
65 * not both. An index of |cursors.NODE_INDEX| means the node as a whole is | 65 * not both. An index of |cursors.NODE_INDEX| means the node as a whole is |
66 * pointed to and covers the case where the accessible text is empty. | 66 * pointed to and covers the case where the accessible text is empty. |
67 */ | 67 */ |
68 cursors.Cursor = function(node, index) { | 68 cursors.Cursor = function(node, index) { |
69 /** @type {!AutomationNode} */ | 69 /** @type {!AutomationNode} @private */ |
70 this.node = node; | 70 this.node_ = node; |
71 /** @type {number} */ | 71 /** @type {number} @private */ |
72 this.index = index; | 72 this.index_ = index; |
| 73 }; |
| 74 |
| 75 /** |
| 76 * Convenience method to construct a Cursor from a node. |
| 77 * @param {!AutomationNode} node |
| 78 * @return {!cursors.Cursor} |
| 79 */ |
| 80 cursors.Cursor.fromNode = function(node) { |
| 81 return new cursors.Cursor(node, cursors.NODE_INDEX); |
73 }; | 82 }; |
74 | 83 |
75 cursors.Cursor.prototype = { | 84 cursors.Cursor.prototype = { |
76 /** | 85 /** |
77 * Returns a copy of this cursor. | 86 * Returns true if |rhs| is equal to this cursor. |
78 * @return {cursors.Cursor} | 87 * @param {!cursors.Cursor} rhs |
| 88 * @return {boolean} |
79 */ | 89 */ |
80 clone: function() { | 90 equals: function(rhs) { |
81 return new cursors.Cursor(this.node, this.index); | 91 return this.node_ === rhs.getNode() && |
| 92 this.index_ === rhs.getIndex(); |
82 }, | 93 }, |
83 | 94 |
84 /** | 95 /** |
| 96 * @return {!AutomationNode} |
| 97 */ |
| 98 getNode: function() { |
| 99 return this.node_; |
| 100 }, |
| 101 |
| 102 /** |
| 103 * @return {number} |
| 104 */ |
| 105 getIndex: function() { |
| 106 return this.index_; |
| 107 }, |
| 108 |
| 109 /** |
85 * Gets the accessible text of the node associated with this cursor. | 110 * Gets the accessible text of the node associated with this cursor. |
86 * | 111 * |
87 * Note that only one of |name| or |value| attribute is ever nonempty on an | 112 * Note that only one of |name| or |value| attribute is ever nonempty on an |
88 * automation node. If either contains whitespace, we still treat it as we do | 113 * automation node. If either contains whitespace, we still treat it as we do |
89 * for a nonempty string. | 114 * for a nonempty string. |
90 * @param {!AutomationNode=} opt_node Use this node rather than this cursor's | 115 * @param {!AutomationNode=} opt_node Use this node rather than this cursor's |
91 * node. | 116 * node. |
92 * @return {string} | 117 * @return {string} |
93 */ | 118 */ |
94 getText: function(opt_node) { | 119 getText: function(opt_node) { |
95 var node = opt_node || this.node; | 120 var node = opt_node || this.node_; |
96 return node.attributes.name || node.attributes.value || ''; | 121 return node.attributes.name || node.attributes.value || ''; |
97 }, | 122 }, |
98 | 123 |
99 /** | 124 /** |
100 * Moves this cursor by the unit in the given direction using the given | 125 * Makes a Cursor which has been moved from this cursor by the unit in the |
101 * movement type. | 126 * given direction using the given movement type. |
102 * @param {Unit} unit | 127 * @param {Unit} unit |
103 * @param {Movement} movement | 128 * @param {Movement} movement |
104 * @param {Dir} dir | 129 * @param {Dir} dir |
| 130 * @return {!cursors.Cursor} The moved cursor. |
105 */ | 131 */ |
106 move: function(unit, movement, dir) { | 132 move: function(unit, movement, dir) { |
| 133 var newNode, newIndex; |
107 switch (unit) { | 134 switch (unit) { |
108 case Unit.CHARACTER: | 135 case Unit.CHARACTER: |
109 // BOUND and DIRECTIONAL are the same for characters. | 136 // BOUND and DIRECTIONAL are the same for characters. |
110 var node = this.node; | 137 var node = this.node_; |
111 var nextIndex = dir == Dir.FORWARD ? this.index + 1 : this.index - 1; | 138 var nextIndex = dir == Dir.FORWARD ? this.index_ + 1 : this.index_ - 1; |
112 if (nextIndex < 0 || nextIndex >= this.getText().length) { | 139 if (nextIndex < 0 || nextIndex >= this.getText().length) { |
113 node = AutomationUtil.findNextNode( | 140 node = AutomationUtil.findNextNode( |
114 node, dir, AutomationPredicate.leaf); | 141 node, dir, AutomationPredicate.leaf); |
115 if (node) { | 142 if (node) { |
116 nextIndex = | 143 nextIndex = |
117 dir == Dir.FORWARD ? 0 : this.getText(node).length - 1; | 144 dir == Dir.FORWARD ? 0 : this.getText(node).length - 1; |
118 } else { | 145 } else { |
119 node = this.node; | 146 node = this.node_; |
120 nextIndex = this.index; | 147 nextIndex = this.index_; |
121 } | 148 } |
122 } | 149 } |
123 this.node = node; | 150 newNode = node; |
124 this.index = nextIndex; | 151 newIndex = nextIndex; |
125 break; | 152 break; |
126 case Unit.WORD: | 153 case Unit.WORD: |
127 switch (movement) { | 154 switch (movement) { |
128 case Movement.BOUND: | 155 case Movement.BOUND: |
129 if (this.node.role == Role.inlineTextBox) { | 156 if (this.node_.role == Role.inlineTextBox) { |
130 var start, end; | 157 var start, end; |
131 for (var i = 0; i < this.node.attributes.wordStarts.length; i++) { | 158 for (var i = 0; |
132 if (this.index >= this.node.attributes.wordStarts[i] && | 159 i < this.node_.attributes.wordStarts.length; |
133 this.index <= this.node.attributes.wordEnds[i]) { | 160 i++) { |
134 start = this.node.attributes.wordStarts[i]; | 161 if (this.index_ >= this.node_.attributes.wordStarts[i] && |
135 end = this.node.attributes.wordEnds[i]; | 162 this.index_ <= this.node_.attributes.wordEnds[i]) { |
| 163 start = this.node_.attributes.wordStarts[i]; |
| 164 end = this.node_.attributes.wordEnds[i]; |
136 break; | 165 break; |
137 } | 166 } |
138 } | 167 } |
139 if (goog.isDef(start) && goog.isDef(end)) | 168 if (goog.isDef(start) && goog.isDef(end)) |
140 this.index = dir == Dir.FORWARD ? end : start; | 169 newIndex = dir == Dir.FORWARD ? end : start; |
141 } else { | 170 } else { |
142 // TODO(dtseng): Figure out what to do in this case. | 171 // TODO(dtseng): Figure out what to do in this case. |
143 } | 172 } |
144 break; | 173 break; |
145 case Movement.DIRECTIONAL: | 174 case Movement.DIRECTIONAL: |
146 if (this.node.role == Role.inlineTextBox) { | 175 if (this.node_.role == Role.inlineTextBox) { |
147 var start, end; | 176 var start, end; |
148 for (var i = 0; i < this.node.attributes.wordStarts.length; i++) { | 177 for (var i = 0; |
149 if (this.index >= this.node.attributes.wordStarts[i] && | 178 i < this.node_.attributes.wordStarts.length; |
150 this.index <= this.node.attributes.wordEnds[i]) { | 179 i++) { |
| 180 if (this.index_ >= this.node_.attributes.wordStarts[i] && |
| 181 this.index_ <= this.node_.attributes.wordEnds[i]) { |
151 var nextIndex = dir == Dir.FORWARD ? i + 1 : i - 1; | 182 var nextIndex = dir == Dir.FORWARD ? i + 1 : i - 1; |
152 start = this.node.attributes.wordStarts[nextIndex]; | 183 start = this.node_.attributes.wordStarts[nextIndex]; |
153 end = this.node.attributes.wordEnds[nextIndex]; | 184 end = this.node_.attributes.wordEnds[nextIndex]; |
154 break; | 185 break; |
155 } | 186 } |
156 } | 187 } |
157 if (goog.isDef(start)) { | 188 if (goog.isDef(start)) { |
158 this.index = start; | 189 newIndex = start; |
159 } else { | 190 } else { |
160 var node = AutomationUtil.findNextNode(this.node, dir, | 191 // The backward case is special at the beginning of nodes. |
161 AutomationPredicate.leaf); | 192 if (dir == Dir.BACKWARD && this.index_ != 0) { |
162 if (node) { | 193 this.index_ = 0; |
163 this.node = node; | 194 } else { |
164 this.index = 0; | 195 var node = AutomationUtil.findNextNode(this.node_, dir, |
165 if (dir == Dir.BACKWARD && node.role == Role.inlineTextBox) { | 196 AutomationPredicate.leaf); |
166 var starts = node.attributes.wordStarts; | 197 if (node) { |
167 this.index = starts[starts.length - 1] || 0; | 198 newNode = node; |
168 } else { | 199 newIndex = 0; |
169 // TODO(dtseng): Figure out what to do for general nodes. | 200 if (dir == Dir.BACKWARD && |
| 201 node.role == Role.inlineTextBox) { |
| 202 var starts = node.attributes.wordStarts; |
| 203 newIndex = starts[starts.length - 1] || 0; |
| 204 } else { |
| 205 // TODO(dtseng): Figure out what to do for general nodes. |
| 206 } |
170 } | 207 } |
171 } | 208 } |
172 } | 209 } |
173 } else { | 210 } else { |
174 // TODO(dtseng): Figure out what to do in this case. | 211 // TODO(dtseng): Figure out what to do in this case. |
175 } | 212 } |
176 } | 213 } |
177 break; | 214 break; |
178 case Unit.NODE: | 215 case Unit.NODE: |
179 switch (movement) { | 216 switch (movement) { |
180 case Movement.BOUND: | 217 case Movement.BOUND: |
181 this.index = dir == Dir.FORWARD ? this.getText().length - 1 : 0; | 218 newIndex = dir == Dir.FORWARD ? this.getText().length - 1 : 0; |
182 break; | 219 break; |
183 case Movement.DIRECTIONAL: | 220 case Movement.DIRECTIONAL: |
184 this.node = AutomationUtil.findNextNode( | 221 newNode = AutomationUtil.findNextNode( |
185 this.node, dir, AutomationPredicate.leaf) || this.node; | 222 this.node_, dir, AutomationPredicate.leaf) || this.node_; |
186 this.index = cursors.NODE_INDEX; | 223 newIndex = cursors.NODE_INDEX; |
187 break; | 224 break; |
188 } | 225 } |
189 break; | 226 break; |
190 case Unit.LINE: | 227 case Unit.LINE: |
191 this.index = 0; | 228 newIndex = 0; |
192 switch (movement) { | 229 switch (movement) { |
193 case Movement.BOUND: | 230 case Movement.BOUND: |
194 var node = this.node; | 231 newNode = AutomationUtil.findNodeUntil(this.node_, dir, |
195 node = AutomationUtil.findNodeUntil(node, dir, | |
196 AutomationPredicate.linebreak, {before: true}); | 232 AutomationPredicate.linebreak, {before: true}); |
197 this.node = node || this.node; | 233 newNode = newNode || this.node_; |
| 234 newIndex = |
| 235 dir == Dir.FORWARD ? this.getText(newNode).length - 1 : 0; |
198 break; | 236 break; |
199 case Movement.DIRECTIONAL: | 237 case Movement.DIRECTIONAL: |
200 var node = this.node; | 238 newNode = AutomationUtil.findNodeUntil( |
201 node = AutomationUtil.findNodeUntil( | 239 this.node_, dir, AutomationPredicate.linebreak); |
202 node, dir, AutomationPredicate.linebreak); | |
203 | 240 |
204 // We stick to the beginning of lines out of convention. | 241 // We stick to the beginning of lines out of convention. |
205 if (node && dir == Dir.BACKWARD) { | 242 if (newNode && dir == Dir.BACKWARD) { |
206 node = AutomationUtil.findNodeUntil(node, dir, | 243 newNode = AutomationUtil.findNodeUntil(newNode, dir, |
207 AutomationPredicate.linebreak, {before: true}) || node; | 244 AutomationPredicate.linebreak, {before: true}) || node; |
208 } | 245 } |
209 this.node = node || this.node; | |
210 break; | 246 break; |
211 } | 247 } |
212 break; | 248 break; |
| 249 default: |
| 250 throw 'Unrecognized unit: ' + unit; |
213 } | 251 } |
| 252 newNode = newNode || this.node_; |
| 253 newIndex = goog.isDef(newIndex) ? newIndex : this.index_; |
| 254 return new cursors.Cursor(newNode, newIndex); |
214 } | 255 } |
215 }; | 256 }; |
216 | 257 |
| 258 /** |
| 259 * Represents a range in the automation tree. There is no visible selection on |
| 260 * the page caused by usage of this object. |
| 261 * It is assumed that the caller provides |start| and |end| in document order. |
| 262 * @param {!cursors.Cursor} start |
| 263 * @param {!cursors.Cursor} end |
| 264 * @constructor |
| 265 */ |
| 266 cursors.Range = function(start, end) { |
| 267 /** @type {!cursors.Cursor} @private */ |
| 268 this.start_ = start; |
| 269 /** @type {!cursors.Cursor} @private */ |
| 270 this.end_ = end; |
| 271 }; |
| 272 |
| 273 /** |
| 274 * Convenience method to construct a Range surrounding one node. |
| 275 * @param {!AutomationNode} node |
| 276 * @return {!cursors.Range} |
| 277 */ |
| 278 cursors.Range.fromNode = function(node) { |
| 279 var cursor = cursors.Cursor.fromNode(node); |
| 280 return new cursors.Range(cursor, cursor); |
| 281 }; |
| 282 |
| 283 cursors.Range.prototype = { |
| 284 /** |
| 285 * Returns true if |rhs| is equal to this range. |
| 286 * @param {!cursors.Range} rhs |
| 287 * @return {boolean} |
| 288 */ |
| 289 equals: function(rhs) { |
| 290 return this.start_.equals(rhs.getStart()) && |
| 291 this.end_.equals(rhs.getEnd()); |
| 292 }, |
| 293 |
| 294 /** |
| 295 * Gets a cursor bounding this range. |
| 296 * @param {Dir} dir Which endpoint cursor to return; Dir.FORWARD for end, |
| 297 * Dir.BACKWARD for start. |
| 298 * @return {!cursors.Cursor} |
| 299 */ |
| 300 getBound: function(dir) { |
| 301 return dir == Dir.FORWARD ? this.end_ : this.start_; |
| 302 }, |
| 303 |
| 304 /** |
| 305 * @return {!cursors.Cursor} |
| 306 */ |
| 307 getStart: function() { |
| 308 return this.start_; |
| 309 }, |
| 310 |
| 311 /** |
| 312 * @return {!cursors.Cursor} |
| 313 */ |
| 314 getEnd: function() { |
| 315 return this.end_; |
| 316 }, |
| 317 |
| 318 /** |
| 319 * Makes a Range which has been moved from this range by the given unit and |
| 320 * direction. |
| 321 * @param {Unit} unit |
| 322 * @param {Dir} dir |
| 323 * @return {cursors.Range} |
| 324 */ |
| 325 move: function(unit, dir) { |
| 326 var newStart = this.start_; |
| 327 var newEnd = newStart; |
| 328 switch (unit) { |
| 329 case Unit.CHARACTER: |
| 330 newStart = newStart.move(unit, Movement.BOUND, dir); |
| 331 newEnd = newStart.move(unit, Movement.BOUND, Dir.FORWARD); |
| 332 // Character crossed a node; collapses to the end of the node. |
| 333 if (newStart.getNode() !== newEnd.getNode()) |
| 334 newEnd = newStart; |
| 335 break; |
| 336 case Unit.WORD: |
| 337 case Unit.LINE: |
| 338 case Unit.NODE: |
| 339 newEnd = newEnd.move(unit, Movement.DIRECTIONAL, dir); |
| 340 newStart = newEnd; |
| 341 newEnd = newEnd.move(unit, Movement.BOUND, Dir.FORWARD); |
| 342 break; |
| 343 } |
| 344 return new cursors.Range(newStart, newEnd); |
| 345 } |
| 346 }; |
| 347 |
217 }); // goog.scope | 348 }); // goog.scope |
OLD | NEW |