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 ( |
132 if (this.index >= this.node.attributes.wordStarts[i] && | 159 var i = 0; i < this.node_.attributes.wordStarts.length; i++) { |
dmazzoni
2014/10/24 16:13:30
funny indentation?
| |
133 this.index <= this.node.attributes.wordEnds[i]) { | 160 if (this.index_ >= this.node_.attributes.wordStarts[i] && |
134 start = this.node.attributes.wordStarts[i]; | 161 this.index_ <= this.node_.attributes.wordEnds[i]) { |
135 end = this.node.attributes.wordEnds[i]; | 162 start = this.node_.attributes.wordStarts[i]; |
163 end = this.node_.attributes.wordEnds[i]; | |
136 break; | 164 break; |
137 } | 165 } |
138 } | 166 } |
139 if (goog.isDef(start) && goog.isDef(end)) | 167 if (goog.isDef(start) && goog.isDef(end)) |
140 this.index = dir == Dir.FORWARD ? end : start; | 168 newIndex = dir == Dir.FORWARD ? end : start; |
141 } else { | 169 } else { |
142 // TODO(dtseng): Figure out what to do in this case. | 170 // TODO(dtseng): Figure out what to do in this case. |
143 } | 171 } |
144 break; | 172 break; |
145 case Movement.DIRECTIONAL: | 173 case Movement.DIRECTIONAL: |
146 if (this.node.role == Role.inlineTextBox) { | 174 if (this.node_.role == Role.inlineTextBox) { |
147 var start, end; | 175 var start, end; |
148 for (var i = 0; i < this.node.attributes.wordStarts.length; i++) { | 176 for ( |
149 if (this.index >= this.node.attributes.wordStarts[i] && | 177 var i = 0; i < this.node_.attributes.wordStarts.length; i++) { |
150 this.index <= this.node.attributes.wordEnds[i]) { | 178 if (this.index_ >= this.node_.attributes.wordStarts[i] && |
179 this.index_ <= this.node_.attributes.wordEnds[i]) { | |
151 var nextIndex = dir == Dir.FORWARD ? i + 1 : i - 1; | 180 var nextIndex = dir == Dir.FORWARD ? i + 1 : i - 1; |
152 start = this.node.attributes.wordStarts[nextIndex]; | 181 start = this.node_.attributes.wordStarts[nextIndex]; |
153 end = this.node.attributes.wordEnds[nextIndex]; | 182 end = this.node_.attributes.wordEnds[nextIndex]; |
154 break; | 183 break; |
155 } | 184 } |
156 } | 185 } |
157 if (goog.isDef(start)) { | 186 if (goog.isDef(start)) { |
158 this.index = start; | 187 newIndex = start; |
159 } else { | 188 } else { |
160 var node = AutomationUtil.findNextNode(this.node, dir, | 189 // The backward case is special at the beginning of nodes. |
161 AutomationPredicate.leaf); | 190 if (dir == Dir.BACKWARD && this.index_ != 0) { |
162 if (node) { | 191 this.index_ = 0; |
163 this.node = node; | 192 } else { |
164 this.index = 0; | 193 var node = AutomationUtil.findNextNode(this.node_, dir, |
165 if (dir == Dir.BACKWARD && node.role == Role.inlineTextBox) { | 194 AutomationPredicate.leaf); |
166 var starts = node.attributes.wordStarts; | 195 if (node) { |
167 this.index = starts[starts.length - 1] || 0; | 196 newNode = node; |
168 } else { | 197 newIndex = 0; |
169 // TODO(dtseng): Figure out what to do for general nodes. | 198 if (dir == Dir.BACKWARD && |
199 node.role == Role.inlineTextBox) { | |
200 var starts = node.attributes.wordStarts; | |
201 newIndex = starts[starts.length - 1] || 0; | |
202 } else { | |
203 // TODO(dtseng): Figure out what to do for general nodes. | |
204 } | |
170 } | 205 } |
171 } | 206 } |
172 } | 207 } |
173 } else { | 208 } else { |
174 // TODO(dtseng): Figure out what to do in this case. | 209 // TODO(dtseng): Figure out what to do in this case. |
175 } | 210 } |
176 } | 211 } |
177 break; | 212 break; |
178 case Unit.NODE: | 213 case Unit.NODE: |
179 switch (movement) { | 214 switch (movement) { |
180 case Movement.BOUND: | 215 case Movement.BOUND: |
181 this.index = dir == Dir.FORWARD ? this.getText().length - 1 : 0; | 216 newIndex = dir == Dir.FORWARD ? this.getText().length - 1 : 0; |
182 break; | 217 break; |
183 case Movement.DIRECTIONAL: | 218 case Movement.DIRECTIONAL: |
184 this.node = AutomationUtil.findNextNode( | 219 newNode = AutomationUtil.findNextNode( |
185 this.node, dir, AutomationPredicate.leaf) || this.node; | 220 this.node_, dir, AutomationPredicate.leaf) || this.node_; |
186 this.index = cursors.NODE_INDEX; | 221 newIndex = cursors.NODE_INDEX; |
187 break; | 222 break; |
188 } | 223 } |
189 break; | 224 break; |
190 case Unit.LINE: | 225 case Unit.LINE: |
191 this.index = 0; | 226 newIndex = 0; |
192 switch (movement) { | 227 switch (movement) { |
193 case Movement.BOUND: | 228 case Movement.BOUND: |
194 var node = this.node; | 229 newNode = AutomationUtil.findNodeUntil(this.node_, dir, |
195 node = AutomationUtil.findNodeUntil(node, dir, | |
196 AutomationPredicate.linebreak, {before: true}); | 230 AutomationPredicate.linebreak, {before: true}); |
197 this.node = node || this.node; | 231 newNode = newNode || this.node_; |
232 newIndex = | |
233 dir == Dir.FORWARD ? this.getText(newNode).length - 1 : 0; | |
198 break; | 234 break; |
199 case Movement.DIRECTIONAL: | 235 case Movement.DIRECTIONAL: |
200 var node = this.node; | 236 newNode = AutomationUtil.findNodeUntil( |
201 node = AutomationUtil.findNodeUntil( | 237 this.node_, dir, AutomationPredicate.linebreak); |
202 node, dir, AutomationPredicate.linebreak); | |
203 | 238 |
204 // We stick to the beginning of lines out of convention. | 239 // We stick to the beginning of lines out of convention. |
205 if (node && dir == Dir.BACKWARD) { | 240 if (newNode && dir == Dir.BACKWARD) { |
206 node = AutomationUtil.findNodeUntil(node, dir, | 241 newNode = AutomationUtil.findNodeUntil(newNode, dir, |
207 AutomationPredicate.linebreak, {before: true}) || node; | 242 AutomationPredicate.linebreak, {before: true}) || node; |
208 } | 243 } |
209 this.node = node || this.node; | |
210 break; | 244 break; |
211 } | 245 } |
212 break; | 246 break; |
247 default: | |
248 throw 'Unrecognized unit: ' + unit; | |
213 } | 249 } |
250 newNode = newNode || this.node_; | |
251 newIndex = goog.isDef(newIndex) ? newIndex : this.index_; | |
252 return new cursors.Cursor(newNode, newIndex); | |
214 } | 253 } |
215 }; | 254 }; |
216 | 255 |
256 /** | |
257 * Represents a range in the automation tree. There is no visible selection on | |
258 * the page caused by usage of this object. | |
259 * @param {!cursors.Cursor} start | |
260 * @param {!cursors.Cursor} end | |
261 * @constructor | |
262 */ | |
263 cursors.Range = function(start, end) { | |
264 /** @type {!cursors.Cursor} @private */ | |
265 this.start_ = start; | |
266 /** @type {!cursors.Cursor} @private */ | |
267 this.end_ = end; | |
268 }; | |
269 | |
270 /** | |
271 * Convenience method to construct a Range surrounding one node. | |
272 * @param {!AutomationNode} node | |
273 * @return {!cursors.Range} | |
274 */ | |
275 cursors.Range.fromNode = function(node) { | |
276 return new cursors.Range(cursors.Cursor.fromNode(node), | |
277 cursors.Cursor.fromNode(node)); | |
278 }; | |
279 | |
280 cursors.Range.prototype = { | |
281 /** | |
282 * Returns true if |rhs| is equal to this range. | |
283 * @param {!cursors.Range} rhs | |
284 * @return {boolean} | |
285 */ | |
286 equals: function(rhs) { | |
287 return this.start_.equals(rhs.getStart()) && | |
288 this.end_.equals(rhs.getEnd()); | |
289 }, | |
290 | |
291 /** | |
292 * Gets a cursor bounding this range. | |
293 * @param {Dir} dir Which endpoint cursor to return; Dir.FORWARD for end, | |
294 * Dir.BACKWARD for start. | |
295 * @return {!cursors.Cursor} | |
296 */ | |
297 getBound: function(dir) { | |
298 return dir == Dir.FORWARD ? this.end_ : this.start_; | |
299 }, | |
300 | |
301 /** | |
302 * @return {!cursors.Cursor} | |
303 */ | |
304 getStart: function() { | |
305 return this.start_; | |
306 }, | |
307 | |
308 /** | |
309 * @return {!cursors.Cursor} | |
310 */ | |
311 getEnd: function() { | |
312 return this.end_; | |
313 }, | |
314 | |
315 /** | |
316 * Makes a Range which has been moved from this range by the given unit and | |
317 * direction. | |
318 * @param {Unit} unit | |
319 * @param {Dir} dir | |
320 * @return {cursors.Range} | |
321 */ | |
322 move: function(unit, dir) { | |
323 var newStart = this.start_; | |
324 var newEnd = newStart; | |
325 switch (unit) { | |
326 case Unit.CHARACTER: | |
327 newStart = newStart.move(unit, Movement.BOUND, dir); | |
328 newEnd = newStart.move(unit, Movement.BOUND, Dir.FORWARD); | |
329 // Character crossed a node; collapses to the end of the node. | |
330 if (newStart.getNode() !== newEnd.getNode()) | |
331 newEnd = newStart; | |
332 break; | |
333 case Unit.WORD: | |
334 case Unit.LINE: | |
335 case Unit.NODE: | |
336 newEnd = newEnd.move(unit, Movement.DIRECTIONAL, dir); | |
337 newStart = newEnd; | |
338 newEnd = newEnd.move(unit, Movement.BOUND, Dir.FORWARD); | |
339 break; | |
340 } | |
341 return new cursors.Range(newStart, newEnd); | |
342 } | |
343 }; | |
344 | |
217 }); // goog.scope | 345 }); // goog.scope |
OLD | NEW |