blob: 3ba258438e2bcbdbf17a977992a9cc26c6b75b0e [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package flashx.textLayout.edit
{
/**
* The ElementRange class represents the range of objects selected within a text flow.
*
* <p>The beginning elements
* (such as <code>firstLeaf</code>) are always less than or equal to the end elements (in this case, <code>lastLeaf</code>)
* for each pair of values in an element range.</p>
*
* @see flashx.textLayout.elements.TextFlow
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public class ElementRange
{
import flashx.textLayout.compose.IFlowComposer;
import flashx.textLayout.container.ContainerController;
import flashx.textLayout.debug.assert;
import flashx.textLayout.elements.ContainerFormattedElement;
import flashx.textLayout.elements.FlowLeafElement;
import flashx.textLayout.elements.ParagraphElement;
import flashx.textLayout.elements.SubParagraphGroupElementBase;
import flashx.textLayout.elements.TextFlow;
import flashx.textLayout.formats.Category;
import flashx.textLayout.formats.ITextLayoutFormat;
import flashx.textLayout.formats.TextLayoutFormat;
import flashx.textLayout.property.Property;
import flashx.textLayout.tlf_internal;
use namespace tlf_internal;
private var _absoluteStart:int;
private var _absoluteEnd:int;
private var _firstLeaf:FlowLeafElement;
private var _lastLeaf:FlowLeafElement;
private var _firstParagraph:ParagraphElement;
private var _lastParagraph:ParagraphElement;
private var _textFlow:TextFlow;
/**
* The absolute text position of the FlowLeafElement object that contains the start of the range.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function get absoluteStart():int
{
return _absoluteStart;
}
public function set absoluteStart(value:int):void
{
_absoluteStart = value;
}
/**
* The absolute text position of the FlowLeafElement object that contains the end of the range.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function get absoluteEnd():int
{
return _absoluteEnd;
}
public function set absoluteEnd(value:int):void
{
_absoluteEnd = value;
}
/**
* The FlowLeafElement object that contains the start of the range.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function get firstLeaf():FlowLeafElement
{
return _firstLeaf;
}
public function set firstLeaf(value:FlowLeafElement):void
{
_firstLeaf = value;
}
/**
* The FlowLeafElement object that contains the end of the range.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function get lastLeaf():FlowLeafElement
{
return _lastLeaf;
}
public function set lastLeaf(value:FlowLeafElement):void
{
_lastLeaf = value;
}
/**
* The ParagraphElement object that contains the start of the range.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function get firstParagraph():ParagraphElement
{
return _firstParagraph;
}
public function set firstParagraph(value:ParagraphElement):void
{
_firstParagraph = value;
}
/**
* The ParagraphElement object that contains the end of the range.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function get lastParagraph():ParagraphElement
{
return _lastParagraph;
}
public function set lastParagraph(value:ParagraphElement):void
{
_lastParagraph = value;
}
/**
* The TextFlow object that contains the range.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function get textFlow():TextFlow
{
return _textFlow;
}
public function set textFlow(value:TextFlow):void
{
_textFlow = value;
}
// This constructor function is here just to silence a compile warning in Eclipse. There
// appears to be no way to turn the warning off selectively.
/** @private */
CONFIG::debug public function ElementRange()
{
super();
}
// NOTE: We've been back and forth on this - should a range selection show null attributes or beginning of range attributes?
// After looking at this for a while I want it to show beginning of range attributes. Two main reasons
// 1. If the range contains different objects but homogoneous settings we should show the attributes.
// 2. If we show null attributes on a range selection there's no way to, for example, turn BOLD off.
// Try this at home - restore the old code. Select the entire text. Turn Bold on. Can't turn bold off.
// Please don't revert this without further discussion.
// Ideally we would have a way of figuring out which attributes are homogoneous over the selection range
// and which were not and showing, for example, a "half-checked" bold item. We'd have to work this out for all the properties.
// OLD CODE that shows null attribute settings on a range selection
// var charAttr:ICharacterFormat = selRange.begElem == selRange.endElem ? selRange.begElem.computedCharacterFormat : new CharacterFormat();
// var paraAttr:IParagraphFormat = selRange.begPara == selRange.endPara ? selRange.begPara.computedParagraphFormat : new ParagraphFormat();
/**
* The format attributes of the container displaying the range.
*
* <p>If the range spans more than one container, the format of the first container is returned.</p>
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function get containerFormat():ITextLayoutFormat
{
// see NOTE above before changing!!
var container:ContainerController;
var flowComposer:IFlowComposer = _textFlow.flowComposer;
if (flowComposer)
{
var idx:int = flowComposer.findControllerIndexAtPosition(absoluteStart);
if (idx != -1)
container = flowComposer.getControllerAt(idx);
}
return container ? container.computedFormat : _textFlow.computedFormat;
}
/**
* The format attributes of the paragraph containing the range.
*
* <p>If the range spans more than one paragraph, the format of the first paragraph is returned.</p>
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function get paragraphFormat():ITextLayoutFormat
{
// see NOTE above before changing!!
return firstParagraph.computedFormat;
}
/**
* The format attributes of the characters in the range.
*
* <p>If the range spans more than one FlowElement object, which means that more than one
* character format may exist within the range, the format of the first FlowElement object is returned.</p>
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function get characterFormat():ITextLayoutFormat
{
// see NOTE above before changing!!
return firstLeaf.computedFormat;
}
/**
* Gets the character format attributes that are common to all characters in the text range or current selection.
*
* <p>Format attributes that do not have the same value for all characters in the element range are set to
* <code>null</code> in the returned TextLayoutFormat instance.</p>
*
* @return The common character style settings
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function getCommonCharacterFormat():TextLayoutFormat
{
var leaf:FlowLeafElement = firstLeaf;
var attr:TextLayoutFormat = new TextLayoutFormat(leaf.computedFormat);
for (;;)
{
if (leaf == lastLeaf)
break;
leaf = leaf.getNextLeaf();
attr.removeClashing(leaf.computedFormat);
}
return Property.extractInCategory(TextLayoutFormat, TextLayoutFormat.description, attr, Category.CHARACTER, false) as TextLayoutFormat;
}
/**
* Gets the paragraph format attributes that are common to all paragraphs in the element range.
*
* <p>Format attributes that do not have the same value for all paragraphs in the element range are set to
* <code>null</code> in the returned TextLayoutFormat instance.</p>
*
* @return The common paragraph style settings
*
* @see flashx.textLayout.edit.ISelectionManager#getCommonParagraphFormat
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function getCommonParagraphFormat():TextLayoutFormat
{
var para:ParagraphElement = firstParagraph;
var attr:TextLayoutFormat = new TextLayoutFormat(para.computedFormat);
for (;;)
{
if (para == lastParagraph)
break;
para = _textFlow.findAbsoluteParagraph(para.getAbsoluteStart()+para.textLength);
attr.removeClashing(para.computedFormat);
}
return Property.extractInCategory(TextLayoutFormat,TextLayoutFormat.description,attr,Category.PARAGRAPH, false) as TextLayoutFormat;
}
/**
* Gets the container format attributes that are common to all containers in the element range.
*
* <p>Format attributes that do not have the same value for all containers in the element range are set to
* <code>null</code> in the returned TextLayoutFormat instance.</p>
*
* @return The common paragraph style settings
*
* @see flashx.textLayout.edit.ISelectionManager#getCommonParagraphFormat *
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function getCommonContainerFormat():TextLayoutFormat
{
var flowComposer:IFlowComposer = _textFlow.flowComposer;
if (!flowComposer)
return null;
var index:int = flowComposer.findControllerIndexAtPosition(this.absoluteStart);
if (index == -1)
return null;
var controller:ContainerController = flowComposer.getControllerAt(index);
var attr:TextLayoutFormat = new TextLayoutFormat(controller.computedFormat);
while (controller.absoluteStart+controller.textLength < absoluteEnd)
{
index++;
if (index == flowComposer.numControllers)
break;
controller = flowComposer.getControllerAt(index);
attr.removeClashing(controller.computedFormat);
}
return Property.extractInCategory(TextLayoutFormat,TextLayoutFormat.description,attr,Category.CONTAINER, false) as TextLayoutFormat;
}
/**
* Creates an ElementRange object.
*
* @param textFlow the text flow
* @param beginIndex absolute text position of the first character in the text range
* @param endIndex one beyond the absolute text position of the last character in the text range
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
static public function createElementRange(textFlow:TextFlow, absoluteStart:int, absoluteEnd:int):ElementRange
{
var rslt:ElementRange = new ElementRange();
if (absoluteStart == absoluteEnd)
{
rslt.absoluteStart = rslt.absoluteEnd = absoluteStart;
rslt.firstLeaf = textFlow.findLeaf(rslt.absoluteStart);
rslt.firstParagraph = rslt.firstLeaf.getParagraph();
// rslt.begContainer = rslt.endContainer = selState.textFlow.findAbsoluteContainer(rslt.begElemIdx);
adjustForLeanLeft(rslt);
rslt.lastLeaf = rslt.firstLeaf;
rslt.lastParagraph = rslt.firstParagraph;
}
else
{
// order the selection points
if (absoluteStart < absoluteEnd)
{
rslt.absoluteStart = absoluteStart;
rslt.absoluteEnd = absoluteEnd;
}
else
{
rslt.absoluteStart = absoluteEnd;
rslt.absoluteEnd = absoluteStart;
}
rslt.firstLeaf = textFlow.findLeaf(rslt.absoluteStart);
rslt.lastLeaf = textFlow.findLeaf(rslt.absoluteEnd);
// back up one element if the end of the selection is the start of an element
// otherwise a block selection of a span looks like it includes discreet selection ranges
if (((rslt.lastLeaf == null) && (rslt.absoluteEnd == textFlow.textLength)) || (rslt.absoluteEnd == rslt.lastLeaf.getAbsoluteStart()))
rslt.lastLeaf = textFlow.findLeaf(rslt.absoluteEnd-1);
rslt.firstParagraph = rslt.firstLeaf.getParagraph();
rslt.lastParagraph = rslt.lastLeaf.getParagraph();
// rslt.begContainer = selState.textFlow.findAbsoluteContainer(rslt.begElemIdx);
// rslt.endContainer = selState.textFlow.findAbsoluteContainer(rslt.endElemIdx);
// if (rslt.endElemIdx == rslt.endContainer.relativeStart)
// rslt.endContainer = rslt.endContainer.preventextContainer;
// if the end of the range includes the next to last character in a paragraph
// expand it to include the paragraph teriminate character
if (rslt.absoluteEnd == rslt.lastParagraph.getAbsoluteStart() + rslt.lastParagraph.textLength - 1)
{
rslt.absoluteEnd++
rslt.lastLeaf = rslt.lastParagraph.getLastLeaf();
}
}
rslt.textFlow = textFlow;
return rslt;
}
static private function adjustForLeanLeft(rslt:ElementRange):void
{
// If we're at the start of a leaf element, look to the previous leaf element and see if it shares the same
// parent. If so, we're going to move the selection to the end of the previous element so it takes on
// the formatting of the character to the left. We don't want to do this if the previous element is in
// a different character, across link or tcy boundaries, etc.
if (rslt.firstLeaf.getAbsoluteStart() == rslt.absoluteStart)
{
var previousNode:FlowLeafElement = rslt.firstLeaf.getPreviousLeaf(rslt.firstParagraph);
if (previousNode && previousNode.getParagraph() == rslt.firstLeaf.getParagraph())
{
if((!(previousNode.parent is SubParagraphGroupElementBase) || (previousNode.parent as SubParagraphGroupElementBase).acceptTextAfter())
&& (!(rslt.firstLeaf.parent is SubParagraphGroupElementBase) || previousNode.parent === rslt.firstLeaf.parent))
rslt.firstLeaf = previousNode;
}
}
}
}
}