//////////////////////////////////////////////////////////////////////////////// // // 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.operations { import flashx.textLayout.debug.assert; import flashx.textLayout.edit.SelectionState; import flashx.textLayout.elements.FlowElement; import flashx.textLayout.elements.FlowGroupElement; import flashx.textLayout.elements.SpanElement; import flashx.textLayout.elements.TextFlow; import flashx.textLayout.tlf_internal; use namespace tlf_internal; /** * The FlowElementOperation class is the base class for operations that transform a FlowElement. * * @see flashx.textLayout.formats.TextLayoutFormat * @see flashx.textLayout.edit.EditManager * @see flashx.textLayout.events.FlowOperationEvent * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ public class FlowElementOperation extends FlowTextOperation { private var nestLevel:int; private var absStart:int; private var absEnd:int; private var origAbsStart:int; private var origAbsEnd:int; private var firstTime:Boolean = true; private var splitAtStart:Boolean = false; private var splitAtEnd:Boolean = false; private var _relStart:int = 0; private var _relEnd:int = -1; /** * Creates a FlowElementOperation object. * * @param operationState Specifies the TextFlow object this operation acts upon. * @param targetElement Specifies the element this operation modifies. * @param relativeStart An offset from the beginning of the targetElement. * @param relativeEnd An offset from the end of the targetElement. * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ public function FlowElementOperation(operationState:SelectionState, targetElement:FlowElement, relativeStart:int = 0, relativeEnd:int = -1) { super(operationState); CONFIG::debug { var elem:FlowElement = this.targetElement; for (var i:int = nestLevel; i > 0; i--) elem = elem.parent; assert (elem is TextFlow, "ChangeElementIdOperation targetElement root is not a TextFlow!"); assert (elem == operationState.textFlow, "ChangeElementIdOperation element is not part of selectionState TextFlow"); } initialize(targetElement,relativeStart,relativeEnd); } private function initialize(targetElement:FlowElement, relativeStart:int, relativeEnd:int ):void { this.targetElement = targetElement; this.relativeEnd = relativeEnd; this.relativeStart = relativeStart; if (relativeEnd == -1) relativeEnd = targetElement.textLength; CONFIG::debug { assert(relativeStart >= 0 && relativeStart <= targetElement.textLength,"ChangeElementIdOperation bad relativeStart"); } CONFIG::debug { assert(relativeEnd >= 0 && relativeEnd <= targetElement.textLength,"ChangeElementIdOperation bad relativeEnd"); } CONFIG::debug { assert(relativeStart <= relativeEnd,"ChangeElementIdOperation relativeStart not before relativeEnd"); } // If we're changing the format of the text right before the terminator, change the terminator to match. // This will make it so that when the format change is undone, the terminator will be restored to previous state. Also // prevents unnecessary split & join of spans (split for apply, joined during normalize). if (targetElement is SpanElement && SpanElement(targetElement).hasParagraphTerminator && relativeEnd == targetElement.textLength - 1) relativeEnd += 1; origAbsStart = absStart = targetElement.getAbsoluteStart() + relativeStart; origAbsEnd = absEnd = absStart - relativeStart + relativeEnd; } /** * Specifies the element this operation modifies. * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ public function get targetElement():FlowElement { var element:FlowElement = originalSelectionState.textFlow; for (var i:int = nestLevel; i > 0; i--) { var groupElement:FlowGroupElement = element as FlowGroupElement; element = groupElement.getChildAt(groupElement.findChildIndexAtPosition(absStart - element.getAbsoluteStart())); } return element; } public function set targetElement(value:FlowElement):void { nestLevel = 0; for (var element:FlowElement = value; element.parent != null; element = element.parent) ++nestLevel; } /** * An offset from the beginning of the targetElement. * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ public function get relativeStart():int { return _relStart; } public function set relativeStart(value:int):void { _relStart = value; } /** * An offset from the start of the targetElement. * * @playerversion Flash 10 * @playerversion AIR 1.5 * @langversion 3.0 */ public function get relativeEnd():int { return _relEnd; } public function set relativeEnd(value:int):void { _relEnd = value; } /** @private */ protected function getTargetElement():FlowElement { var element:FlowElement = this.targetElement; var elemStart:int = element.getAbsoluteStart(); var splitElement:FlowElement; // scratch // split at the back and then split at the start - that way paragraph terminators don't get in the way if (absEnd != elemStart + element.textLength) { splitElement = element.splitAtPosition(absEnd - elemStart); if (firstTime && splitElement != element) splitAtEnd = true; } if (absStart != elemStart) { splitElement = element.splitAtPosition(absStart-elemStart); if (splitElement != element) { if (firstTime) splitAtStart = true; element = splitElement; } } firstTime = false; return element; } /** @private */ protected function adjustForDoOperation(targetElement:FlowElement):void { // adjust for undo absStart = targetElement.getAbsoluteStart(); absEnd = absStart + targetElement.textLength; } /** @private */ protected function adjustForUndoOperation(targetElement:FlowElement):void { // need to do manual merging if ((splitAtEnd || splitAtStart) && (targetElement is FlowGroupElement)) { var targetIdx:int = targetElement.parent.getChildIndex(targetElement); var workElem:FlowGroupElement; var child:FlowElement; if (splitAtEnd) { // merge next to targetElement workElem = targetElement.parent.getChildAt(targetIdx+1) as FlowGroupElement; while (workElem.numChildren) { child = workElem.getChildAt(0); workElem.removeChildAt(0); FlowGroupElement(targetElement).addChild(child); } targetElement.parent.removeChildAt(targetIdx+1); } if (splitAtStart) { // merge targetElement to prevElement workElem = targetElement.parent.getChildAt(targetIdx-1) as FlowGroupElement; while (FlowGroupElement(targetElement).numChildren) { child = FlowGroupElement(targetElement).getChildAt(0); FlowGroupElement(targetElement).removeChildAt(0); workElem.addChild(child); } targetElement.parent.removeChildAt(targetIdx); } } absStart = origAbsStart; absEnd = origAbsEnd; } } }