1//////////////////////////////////////////////////////////////////////////////// 2// 3// ADOBE SYSTEMS INCORPORATED 4// Copyright 2007-2010 Adobe Systems Incorporated 5// All Rights Reserved. 6// 7// NOTICE: Adobe permits you to use, modify, and distribute this file 8// in accordance with the terms of the license agreement accompanying it. 9// 10//////////////////////////////////////////////////////////////////////////////// 11package flashx.textLayout.edit 12{ 13 14/** 15 * The ElementRange class represents the range of objects selected within a text flow. 16 * 17 * <p>The beginning elements 18 * (such as <code>firstLeaf</code>) are always less than or equal to the end elements (in this case, <code>lastLeaf</code>) 19 * for each pair of values in an element range.</p> 20 * 21 * @see flashx.textLayout.elements.TextFlow 22 * 23 * @playerversion Flash 10 24 * @playerversion AIR 1.5 25 * @langversion 3.0 26 */ 27public class ElementRange 28{ 29 import flashx.textLayout.compose.IFlowComposer; 30 import flashx.textLayout.container.ContainerController; 31 import flashx.textLayout.debug.assert; 32 import flashx.textLayout.elements.ContainerFormattedElement; 33 import flashx.textLayout.elements.FlowLeafElement; 34 import flashx.textLayout.elements.ParagraphElement; 35 import flashx.textLayout.elements.SubParagraphGroupElementBase; 36 import flashx.textLayout.elements.TextFlow; 37 import flashx.textLayout.formats.Category; 38 import flashx.textLayout.formats.ITextLayoutFormat; 39 import flashx.textLayout.formats.TextLayoutFormat; 40 import flashx.textLayout.property.Property; 41 import flashx.textLayout.tlf_internal; 42 43 use namespace tlf_internal; 44 45 private var _absoluteStart:int; 46 private var _absoluteEnd:int; 47 private var _firstLeaf:FlowLeafElement; 48 private var _lastLeaf:FlowLeafElement; 49 private var _firstParagraph:ParagraphElement; 50 private var _lastParagraph:ParagraphElement; 51 private var _textFlow:TextFlow; 52 53 /** 54 * The absolute text position of the FlowLeafElement object that contains the start of the range. 55 * 56 * @playerversion Flash 10 57 * @playerversion AIR 1.5 58 * @langversion 3.0 59 */ 60 public function get absoluteStart():int 61 { 62 return _absoluteStart; 63 } 64 public function set absoluteStart(value:int):void 65 { 66 _absoluteStart = value; 67 } 68 69 /** 70 * The absolute text position of the FlowLeafElement object that contains the end of the range. 71 * 72 * @playerversion Flash 10 73 * @playerversion AIR 1.5 74 * @langversion 3.0 75 */ 76 public function get absoluteEnd():int 77 { 78 return _absoluteEnd; 79 } 80 public function set absoluteEnd(value:int):void 81 { 82 _absoluteEnd = value; 83 } 84 85 /** 86 * The FlowLeafElement object that contains the start of the range. 87 * 88 * @playerversion Flash 10 89 * @playerversion AIR 1.5 90 * @langversion 3.0 91 */ 92 public function get firstLeaf():FlowLeafElement 93 { 94 return _firstLeaf; 95 } 96 public function set firstLeaf(value:FlowLeafElement):void 97 { 98 _firstLeaf = value; 99 } 100 101 /** 102 * The FlowLeafElement object that contains the end of the range. 103 * 104 * @playerversion Flash 10 105 * @playerversion AIR 1.5 106 * @langversion 3.0 107 */ 108 public function get lastLeaf():FlowLeafElement 109 { 110 return _lastLeaf; 111 } 112 public function set lastLeaf(value:FlowLeafElement):void 113 { 114 _lastLeaf = value; 115 } 116 117 /** 118 * The ParagraphElement object that contains the start of the range. 119 * 120 * @playerversion Flash 10 121 * @playerversion AIR 1.5 122 * @langversion 3.0 123 */ 124 public function get firstParagraph():ParagraphElement 125 { 126 return _firstParagraph; 127 } 128 public function set firstParagraph(value:ParagraphElement):void 129 { 130 _firstParagraph = value; 131 } 132 133 /** 134 * The ParagraphElement object that contains the end of the range. 135 * 136 * @playerversion Flash 10 137 * @playerversion AIR 1.5 138 * @langversion 3.0 139 */ 140 public function get lastParagraph():ParagraphElement 141 { 142 return _lastParagraph; 143 } 144 public function set lastParagraph(value:ParagraphElement):void 145 { 146 _lastParagraph = value; 147 } 148 149 /** 150 * The TextFlow object that contains the range. 151 * 152 * @playerversion Flash 10 153 * @playerversion AIR 1.5 154 * @langversion 3.0 155 */ 156 public function get textFlow():TextFlow 157 { 158 return _textFlow; 159 } 160 public function set textFlow(value:TextFlow):void 161 { 162 _textFlow = value; 163 } 164 165 // This constructor function is here just to silence a compile warning in Eclipse. There 166 // appears to be no way to turn the warning off selectively. 167 /** @private */ 168 CONFIG::debug public function ElementRange() 169 { 170 super(); 171 } 172 173 // NOTE: We've been back and forth on this - should a range selection show null attributes or beginning of range attributes? 174 // After looking at this for a while I want it to show beginning of range attributes. Two main reasons 175 // 1. If the range contains different objects but homogoneous settings we should show the attributes. 176 // 2. If we show null attributes on a range selection there's no way to, for example, turn BOLD off. 177 // Try this at home - restore the old code. Select the entire text. Turn Bold on. Can't turn bold off. 178 // Please don't revert this without further discussion. 179 // Ideally we would have a way of figuring out which attributes are homogoneous over the selection range 180 // and which were not and showing, for example, a "half-checked" bold item. We'd have to work this out for all the properties. 181 182 // OLD CODE that shows null attribute settings on a range selection 183 // var charAttr:ICharacterFormat = selRange.begElem == selRange.endElem ? selRange.begElem.computedCharacterFormat : new CharacterFormat(); 184 // var paraAttr:IParagraphFormat = selRange.begPara == selRange.endPara ? selRange.begPara.computedParagraphFormat : new ParagraphFormat(); 185 186 187 /** 188 * The format attributes of the container displaying the range. 189 * 190 * <p>If the range spans more than one container, the format of the first container is returned.</p> 191 * 192 * @playerversion Flash 10 193 * @playerversion AIR 1.5 194 * @langversion 3.0 195 */ 196 public function get containerFormat():ITextLayoutFormat 197 { 198 // see NOTE above before changing!! 199 var container:ContainerController; 200 var flowComposer:IFlowComposer = _textFlow.flowComposer; 201 if (flowComposer) 202 { 203 var idx:int = flowComposer.findControllerIndexAtPosition(absoluteStart); 204 if (idx != -1) 205 container = flowComposer.getControllerAt(idx); 206 } 207 return container ? container.computedFormat : _textFlow.computedFormat; 208 } 209 210 /** 211 * The format attributes of the paragraph containing the range. 212 * 213 * <p>If the range spans more than one paragraph, the format of the first paragraph is returned.</p> 214 * 215 * @playerversion Flash 10 216 * @playerversion AIR 1.5 217 * @langversion 3.0 218 */ 219 public function get paragraphFormat():ITextLayoutFormat 220 { 221 // see NOTE above before changing!! 222 return firstParagraph.computedFormat; 223 } 224 225 /** 226 * The format attributes of the characters in the range. 227 * 228 * <p>If the range spans more than one FlowElement object, which means that more than one 229 * character format may exist within the range, the format of the first FlowElement object is returned.</p> 230 * 231 * @playerversion Flash 10 232 * @playerversion AIR 1.5 233 * @langversion 3.0 234 */ 235 public function get characterFormat():ITextLayoutFormat 236 { 237 // see NOTE above before changing!! 238 return firstLeaf.computedFormat; 239 } 240 241 /** 242 * Gets the character format attributes that are common to all characters in the text range or current selection. 243 * 244 * <p>Format attributes that do not have the same value for all characters in the element range are set to 245 * <code>null</code> in the returned TextLayoutFormat instance.</p> 246 * 247 * @return The common character style settings 248 * 249 * @playerversion Flash 10 250 * @playerversion AIR 1.5 251 * @langversion 3.0 252 */ 253 public function getCommonCharacterFormat():TextLayoutFormat 254 { 255 var leaf:FlowLeafElement = firstLeaf; 256 var attr:TextLayoutFormat = new TextLayoutFormat(leaf.computedFormat); 257 258 for (;;) 259 { 260 if (leaf == lastLeaf) 261 break; 262 leaf = leaf.getNextLeaf(); 263 attr.removeClashing(leaf.computedFormat); 264 } 265 266 return Property.extractInCategory(TextLayoutFormat, TextLayoutFormat.description, attr, Category.CHARACTER, false) as TextLayoutFormat; 267 } 268 269 /** 270 * Gets the paragraph format attributes that are common to all paragraphs in the element range. 271 * 272 * <p>Format attributes that do not have the same value for all paragraphs in the element range are set to 273 * <code>null</code> in the returned TextLayoutFormat instance.</p> 274 * 275 * @return The common paragraph style settings 276 * 277 * @see flashx.textLayout.edit.ISelectionManager#getCommonParagraphFormat 278 * 279 * @playerversion Flash 10 280 * @playerversion AIR 1.5 281 * @langversion 3.0 282 */ 283 public function getCommonParagraphFormat():TextLayoutFormat 284 { 285 var para:ParagraphElement = firstParagraph; 286 var attr:TextLayoutFormat = new TextLayoutFormat(para.computedFormat); 287 for (;;) 288 { 289 if (para == lastParagraph) 290 break; 291 para = _textFlow.findAbsoluteParagraph(para.getAbsoluteStart()+para.textLength); 292 attr.removeClashing(para.computedFormat); 293 } 294 return Property.extractInCategory(TextLayoutFormat,TextLayoutFormat.description,attr,Category.PARAGRAPH, false) as TextLayoutFormat; 295 } 296 297 /** 298 * Gets the container format attributes that are common to all containers in the element range. 299 * 300 * <p>Format attributes that do not have the same value for all containers in the element range are set to 301 * <code>null</code> in the returned TextLayoutFormat instance.</p> 302 * 303 * @return The common paragraph style settings 304 * 305 * @see flashx.textLayout.edit.ISelectionManager#getCommonParagraphFormat * 306 * @playerversion Flash 10 307 * @playerversion AIR 1.5 308 * @langversion 3.0 309 */ 310 public function getCommonContainerFormat():TextLayoutFormat 311 { 312 var flowComposer:IFlowComposer = _textFlow.flowComposer; 313 if (!flowComposer) 314 return null; 315 316 var index:int = flowComposer.findControllerIndexAtPosition(this.absoluteStart); 317 if (index == -1) 318 return null; 319 var controller:ContainerController = flowComposer.getControllerAt(index); 320 var attr:TextLayoutFormat = new TextLayoutFormat(controller.computedFormat); 321 while (controller.absoluteStart+controller.textLength < absoluteEnd) 322 { 323 index++; 324 if (index == flowComposer.numControllers) 325 break; 326 controller = flowComposer.getControllerAt(index); 327 attr.removeClashing(controller.computedFormat); 328 } 329 330 return Property.extractInCategory(TextLayoutFormat,TextLayoutFormat.description,attr,Category.CONTAINER, false) as TextLayoutFormat; 331 } 332 333 /** 334 * Creates an ElementRange object. 335 * 336 * @param textFlow the text flow 337 * @param beginIndex absolute text position of the first character in the text range 338 * @param endIndex one beyond the absolute text position of the last character in the text range 339 * 340 * @playerversion Flash 10 341 * @playerversion AIR 1.5 342 * @langversion 3.0 343 */ 344 static public function createElementRange(textFlow:TextFlow, absoluteStart:int, absoluteEnd:int):ElementRange 345 { 346 var rslt:ElementRange = new ElementRange(); 347 if (absoluteStart == absoluteEnd) 348 { 349 rslt.absoluteStart = rslt.absoluteEnd = absoluteStart; 350 rslt.firstLeaf = textFlow.findLeaf(rslt.absoluteStart); 351 rslt.firstParagraph = rslt.firstLeaf.getParagraph(); 352 // rslt.begContainer = rslt.endContainer = selState.textFlow.findAbsoluteContainer(rslt.begElemIdx); 353 adjustForLeanLeft(rslt); 354 rslt.lastLeaf = rslt.firstLeaf; 355 rslt.lastParagraph = rslt.firstParagraph; 356 } 357 else 358 { 359 // order the selection points 360 if (absoluteStart < absoluteEnd) 361 { 362 rslt.absoluteStart = absoluteStart; 363 rslt.absoluteEnd = absoluteEnd; 364 } 365 else 366 { 367 rslt.absoluteStart = absoluteEnd; 368 rslt.absoluteEnd = absoluteStart; 369 } 370 rslt.firstLeaf = textFlow.findLeaf(rslt.absoluteStart); 371 rslt.lastLeaf = textFlow.findLeaf(rslt.absoluteEnd); 372 // back up one element if the end of the selection is the start of an element 373 // otherwise a block selection of a span looks like it includes discreet selection ranges 374 if (((rslt.lastLeaf == null) && (rslt.absoluteEnd == textFlow.textLength)) || (rslt.absoluteEnd == rslt.lastLeaf.getAbsoluteStart())) 375 rslt.lastLeaf = textFlow.findLeaf(rslt.absoluteEnd-1); 376 377 rslt.firstParagraph = rslt.firstLeaf.getParagraph(); 378 rslt.lastParagraph = rslt.lastLeaf.getParagraph(); 379 380 // rslt.begContainer = selState.textFlow.findAbsoluteContainer(rslt.begElemIdx); 381 // rslt.endContainer = selState.textFlow.findAbsoluteContainer(rslt.endElemIdx); 382 // if (rslt.endElemIdx == rslt.endContainer.relativeStart) 383 // rslt.endContainer = rslt.endContainer.preventextContainer; 384 385 // if the end of the range includes the next to last character in a paragraph 386 // expand it to include the paragraph teriminate character 387 if (rslt.absoluteEnd == rslt.lastParagraph.getAbsoluteStart() + rslt.lastParagraph.textLength - 1) 388 { 389 rslt.absoluteEnd++ 390 rslt.lastLeaf = rslt.lastParagraph.getLastLeaf(); 391 } 392 393 } 394 rslt.textFlow = textFlow; 395 396 return rslt; 397 } 398 399 static private function adjustForLeanLeft(rslt:ElementRange):void 400 { 401 // If we're at the start of a leaf element, look to the previous leaf element and see if it shares the same 402 // parent. If so, we're going to move the selection to the end of the previous element so it takes on 403 // the formatting of the character to the left. We don't want to do this if the previous element is in 404 // a different character, across link or tcy boundaries, etc. 405 if (rslt.firstLeaf.getAbsoluteStart() == rslt.absoluteStart) 406 { 407 var previousNode:FlowLeafElement = rslt.firstLeaf.getPreviousLeaf(rslt.firstParagraph); 408 if (previousNode && previousNode.getParagraph() == rslt.firstLeaf.getParagraph()) 409 { 410 if((!(previousNode.parent is SubParagraphGroupElementBase) || (previousNode.parent as SubParagraphGroupElementBase).acceptTextAfter()) 411 && (!(rslt.firstLeaf.parent is SubParagraphGroupElementBase) || previousNode.parent === rslt.firstLeaf.parent)) 412 rslt.firstLeaf = previousNode; 413 } 414 415 } 416 } 417} 418}