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}