//////////////////////////////////////////////////////////////////////////////// // // 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.elements { import __AS3__.vec.Vector; import flash.events.EventDispatcher; import flash.text.engine.ContentElement; import flash.text.engine.GroupElement; import flash.utils.getQualifiedClassName; import flashx.textLayout.debug.Debugging; import flashx.textLayout.debug.assert; import flashx.textLayout.tlf_internal; use namespace tlf_internal; /** * The SubParagraphGroupElement class groups FlowLeafElements together. A SubParagraphGroupElement is a child of a * ParagraphElement object and it can contain one or more FlowLeafElement objects as children. * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 * * @see FlowLeafElement * @see LinkElement * @see ParagraphElement * @see TCYElement */ public class SubParagraphGroupElement extends FlowGroupElement { private var _groupElement:GroupElement; /** @private - no listeners attached */ tlf_internal static const NO_ATTACHED_LISTENERS:uint = 0; /** @private - only internal listeners attached */ tlf_internal static const INTERNAL_ATTACHED_LISTENERS:uint = 1; /** @private - *may* be client attached listeners. */ tlf_internal static const CLIENT_ATTACHED_LISTENERS:uint = 2; /** bit field describing attached listeners */ private var _attachedListenerStatus:uint; /** Maximum precedence value @private */ tlf_internal static const kMaxSPGEPrecedence:uint = 1000; /** Minimum precedence value @private */ tlf_internal static const kMinSPGEPrecedence:uint = 0; private var _canMerge:Boolean; // true if element is bound to an external data source - generated text /** Constructor - creates a new SubParagraphGroupElement instance. * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ public function SubParagraphGroupElement() { _canMerge = true; _attachedListenerStatus = NO_ATTACHED_LISTENERS; super(); } /** @private */ public override function shallowCopy(startPos:int = 0, endPos:int = -1):FlowElement { var retFlow:SubParagraphGroupElement = super.shallowCopy(startPos, endPos) as SubParagraphGroupElement; if (_groupElement) retFlow.createContentElement(); return retFlow; } /** @private */ override tlf_internal function createContentElement():void { if (_groupElement) return; _groupElement = new GroupElement(null); CONFIG::debug { Debugging.traceFTECall(_groupElement,null,"new GroupElement",null); } for (var i:int = 0; i < numChildren; i++) { var child:FlowElement = getChildAt(i); child.createContentElement(); } if (parent) parent.insertBlockElement(this, _groupElement); } /** @private */ override tlf_internal function releaseContentElement():void { if (!canReleaseContentElement() || groupElement == null) return; for (var i:int = 0; i < numChildren; i++) { var child:FlowElement = getChildAt(i); child.releaseContentElement(); } _groupElement = null; } /** @private */ override tlf_internal function canReleaseContentElement():Boolean { return _attachedListenerStatus == NO_ATTACHED_LISTENERS; } /** * @public getter to return the precedence value of this SubParagraphGroupElement * Precedence is used to determine which SPGE element will be the container element * when two or more SPGEs of the same textLength are inside one another. * * Precedence is used to determine which SubParagraphGroupElement is the owner when two or * more elements have the same text and are embedded within each other. * * Example: SPGEs A(precedence 900), B(precedence 400), C(precedence 600) * Model Result when all wrap SpanElement "123" * * 123 * * If two or more SPGE's have the same precedence value, then the alphabetic order is used: * Example: SPGE A(precedence 400), B(precedence 400), C(precedence 600) * * 123 * * Current values for SubParagraphGroupElements are: * LinkElement - 800 * TCYElement - 100 * * If the value is not overriden by descendents of SPGE, then value is kMaxSPGEPrecedence; * @private */ tlf_internal function get precedence():uint { return kMaxSPGEPrecedence; } /** @private */ tlf_internal function get groupElement():GroupElement { return _groupElement; } /** @private */ tlf_internal function get attachedListenerStatus():int { return _attachedListenerStatus; } /** @private */ tlf_internal override function createContentAsGroup():GroupElement { return groupElement; } /** @private */ tlf_internal override function removeBlockElement(child:FlowElement, block:ContentElement):void { var idx:int = this.getChildIndex(child); groupElement.replaceElements(idx,idx+1,null); } /** @private */ tlf_internal override function insertBlockElement(child:FlowElement, block:ContentElement):void { if (groupElement) { var idx:int = this.getChildIndex(child); var gc:Vector. = new Vector.(); gc.push(block); groupElement.replaceElements(idx,idx,gc); } else { child.releaseContentElement(); var para:ParagraphElement = getParagraph(); if (para) para.createTextBlock(); } } /** @private */ tlf_internal override function hasBlockElement():Boolean { return groupElement != null; } /** @private */ override tlf_internal function setParentAndRelativeStart(newParent:FlowGroupElement,newStart:int):void { if (newParent == parent) return; // remove textElement from the parent content if (parent && parent.hasBlockElement() && groupElement) parent.removeBlockElement(this,groupElement); if (newParent && !newParent.hasBlockElement() && groupElement) newParent.createContentElement(); super.setParentAndRelativeStart(newParent,newStart); // Update the FTE ContentElement structure. If the parent has FTE elements, then create FTE elements for the leaf node // if it doesn't already have them, and add them in. If the parent does not have FTE elements, release the leaf's FTE // elements also so they match. if (parent && parent.hasBlockElement()) { if (!groupElement) createContentElement(); else parent.insertBlockElement(this,groupElement); } } /** @private */ public override function replaceChildren(beginChildIndex:int,endChildIndex:int,...rest):void { var p:ParagraphElement = this.getParagraph(); // are we replacing the last element? var oldLastLeaf:FlowLeafElement = p ? p.getLastLeaf() : null; var applyParams:Array = [beginChildIndex, endChildIndex]; super.replaceChildren.apply(this, applyParams.concat(rest)); if (p) p.ensureTerminatorAfterReplace(oldLastLeaf); } /** @private * Returns the EventDispatcher associated with this SubParagraphGroupElement instance. Use the functions * of EventDispatcher such as addEventHandler() and removeEventHandler() to * capture events that happen over this SubParagraphGroupElement instance. * * The event handling that you specify is called after this element does its processing. * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 * * @see flash.events.EventDispatcher * @see flash.text.engine.TextLineMirrorRegion */ tlf_internal function getEventMirror(statusMask:uint = CLIENT_ATTACHED_LISTENERS):EventDispatcher { if (!_groupElement) { var para:ParagraphElement = getParagraph(); if (para) para.getTextBlock(); else createContentElement(); } if (_groupElement.eventMirror == null) { _groupElement.eventMirror = new EventDispatcher(); } _attachedListenerStatus |= statusMask; return (_groupElement.eventMirror); } /** @private */ tlf_internal override function normalizeRange(normalizeStart:uint,normalizeEnd:uint):void { var idx:int = findChildIndexAtPosition(normalizeStart); if (idx != -1 && idx < numChildren) { var child:FlowElement = getChildAt(idx); normalizeStart = normalizeStart-child.parentRelativeStart; CONFIG::debug { assert(normalizeStart >= 0, "bad normalizeStart in normalizeRange"); } for (;;) { // watch out for changes in the length of the child var origChildEnd:int = child.parentRelativeStart+child.textLength; child.normalizeRange(normalizeStart,normalizeEnd-child.parentRelativeStart); var newChildEnd:int = child.parentRelativeStart+child.textLength; normalizeEnd += newChildEnd-origChildEnd; // adjust // no zero length children if (child.textLength == 0 && !child.bindableElement) replaceChildren(idx,idx+1); else if (child.mergeToPreviousIfPossible()) { var prevElement:FlowElement = this.getChildAt(idx-1); // possibly optimize the start to the length of prevelement before the merge prevElement.normalizeRange(0,prevElement.textLength); } else idx++; if (idx == numChildren) break; // next child child = getChildAt(idx); if (child.parentRelativeStart > normalizeEnd) break; normalizeStart = 0; // for the next child } } // empty subparagraphgroups not allowed after normalize if (numChildren == 0 && parent != null) { var s:SpanElement = new SpanElement(); replaceChildren(0,0,s); s.normalizeRange(0,s.textLength); } } /** @private */ tlf_internal override function canOwnFlowElement(elem:FlowElement):Boolean { // Only allow sub-paragraph group elements (with restrictions) and leaf elements if (elem is FlowLeafElement) return true; var subParagraphGroupElem:SubParagraphGroupElement = elem as SubParagraphGroupElement; if (subParagraphGroupElem) { // Sub-paragraph group elements of the same kind cannot nest, even indirectly. // For example, a link cannot contain a link, nor can it contain a TCY whose child is a link and so on. // We assume that the two trees being joined are themselves valid, so the above rule can only be violated if // a) elem's type is same as my type // b) elem's type is same as my parent's type // c) an elem's child's type is same as my type // [Note: this is valid only because we have just two kinds of sub-paragraph groups. Any more, and we'll need more // complex rules] var myClass:String = getQualifiedClassName(this); var elemClass:String = getQualifiedClassName(elem); var parentClass:String = parent ? getQualifiedClassName(parent) : null; if (elemClass == myClass || elemClass == parentClass) return false; for (var i:int=0; i