Chromium Code Reviews| 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 |