blob: e477c4449ba60b3434b1f23bc414cc53d7c03163 [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.operations
{
import flashx.textLayout.debug.assert;
import flashx.textLayout.edit.ModelEdit;
import flashx.textLayout.edit.ParaEdit;
import flashx.textLayout.edit.PointFormat;
import flashx.textLayout.edit.SelectionState;
import flashx.textLayout.edit.TextFlowEdit;
import flashx.textLayout.elements.FlowElement;
import flashx.textLayout.elements.FlowLeafElement;
import flashx.textLayout.elements.LinkElement;
import flashx.textLayout.elements.SpanElement;
import flashx.textLayout.elements.TCYElement;
import flashx.textLayout.formats.ITextLayoutFormat;
import flashx.textLayout.formats.TextLayoutFormat;
import flashx.textLayout.tlf_internal;
use namespace tlf_internal;
/**
* The InsertTextOperation class encapsulates a text insertion operation.
*
* @see flashx.textLayout.edit.EditManager
* @see flashx.textLayout.events.FlowOperationEvent
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public class InsertTextOperation extends FlowTextOperation
{
private var _deleteSelectionState:SelectionState;
private var delSelOp:DeleteTextOperation = null;
/** @private - this should be private but too late for code changes on Labs */
public var _text:String;
private var _pointFormat:ITextLayoutFormat;
/**
* Creates an InsertTextOperation object.
*
* @param operationState Describes the insertion point or range of text.
* @param text The string to insert.
* @param deleteSelectionState Describes the range of text to delete before doing insertion,
* if different than the range described by <code>operationState</code>.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function InsertTextOperation(operationState:SelectionState, text:String, deleteSelectionState:SelectionState = null)
{
super(operationState);
_pointFormat = operationState.pointFormat;
_text = text;
initialize(deleteSelectionState);
}
private function initialize(deleteSelectionState:SelectionState):void
{
if (deleteSelectionState == null)
deleteSelectionState = originalSelectionState;
if (deleteSelectionState.anchorPosition != deleteSelectionState.activePosition)
{
_deleteSelectionState = deleteSelectionState;
delSelOp = new DeleteTextOperation(_deleteSelectionState);
}
}
/**
* The text inserted by this operation.
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function get text():String
{
return _text;
}
public function set text(value:String):void
{
_text = value;
}
/**
* The text deleted by this operation, if any.
*
* <p><code>null</code> if no text is deleted.</p>
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function get deleteSelectionState():SelectionState
{
return _deleteSelectionState;
}
public function set deleteSelectionState(value:SelectionState):void
{
_deleteSelectionState = value;
}
/**
* The character format applied to the inserted text.
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public function get characterFormat():ITextLayoutFormat
{
return _pointFormat;
}
public function set characterFormat(value:ITextLayoutFormat):void
{
_pointFormat = new PointFormat(value);
}
private function doDelete(leaf:FlowLeafElement):ITextLayoutFormat
{
// User selected a range of text and is replacing it. We're doing the delete here.
// We preserve the format from the deleted text, and apply it to the text insert,
// unless the user has specified an alternate format.
var deleteFormat:PointFormat = PointFormat.createFromFlowElement(textFlow.findLeaf(absoluteStart));
var beforeDeleteFormat:PointFormat = absoluteStart == leaf.getParagraph().getAbsoluteStart() ? null : PointFormat.createFromFlowElement(textFlow.findLeaf(absoluteStart - 1));
if (delSelOp.doOperation()) // figure out what to do here
{
//do not change characterFormat if user specified one already, or if its the same as in the surrounding text.
// If the surrounding text is the same, forcing the point format requires more composition because it inserts
// the text in its own span.
if (!_pointFormat && (absoluteStart < absoluteEnd) && PointFormat.isEqual(deleteFormat, beforeDeleteFormat))
deleteFormat = null;
else
{
// If the leaf element is empty, remove it now
if (leaf.textLength == 0)
leaf.parent.removeChild(leaf);
}
}
return deleteFormat;
}
private function applyPointFormat(span:SpanElement, pointFormat:ITextLayoutFormat):void
{
if (!TextLayoutFormat.isEqual(pointFormat, span.format))
{
var spanFormat:TextLayoutFormat = new TextLayoutFormat(span.format);
spanFormat.apply(pointFormat);
span.format = spanFormat;
}
if (pointFormat is PointFormat)
{
var pf:PointFormat = pointFormat as PointFormat;
if (pf.linkElement)
{
if (pf.linkElement.href)
{
TextFlowEdit.makeLink(textFlow, absoluteStart, absoluteStart + _text.length, pf.linkElement.href, pf.linkElement.target);
var linkLeaf:FlowLeafElement = textFlow.findLeaf(absoluteStart);
var linkElement:FlowElement = linkLeaf.getParentByType(LinkElement);
linkElement.format = pf.linkElement.format;
}
}
if (pf.tcyElement)
{
TextFlowEdit.makeTCY(textFlow, absoluteStart, absoluteStart + _text.length);
var tcyLeaf:FlowLeafElement = textFlow.findLeaf(absoluteStart);
var tcyElement:FlowElement = tcyLeaf.getParentByType(TCYElement);
tcyElement.format = pf.tcyElement.format;
}
else if (span.getParentByType(TCYElement))
TextFlowEdit.removeTCY(textFlow, absoluteStart, absoluteStart + _text.length);
}
}
private function doInternal():void
{
var deleteFormat:ITextLayoutFormat;
if (delSelOp != null)
deleteFormat = doDelete(textFlow.findLeaf(absoluteStart));
var span:SpanElement = ParaEdit.insertText(textFlow, absoluteStart, _text, _pointFormat != null || deleteFormat != null/* createNewSpan */);
if (textFlow.interactionManager)
textFlow.interactionManager.notifyInsertOrDelete(absoluteStart, _text.length);
if (span != null)
{
if (deleteFormat)
{
span.format = deleteFormat;
applyPointFormat(span, deleteFormat);
if ((deleteFormat is PointFormat) && PointFormat(deleteFormat).linkElement && PointFormat(deleteFormat).linkElement.href && originalSelectionState.selectionManagerOperationState && textFlow.interactionManager)
{
// set pointFormat from leafFormat, to insure link attributes are propagated from replaced text to next insertion
// if I select a range of text in a link, and type over it to replace, the new text should be in a link with the same settings.
var state:SelectionState = textFlow.interactionManager.getSelectionState();
state.pointFormat = PointFormat.clone(deleteFormat as PointFormat);
textFlow.interactionManager.setSelectionState(state);
}
}
if (_pointFormat)
applyPointFormat(span, _pointFormat);
}
}
/** @private */
public override function doOperation():Boolean
{
doInternal();
return true;
}
/** @private */
public override function undo():SelectionState
{
ModelEdit.deleteText(textFlow, absoluteStart, absoluteStart + _text.length, false);
var newSelectionState:SelectionState = originalSelectionState;
if (delSelOp != null)
newSelectionState = delSelOp.undo();
return originalSelectionState;
}
/**
* Re-executes the operation after it has been undone.
*
* <p>This function is called by the edit manager, when necessary.</p>
*
* @playerversion Flash 10
* @playerversion AIR 1.5
* @langversion 3.0
*/
public override function redo():SelectionState
{
doInternal();
return new SelectionState(textFlow,absoluteStart+_text.length,absoluteStart+_text.length,null);
}
/** @private */
tlf_internal override function merge(op2:FlowOperation):FlowOperation
{
if (absoluteStart < absoluteEnd)
return null;
if (this.endGeneration != op2.beginGeneration)
return null;
// We are assuming here that these operations are contiguous, because
// SelectionManager doesn't try to merge operations if the selection
// has changed
var insertOp:InsertTextOperation = null;
if (op2 is InsertTextOperation)
insertOp = op2 as InsertTextOperation;
if (insertOp)
{
if (insertOp.deleteSelectionState != null || deleteSelectionState != null)
return null;
if ((insertOp.originalSelectionState.pointFormat == null) && (originalSelectionState.pointFormat != null))
return null;
if ((originalSelectionState.pointFormat == null) && (insertOp.originalSelectionState.pointFormat != null))
return null;
if (originalSelectionState.absoluteStart + _text.length != insertOp.originalSelectionState.absoluteStart)
return null;
if (((originalSelectionState.pointFormat == null) && (insertOp.originalSelectionState.pointFormat == null)) ||
(PointFormat.isEqual(originalSelectionState.pointFormat, insertOp.originalSelectionState.pointFormat)))
{
_text += insertOp.text;
setGenerations(beginGeneration,insertOp.endGeneration);
}
else
return null;
setGenerations(beginGeneration,insertOp.endGeneration);
return this;
}
if (op2 is SplitParagraphOperation)
return new CompositeOperation([this,op2]);
return null;
}
}
}