| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // 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 spark.components |
| { |
| |
| import flash.display.DisplayObject; |
| import flash.display.DisplayObjectContainer; |
| import flash.events.Event; |
| import flash.events.FocusEvent; |
| import flash.events.KeyboardEvent; |
| import flash.events.MouseEvent; |
| import flash.geom.Rectangle; |
| import flash.system.IME; |
| import flash.system.IMEConversionMode; |
| import flash.text.engine.ElementFormat; |
| import flash.text.engine.FontDescription; |
| import flash.text.engine.FontLookup; |
| import flash.text.engine.TextBlock; |
| import flash.text.engine.TextElement; |
| import flash.text.engine.TextLine; |
| import flash.ui.Keyboard; |
| |
| import flashx.textLayout.compose.ISWFContext; |
| import flashx.textLayout.container.TextContainerManager; |
| import flashx.textLayout.conversion.ConversionType; |
| import flashx.textLayout.conversion.ITextExporter; |
| import flashx.textLayout.conversion.ITextImporter; |
| import flashx.textLayout.conversion.TextConverter; |
| import flashx.textLayout.edit.EditManager; |
| import flashx.textLayout.edit.EditingMode; |
| import flashx.textLayout.edit.IEditManager; |
| import flashx.textLayout.edit.ISelectionManager; |
| import flashx.textLayout.edit.SelectionState; |
| import flashx.textLayout.edit.TextClipboard; |
| import flashx.textLayout.elements.Configuration; |
| import flashx.textLayout.elements.GlobalSettings; |
| import flashx.textLayout.elements.InlineGraphicElement; |
| import flashx.textLayout.elements.InlineGraphicElementStatus; |
| import flashx.textLayout.elements.TextFlow; |
| import flashx.textLayout.events.CompositionCompleteEvent; |
| import flashx.textLayout.events.DamageEvent; |
| import flashx.textLayout.events.FlowOperationEvent; |
| import flashx.textLayout.events.SelectionEvent; |
| import flashx.textLayout.events.StatusChangeEvent; |
| import flashx.textLayout.factory.StringTextLineFactory; |
| import flashx.textLayout.factory.TextFlowTextLineFactory; |
| import flashx.textLayout.formats.BlockProgression; |
| import flashx.textLayout.formats.Category; |
| import flashx.textLayout.formats.ITextLayoutFormat; |
| import flashx.textLayout.formats.TextLayoutFormat; |
| import flashx.textLayout.operations.CopyOperation; |
| import flashx.textLayout.operations.CutOperation; |
| import flashx.textLayout.operations.DeleteTextOperation; |
| import flashx.textLayout.operations.FlowOperation; |
| import flashx.textLayout.operations.FlowTextOperation; |
| import flashx.textLayout.operations.InsertTextOperation; |
| import flashx.textLayout.operations.PasteOperation; |
| import flashx.textLayout.tlf_internal; |
| import flashx.undo.IUndoManager; |
| |
| import mx.core.FlexVersion; |
| import mx.core.IFlexModuleFactory; |
| import mx.core.IIMESupport; |
| import mx.core.ISystemCursorClient; |
| import mx.core.UIComponent; |
| import mx.core.mx_internal; |
| import mx.events.FlexEvent; |
| import mx.managers.IFocusManager; |
| import mx.managers.IFocusManagerComponent; |
| import mx.utils.StringUtil; |
| |
| import spark.components.supportClasses.RichEditableTextContainerManager; |
| import spark.core.CSSTextLayoutFormat; |
| import spark.core.IEditableText; |
| import spark.core.IViewport; |
| import spark.core.NavigationUnit; |
| import spark.events.TextOperationEvent; |
| import spark.utils.TextUtil; |
| |
| use namespace mx_internal; |
| use namespace tlf_internal; |
| |
| //-------------------------------------- |
| // Events |
| //-------------------------------------- |
| |
| /** |
| * Dispached after the <code>selectionAnchorPosition</code> and/or |
| * <code>selectionActivePosition</code> properties have changed |
| * for any reason. |
| * |
| * @eventType mx.events.FlexEvent.SELECTION_CHANGE |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| [Event(name="selectionChange", type="mx.events.FlexEvent")] |
| |
| /** |
| * Dispatched before a user editing operation occurs. |
| * You can alter the operation, or cancel the event |
| * to prevent the operation from being processed. |
| * |
| * @eventType spark.events.TextOperationEvent.CHANGING |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| [Event(name="changing", type="spark.events.TextOperationEvent")] |
| |
| /** |
| * Dispatched after a user editing operation is complete. |
| * |
| * @eventType spark.events.TextOperationEvent.CHANGE |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| [Event(name="change", type="spark.events.TextOperationEvent")] |
| |
| /** |
| * Dispatched when the user presses the Enter key, |
| * if the <code>multiline</code> property is false. |
| * |
| * @eventType mx.events.FlexEvent.ENTER |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| [Event(name="enter", type="mx.events.FlexEvent")] |
| |
| //-------------------------------------- |
| // Styles |
| //-------------------------------------- |
| |
| include "../styles/metadata/BasicInheritingTextStyles.as" |
| include "../styles/metadata/BasicNonInheritingTextStyles.as" |
| include "../styles/metadata/AdvancedInheritingTextStyles.as" |
| include "../styles/metadata/AdvancedNonInheritingTextStyles.as" |
| include "../styles/metadata/SelectionFormatTextStyles.as" |
| |
| /** |
| * The alpha level of the color defined by |
| * the <code>backgroundColor</code> style. |
| * Valid values range from 0.0 to 1.0. |
| * |
| * @default 1.0 |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| [Style(name="backgroundAlpha", type="Number", inherit="no")] |
| |
| /** |
| * The color of the background of the entire |
| * bounding rectangle of this component. |
| * If this style is <code>undefined</code>, |
| * no background is drawn. |
| * Otherwise, this RGB color is drawn with an alpha level |
| * determined by the <code>backgroundAlpha</code> style. |
| * |
| * @default undefined |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| [Style(name="backgroundColor", type="uint", format="Color", inherit="no")] |
| |
| //-------------------------------------- |
| // Excluded APIs |
| //-------------------------------------- |
| |
| [Exclude(name="chromeColor", kind="style")] |
| |
| //-------------------------------------- |
| // Other metadata |
| //-------------------------------------- |
| |
| [AccessibilityClass(implementation="spark.accessibility.RichEditableTextAccImpl")] |
| |
| [DefaultProperty("content")] |
| |
| [IconFile("RichEditableText.png")] |
| |
| [DefaultTriggerEvent("change")] |
| |
| [DiscouragedForProfile("mobileDevice")] |
| |
| /** |
| * RichEditableText is a low-level UIComponent for displaying, |
| * scrolling, selecting, and editing richly-formatted text. |
| * |
| * <p>The rich text can contain clickable hyperlinks and inline graphics |
| * that are either embedded or loaded from URLs.</p> |
| * |
| * <p>RichEditableText does not have scrollbars, but it implements |
| * the IViewport interface for programmatic scrolling so that it |
| * can be controlled by a Scroller, which does provide scrollbars. |
| * It also supports vertical scrolling with the mouse wheel.</p> |
| * |
| * <p>It does not include any user interface for changing |
| * the formatting of the text. |
| * But it offers APIs which can do this programmatically; |
| * these make it possible, for example, for you to create |
| * a Bold button that makes the selected text bold.</p> |
| * |
| * <p>This class is used in the skins of the Spark versions |
| * of TextInput and TextArea. |
| * (TextInput does not expose its ability to handle rich text, |
| * but TextArea does.) |
| * By default, RichEditableText has a transparent background, |
| * and it does not support drawing a border.</p> |
| * |
| * <p>RichEditableText uses the Text Layout Framework (TLF) library, |
| * which in turn builds on the Flash Text Engine (FTE) |
| * introduced in Flash Player 10. In combination, these layers provide text editing with |
| * high-quality international typography and layout.</p> |
| * |
| * <p>The Spark architecture provides three text "primitives" -- |
| * Label, RichText, and RichEditableText. |
| * Label is the fastest and most lightweight |
| * because it uses only FTE, not TLF, |
| * but it is limited in its capabilities: no rich text, |
| * no scrolling, no selection, and no editing. |
| * RichText adds the ability to display rich text |
| * with complex layout, but is still completely non-interactive. |
| * RichEditableText is the heaviest-weight, |
| * but offers most of what TLF can do. |
| * In general, use the fastest text primitive that meets your needs.</p> |
| * |
| * <p>RichEditableText is similar to the UITextField class |
| * used in MX components. This class did not use FTE or TLF |
| * but rather extended the older TextField class.</p> |
| * |
| * <p>The most important differences between UITextField and RichEditableText are: |
| * <ul> |
| * <li>RichEditableText offers better typography, better support |
| * for international languages, and better text layout.</li> |
| * <li>RichEditableText has an object-oriented model of rich text, |
| * while UITextField does not.</li> |
| * <li>RichEditableText has better support for displaying |
| * large amounts of text.</li> |
| * <li>RichEditableText requires that fonts be embedded |
| * differently than UITextField. |
| * Consult the documentation regarding how to use the |
| * <code>embedAsCFF</code> attribute when you embed a font.</li> |
| * </ul></p> |
| * |
| * <p>RichEditableText uses TLF's object-oriented model of rich text, |
| * in which text layout elements such as divisions, paragraphs, spans, |
| * hyperlinks, and images are represented at runtime by ActionScript |
| * objects which can be programmatically accessed and manipulated. |
| * The central object in TLF for representing rich text is a |
| * TextFlow, so you specify rich text for a RichEditableText control to display |
| * by setting its <code>textFlow</code> property to a TextFlow instance. |
| * See the description of the <code>textFlow</code> |
| * property for information about how to create one, |
| * such as by importing TLF markup. |
| * If you don't need to display text that has multiple formats, |
| * you can use the <code>text</code> property to set a "plain text" String. |
| * See the description of the <code>text</code> and <code>textFlow</code> |
| * properties for information about how they interact; |
| * for example, you can set one and get the other.</p> |
| * |
| * <p>At compile time, you can put TLF markup tags inside |
| * the RichEditableText tag, as the following example shows: |
| * <pre> |
| * <s:RichEditableText>Hello <s:span fontWeight="bold">World!</s:span></s:RichEditableText> |
| * </pre> |
| * In this case, the MXML compiler sets the <code>content</code> |
| * property, causing a TextFlow to be automatically created |
| * from the FlowElements that you specify.</p> |
| * |
| * <p>The default text formatting is determined by CSS styles |
| * such as <code>fontFamily</code>, <code>fontSize</code>. |
| * Any formatting information in the TextFlow overrides |
| * the default formatting provided by the CSS styles.</p> |
| * |
| * <p>You can control the spacing between lines with the |
| * <code>lineHeight</code> style and the spacing between |
| * paragraphs with the <code>paragraphSpaceBefore</code> |
| * and <code>paragraphSpaceAfter</code> styles. |
| * You can align or justify the text using the <code>textAlign</code> |
| * and <code>textAlignLast</code> styles. |
| * You can inset the text from the component's edges using the |
| * <code>paddingLeft</code>, <code>paddingTop</code>, |
| * <code>paddingRight</code>, and <code>paddingBottom</code> styles.</p> |
| * |
| * <p>By default, a RichEditableText "autosizes": it starts out very |
| * small if it has no text, and grows in width up to |
| * <code>maxWidth</code> as you type. It grows in height when you |
| * press the Enter key to start a new line.</p> |
| * |
| * <p>The <code>widthInChars</code> and <code>heightInLines</code> |
| * properties provide a convenient way to specify the width and height |
| * in a way that scales with the font size. |
| * You can use the <code>typicalText</code> property as well. |
| * Note that if you use <code>typicalText</code>, the |
| * <code>widthInChars</code> and <code>heightInLines</code> |
| * are ignored. |
| * You can also specify an explicit width or height in pixels, |
| * or use a percent width and height, or use constraints such as |
| * <code>left</code> and <code>right</code> |
| * or <code>top</code> and <code>bottom</code>.</p> |
| * |
| * <p>When you specify some kind of width -- whether an explicit or |
| * percent width, a <code>maxWidth</code> or <code>left</code> |
| * and <code>right</code> constraints -- the text wraps at the right |
| * edge of the component and the text becomes vertically scrollable |
| * when there is more text than fits. |
| * If you set the <code>lineBreak</code> style to <code>explicit</code>, |
| * new lines will start only at explicit lines breaks, such as |
| * if you use CR (<code>\r</code>), LF (<code>\n</code>), |
| * or CR+LF (<code>\r\n</code>) in <code>text</code> |
| * or if you use <code><p></code> and <code><br/></code> |
| * in TLF markup. |
| * In that case, the text becomes horizontally scrollable |
| * if any lines are wider than the control.</p> |
| * |
| * <p>You can use the <code>maxChars</code> property to limit the number |
| * of character that the user can enter, and the <code>restrict</code> |
| * to limit which characters the user can enter.</p> |
| * |
| * <p>The <code>multiline</code> property determines what happens |
| * when you press the Enter key. |
| * If it is <code>true</code>, the Enter key starts a new paragraph. |
| * If it is <code>false</code>, it causes a <code>FlexEvent.ENTER</code> |
| * event to be dispatched.</p> |
| * |
| * <p>If you don't want the text to be editable, |
| * set the <code>editable</code> property to <code>false</code>. |
| * If you don't even want the text to be selectable, |
| * set the <code>selectable</code> property to <code>false</code>.</p> |
| * |
| * <p>Because RichEditableText uses TLF, |
| * it supports displaying left-to-right (LTR) text such as French, |
| * right-to-left (RTL) text such as Arabic, and bidirectional text |
| * such as a French phrase inside of an Arabic one. |
| * If the predominant text direction is right-to-left, |
| * set the <code>direction</code> style to <code>rtl</code>. |
| * The <code>textAlign</code> style defaults to <code>"start"</code>, |
| * which makes the text left-aligned when <code>direction</code> |
| * is <code>ltr</code> and right-aligned when <code>direction</code> |
| * is <code>rtl</code>. |
| * To get the opposite alignment, set <code>textAlign</code> to <code>end</code>.</p> |
| * |
| * <p>As a result of using TLF, the RichEditableText supports |
| * unlimited undo/redo within one editing session. |
| * An editing session starts when the component gets keyboard focus |
| * and ends when it loses focus.</p> |
| * |
| * <p>RichEditableText uses TLF's TextContainerManager class |
| * to handle its text display, scrolling, selection, editing and context menu.</p> |
| * |
| * <p>To use this component in a list-based component, such as a List or DataGrid, |
| * create an item renderer. |
| * For information about creating an item renderer, see |
| * <a href="http://help.adobe.com/en_US/flex/using/WS4bebcd66a74275c3-fc6548e124e49b51c4-8000.html"> |
| * Custom Spark item renderers</a>. </p> |
| * |
| * @mxml |
| * |
| * <p>The <code><s:RichEditableText></code> tag inherits all of the tag |
| * attributes of its superclass and adds the following tag attributes:</p> |
| * |
| * <pre> |
| * <s:RichEditableText |
| * <strong>Properties</strong> |
| * clipAndEnableScrolling="false|true" |
| * contentHeight="<i>Calculated default</i>" |
| * contentWidth="<i>Calculated default</i>" |
| * displayAsPassword="false" |
| * editable="true" |
| * heightInLines="NaN" |
| * horizontalScrollPosition="0" |
| * imeMode="null" |
| * maxChars="0" |
| * multiline="true" |
| * restrict="null" |
| * selectable="true" |
| * selectionActivePosition="-1" |
| * selectionAnchorPosition="-1" |
| * selectionHighlighting="TextSelectionHighlighting.WHEN_FOCUSED" |
| * text="" |
| * textFlow="<i>TextFlow</i>" |
| * typicalText=null |
| * verticalScrollPosition="0" |
| * widthInChars="NaN" |
| * |
| * <strong>Events</strong> |
| * change="<i>No default</i>" |
| * changing="<i>No default</i>" |
| * enter="<i>No default</i>" |
| * selectionChange="<i>No default</i>" |
| * /> |
| * </pre> |
| * |
| * @includeExample examples/RichEditableTextExample.mxml |
| * @includeExample examples/externalTextFlow.xml -noswf |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| * |
| * @see spark.components.Label |
| * @see spark.components.RichText |
| * @see spark.utils.TextFlowUtil |
| * @see flashx.textLayout.container.TextContainerManager |
| */ |
| public class RichEditableText extends UIComponent |
| implements IFocusManagerComponent, IIMESupport, ISystemCursorClient, |
| IViewport, IEditableText |
| { |
| include "../core/Version.as"; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Class mixins |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Placeholder for mixin by RichEditableTextAccImpl. |
| */ |
| mx_internal static var createAccessibilityImplementation:Function; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Class initialization |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * This method initializes the static vars of this class. |
| * Rather than calling it at static initialization time, |
| * we call it in the constructor to do the class initialization |
| * when the first instance is created. |
| * (It does an immediate return if it has already run.) |
| * By doing so, we avoid any static initialization issues |
| * related to whether this class or the TLF classes |
| * that it uses are initialized first. |
| */ |
| private static function initClass():void |
| { |
| if (classInitialized) |
| return; |
| |
| // Set the TLF hook used for localizing runtime error messages. |
| // TLF itself has English-only messages, |
| // but higher layers like Flex can provide localized versions. |
| GlobalSettings.resourceStringFunction = TextUtil.getResourceString; |
| |
| // Set the TLF hook used to specify the callback used for changing |
| // the FontLookup based on SWFContext. |
| GlobalSettings.resolveFontLookupFunction = TextUtil.resolveFontLookup; |
| |
| // Pre-FP10.1, set default tab stops in TLF. Without this, if there |
| // is a tab and TLF is measuring width, the tab will |
| // measure as the rest of the remaining width up to 10000. |
| GlobalSettings.enableDefaultTabStops = |
| !Configuration.playerEnablesArgoFeatures; |
| |
| staticPlainTextImporter = |
| TextConverter.getImporter(TextConverter.PLAIN_TEXT_FORMAT); |
| |
| // Throw import errors rather than return a null textFlow. |
| // Alternatively, the error strings are in the Vector, importer.errors. |
| staticPlainTextImporter.throwOnError = true; |
| |
| staticPlainTextExporter = |
| TextConverter.getExporter(TextConverter.PLAIN_TEXT_FORMAT); |
| |
| classInitialized = true; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Class variables |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| private static var classInitialized:Boolean = false; |
| |
| /** |
| * @private |
| * This TLF object composes TextLines from a text String. |
| * We use it when the 'typicalText' property is set to a String |
| * that doesn't contain linebreaks. |
| */ |
| private static var staticStringFactory:StringTextLineFactory; |
| |
| /** |
| * @private |
| * This TLF object composes TextLines from a TextFlow. |
| * We use it when the 'typicalText is set to a String |
| * that contains linebreaks (and therefore is interpreted |
| * as multiple paragraphs). |
| */ |
| private static var staticTextFlowFactory:TextFlowTextLineFactory; |
| |
| /** |
| * @private |
| * This TLF object is used to import a 'text' String |
| * containing linebreaks to create a multiparagraph TextFlow. |
| */ |
| private static var staticPlainTextImporter:ITextImporter; |
| |
| /** |
| * @private |
| * This TLF object is used to export a TextFlow as plain 'text', |
| * by walking the leaf FlowElements in the TextFlow. |
| */ |
| private static var staticPlainTextExporter:ITextExporter; |
| |
| /** |
| * @private |
| * Regular expression which matches all newlines in the text. Used |
| * to strip newlines when pasting text when multiline is false. |
| */ |
| private static const ALL_NEWLINES_REGEXP:RegExp = /[\n\r]/g; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Class methods |
| // |
| //-------------------------------------------------------------------------- |
| /** |
| * @private |
| */ |
| public static function getNumberOrPercentOf(value:Object, |
| n:Number):Number |
| { |
| // If 'value' is a Number like 10.5, return it. |
| if (value is Number) |
| return Number(value); |
| |
| // If 'value' is a percentage String like "10.5%", |
| // return that percentage of 'n'. |
| if (value is String) |
| { |
| var len:int = String(value).length; |
| if (len >= 1 && value.charAt(len - 1) == "%") |
| { |
| var percent:Number = Number(value.substring(0, len - 1)); |
| return percent / 100 * n; |
| } |
| } |
| |
| // Otherwise, return NaN. |
| return NaN; |
| } |
| |
| /** |
| * @private |
| */ |
| private static function splice(str:String, start:int, end:int, |
| strToInsert:String):String |
| { |
| return str.substring(0, start) + |
| strToInsert + |
| str.substring(end, str.length); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Constructor |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Constructor. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function RichEditableText() |
| { |
| super(); |
| |
| initClass(); |
| |
| // Use the setter. |
| text = ""; |
| |
| // Create the TLF TextContainerManager, using this component |
| // as the DisplayObjectContainer for its TextLines. |
| // This TextContainerManager instance persists for the lifetime |
| // of the component. |
| _textContainerManager = createTextContainerManager(); |
| |
| // Turn on TextField-like behavior which preserves the selection when text is set. |
| // If the new text is shorter than the exisiting text, the selection may change. |
| if (FlexVersion.compatibilityVersion > FlexVersion.VERSION_4_8) |
| _textContainerManager.preserveSelectionOnSetText = true; |
| |
| // Add event listeners on this component. |
| addEventListener(Event.REMOVED_FROM_STAGE, removedFromStageHandler); |
| |
| // The focusInHandler is called by the TCMContainer focusInHandler. |
| // The focusOutHandler is called by the TCMContainer focusOutHandler. |
| // The keyDownHandler is called by the TCMContainer keyDownHandler. |
| |
| // Add event listeners on its TextContainerManager. |
| |
| _textContainerManager.addEventListener( |
| CompositionCompleteEvent.COMPOSITION_COMPLETE, |
| textContainerManager_compositionCompleteHandler); |
| |
| _textContainerManager.addEventListener( |
| DamageEvent.DAMAGE, textContainerManager_damageHandler); |
| |
| _textContainerManager.addEventListener( |
| Event.SCROLL, textContainerManager_scrollHandler); |
| |
| _textContainerManager.addEventListener( |
| SelectionEvent.SELECTION_CHANGE, |
| textContainerManager_selectionChangeHandler); |
| |
| _textContainerManager.addEventListener( |
| FlowOperationEvent.FLOW_OPERATION_BEGIN, |
| textContainerManager_flowOperationBeginHandler); |
| |
| _textContainerManager.addEventListener( |
| FlowOperationEvent.FLOW_OPERATION_END, |
| textContainerManager_flowOperationEndHandler); |
| |
| _textContainerManager.addEventListener( |
| FlowOperationEvent.FLOW_OPERATION_COMPLETE, |
| textContainerManager_flowOperationCompleteHandler); |
| |
| _textContainerManager.addEventListener( |
| StatusChangeEvent.INLINE_GRAPHIC_STATUS_CHANGE, |
| textContainerManager_inlineGraphicStatusChangeHandler); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Variables |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * The composition bounds used when creating the TextLines. |
| */ |
| mx_internal var unbounded:Rectangle = new Rectangle(0, 0, NaN, NaN); |
| |
| /** |
| * @private |
| * The hostFormat object in the _textContainerManager determines the |
| * default text formatting used by this component, based on its CSS styles. |
| * This flag is used by updateStylesIfChanged() determine when the object |
| * must be reinitialized. It is set in stylesInitialized() and |
| * styleChanged(), and cleared in commitProperties(). After |
| * initialization, the hostFormat object should never be null. |
| */ |
| private var hostFormatChanged:Boolean; |
| |
| /** |
| * @private |
| * It is set to NaN by stylesInitialized() and styleChanged(), |
| * and recreated whenever necessary in calculateFontMetrics(). |
| */ |
| private var ascent:Number = NaN; |
| |
| /** |
| * @private |
| * It is set to NaN by stylesInitialized() and styleChanged(), |
| * and recreated whenever necessary in calculateFontMetrics(). |
| */ |
| private var descent:Number = NaN; |
| |
| /** |
| * @private |
| * Source of text: one of "text", "textFlow" or "content". |
| */ |
| private var source:String = "text"; |
| |
| /** |
| * @private |
| * Holds the last recorded value of the textFlow generation. Used to |
| * determine whether to return immediately from damage event if there |
| * have been no changes. |
| */ |
| private var lastGeneration:uint = 0; // 0 means not set |
| |
| /** |
| * @private |
| * The generation of the text flow that last reported its content |
| * bounds. |
| */ |
| private var lastContentBoundsGeneration:int = 0; // 0 means not set |
| |
| /** |
| * @private |
| * True if TextOperationEvent.CHANGING and TextOperationEvent.CHANGE |
| * events should be dispatched. |
| */ |
| private var dispatchChangeAndChangingEvents:Boolean = true; |
| |
| /** |
| * @private |
| */ |
| private var inMeasureMethod:Boolean = false; |
| |
| /** |
| * @private |
| */ |
| private var inUpdateDLMethod:Boolean = false; |
| |
| /** |
| * @private |
| */ |
| private var remeasuringText:Boolean = false; |
| |
| /** |
| * @private |
| */ |
| mx_internal var passwordChar:String = "*"; |
| |
| /** |
| * @private |
| */ |
| mx_internal var undoManager:IUndoManager; |
| |
| /** |
| * @private |
| */ |
| mx_internal var clearUndoOnFocusOut:Boolean = true; |
| |
| /** |
| * @private |
| * Holds the last recorded value of the module factory used to create the |
| * font. |
| */ |
| mx_internal var embeddedFontContext:IFlexModuleFactory; |
| |
| /** |
| * @private |
| * The TLF edit manager will batch all inserted text until the next |
| * enter frame event. This includes text inserted via the GUI as well |
| * as api calls to EditManager.insertText(). Set this to false if you |
| * want every keystroke to be inserted into the text immediately which will |
| * result in a TextOperationEvent.CHANGE event for each character. One |
| * place this is needed is for the type-ahead feature of the editable combo |
| * box. |
| */ |
| mx_internal var batchTextInput:Boolean = true; |
| |
| /** |
| * @private |
| * True if we've seen a MOUSE_DOWN event and haven't seen the |
| * corresponding MOUSE_UP event. |
| */ |
| private var mouseDown:Boolean = false; |
| |
| /** |
| * @private |
| * Hold the previous editingMode while using a specific instance manager |
| * so that the editingMode can be restored when the instance manager is |
| * released. |
| */ |
| private var priorEditingMode:String; |
| |
| /** |
| * @private |
| * Cache the width constraint as set by the layout in setLayoutBoundsSize() |
| * so that text reflow can be calculated during a subsequent measure pass. |
| */ |
| private var widthConstraint:Number = NaN; |
| |
| /** |
| * @private |
| * Cache the height constraint as set by the layout in setLayoutBoundsSize() |
| * so that text reflow can be calculated during a subsequent measure pass. |
| */ |
| private var heightConstraint:Number = NaN; |
| |
| /** |
| * @private |
| * If the selection was via the selectRange() or selectAll() api, remember |
| * that until the next selection is set, either interactively or via the |
| * API. |
| */ |
| private var hasProgrammaticSelectionRange:Boolean = false; |
| |
| /** |
| * @private |
| * True if this component sizes itself based on its actual |
| * contents. |
| */ |
| mx_internal var autoSize:Boolean = false; |
| |
| /** |
| * @private |
| */ |
| private var lastMeasuredWidth:Number = NaN; |
| |
| /** |
| * @private |
| */ |
| private var lastMeasuredHeight:Number = NaN; |
| |
| /** |
| * @private |
| */ |
| private var lastUnscaledWidth:Number; |
| /** |
| * @private |
| */ |
| private var lastUnscaledHeight:Number; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Overridden properties: UIComponent |
| // |
| //-------------------------------------------------------------------------- |
| |
| //---------------------------------- |
| // baselinePosition |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| override public function get baselinePosition():Number |
| { |
| return getStyle("paddingTop") + ascent; |
| } |
| |
| //---------------------------------- |
| // enabled |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var enabledChanged:Boolean = false; |
| |
| /** |
| * @private |
| */ |
| override public function set enabled(value:Boolean):void |
| { |
| if (value == super.enabled) |
| return; |
| |
| super.enabled = value; |
| enabledChanged = true; |
| |
| invalidateProperties(); |
| invalidateDisplayList(); |
| } |
| |
| //---------------------------------- |
| // explicitHeight |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| override public function set explicitHeight(value:Number):void |
| { |
| super.explicitHeight = value; |
| |
| heightConstraint = NaN; |
| |
| // Because of autoSizing, the size and display might be impacted. |
| invalidateSize(); |
| invalidateDisplayList(); |
| } |
| |
| //---------------------------------- |
| // explicitWidth |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| override public function set explicitWidth(value:Number):void |
| { |
| super.explicitWidth = value; |
| |
| widthConstraint = NaN; |
| |
| // Because of autoSizing, the size and display might be impacted. |
| invalidateSize(); |
| invalidateDisplayList(); |
| } |
| |
| //---------------------------------- |
| // isTruncated |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| public function get isTruncated():Boolean |
| { |
| // This class does not support truncation |
| return false; |
| } |
| |
| //---------------------------------- |
| // percentHeight |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| override public function set percentHeight(value:Number):void |
| { |
| super.percentHeight = value; |
| |
| heightConstraint = NaN; |
| |
| // If we were autoSizing and now we are not we need to remeasure. |
| invalidateSize(); |
| invalidateDisplayList(); |
| } |
| |
| //---------------------------------- |
| // percentWidth |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| override public function set percentWidth(value:Number):void |
| { |
| super.percentWidth = value; |
| |
| widthConstraint = NaN; |
| |
| // If we were autoSizing and now we are not we need to remeasure. |
| invalidateSize(); |
| invalidateDisplayList(); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Properties: ISystemCursorClient |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * True if the system cursor should always be shown when the mouse |
| * moves over the component. If false, the custom cursor will be shown. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get showSystemCursor():Boolean |
| { |
| return editable; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Properties: IViewport |
| // |
| //-------------------------------------------------------------------------- |
| |
| //---------------------------------- |
| // clipAndEnableScrolling |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _clipAndEnableScrolling:Boolean = false; |
| |
| /** |
| * @private |
| */ |
| private var clipAndEnableScrollingChanged:Boolean = false; |
| |
| /** |
| * @copy spark.core.IViewport#clipAndEnableScrolling |
| * |
| * @default false |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get clipAndEnableScrolling():Boolean |
| { |
| return _clipAndEnableScrolling; |
| } |
| |
| /** |
| * @private |
| * Set to true by a scroller when it installs this as a viewport. |
| * Set to false by a scroller when it uninstalls this as a viewport. |
| */ |
| public function set clipAndEnableScrolling(value:Boolean):void |
| { |
| if (value == _clipAndEnableScrolling) |
| return; |
| |
| _clipAndEnableScrolling = value; |
| clipAndEnableScrollingChanged = true; |
| |
| invalidateProperties(); |
| } |
| |
| //---------------------------------- |
| // contentHeight |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _contentHeight:Number = 0; |
| |
| [Bindable("propertyChange")] |
| |
| /** |
| * The height of the text. |
| * |
| * <p>Due to the fact that the Text Layout Framework |
| * virtualizes TextLines for performance, |
| * this height will initially be an estimate |
| * if the component cannot display all of the text. |
| * If you scroll to the end of the text, |
| * all the TextLines will get composed |
| * and the <code>contentHeight</code> will be exact.</p> |
| * |
| * <p>To scroll over the text vertically, vary the |
| * <code>verticalScrollPosition</code> between 0 and |
| * <code>contentHeight - height</code>.</p> |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get contentHeight():Number |
| { |
| return _contentHeight; |
| } |
| |
| //---------------------------------- |
| // contentWidth |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _contentWidth:Number = 0; |
| |
| [Bindable("propertyChange")] |
| |
| /** |
| * The width of the text. |
| * |
| * <p>Due to the fact that the Text Layout Framework |
| * virtualizes TextLines for performance, |
| * this width will initially be an estimate |
| * if the component cannot display all of the text. |
| * If you scroll to the end of the text, |
| * all the TextLines will get composed |
| * and the <code>contentWidth</code> will be exact.</p> |
| * |
| * <p>To scroll over the text horizontally, vary the |
| * <code>horizontalScrollPosition</code> between 0 and |
| * <code>contentWidth - width</code>.</p> |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get contentWidth():Number |
| { |
| return _contentWidth; |
| } |
| |
| //---------------------------------- |
| // horizontalScrollPosition |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _horizontalScrollPosition:Number = 0; |
| |
| /** |
| * @private |
| */ |
| private var horizontalScrollPositionChanged:Boolean = false; |
| |
| [Bindable("propertyChange")] |
| [Inspectable(defaultValue="0", minValue="0.0")] |
| |
| /** |
| * The number of pixels by which the text is scrolled horizontally. |
| * |
| * <p>To scroll over the text horizontally, vary the |
| * <code>horizontalScrollPosition</code> between 0 and |
| * <code>contentWidth - width</code>.</p> |
| * |
| * @default 0 |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get horizontalScrollPosition():Number |
| { |
| return _horizontalScrollPosition; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set horizontalScrollPosition(value:Number):void |
| { |
| // Convert NaN to 0 to keep TCM happy. |
| if (isNaN(value)) |
| value = 0; |
| |
| if (value == _horizontalScrollPosition) |
| return; |
| |
| _horizontalScrollPosition = value; |
| horizontalScrollPositionChanged = true; |
| |
| invalidateProperties(); |
| |
| // Note: TLF takes care of updating the container when the scroll |
| // position is set so there is no need for us to invalidate the |
| // display list. |
| } |
| |
| //---------------------------------- |
| // verticalScrollPosition |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _verticalScrollPosition:Number = 0; |
| |
| /** |
| * @private |
| */ |
| private var verticalScrollPositionChanged:Boolean = false; |
| |
| [Bindable("propertyChange")] |
| [Inspectable(defaultValue="0", minValue="0.0")] |
| |
| /** |
| * The number of pixels by which the text is scrolled vertically. |
| * |
| * <p>To scroll over the text vertically, vary the |
| * <code>verticalScrollPosition</code> between 0 and |
| * <code>contentHeight - height</code>.</p> |
| * |
| * @default 0 |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get verticalScrollPosition():Number |
| { |
| return _verticalScrollPosition; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set verticalScrollPosition(value:Number):void |
| { |
| // Convert NaN to 0 to keep TCM happy. |
| if (isNaN(value)) |
| value = 0; |
| |
| if (value == _verticalScrollPosition) |
| return; |
| |
| _verticalScrollPosition = value; |
| verticalScrollPositionChanged = true; |
| |
| invalidateProperties(); |
| |
| // Note: TLF takes care of updating the container when the scroll |
| // position is set so there is no need for us to invalidate the |
| // display list. |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Properties |
| // |
| //-------------------------------------------------------------------------- |
| |
| //---------------------------------- |
| // content |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _content:Object; |
| |
| /** |
| * @private |
| */ |
| private var contentChanged:Boolean = false; |
| |
| /** |
| * @private |
| * This metadata tells the MXML compiler to disable some of its default |
| * interpretation of the value specified for the 'content' property. |
| * Normally, for properties of type Object, it assumes that things |
| * looking like numbers are numbers and things looking like arrays |
| * are arrays. But <content>1</content> should generate code to set the |
| * content to the String "1", not the int 1, and <content>[1]</content> |
| * should set it to the String "[1]", not the Array [ 1 ]. |
| * However, {...} continues to be interpreted as a databinding |
| * expression, and @Resource(...), @Embed(...), etc. |
| * as compiler directives. |
| * Similar metadata on TLF classes causes the same rules to apply |
| * within <p>, <span>, etc. |
| */ |
| [RichTextContent] |
| |
| /** |
| * This property is intended for use in MXML at compile time; |
| * to get or set rich text content at runtime, |
| * please use the <code>textFlow</code> property instead. |
| * |
| * <p>The <code>content</code> property is the default property |
| * for RichEditableText, so that you can write MXML such as |
| * <pre> |
| * <s:RichEditableText>Hello <s:span fontWeight="bold"/>World</s:span></s:RichEditableText> |
| * </pre> |
| * and have the String and SpanElement that you specify |
| * as the content be used to create a TextFlow.</p> |
| * |
| * <p>This property is typed as Object because you can set it to |
| * to a String, a FlowElement, or an Array of Strings and FlowElements. |
| * In the example above, you are specifying the content |
| * to be a 2-element Array whose first element is the String |
| * "Hello" and whose second element is a SpanElement with the text |
| * "World" in boldface.</p> |
| * |
| * <p>No matter how you specify the content, it gets converted |
| * into a TextFlow, and when you get this property, you will get |
| * the resulting TextFlow.</p> |
| * |
| * <p>Adobe recommends using <code>textFlow</code> property |
| * to get and set rich text content at runtime, |
| * because it is strongly typed as a TextFlow |
| * rather than as an Object. |
| * A TextFlow is the canonical representation |
| * for rich text content in the Text Layout Framework.</p> |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get content():Object |
| { |
| return textFlow; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set content(value:Object):void |
| { |
| // Treat setting the 'content' to null |
| // as if 'text' were being set to the empty String |
| // (which is the default state). |
| if (value == null) |
| { |
| text = ""; |
| return; |
| } |
| |
| if (value == _content) |
| return; |
| |
| _content = value; |
| contentChanged = true; |
| source = "content"; |
| |
| // Of 'text', 'textFlow', and 'content', the last one set wins. |
| textChanged = false; |
| textFlowChanged = false; |
| |
| // The other two are now invalid and must be recalculated when needed. |
| _text = null; |
| _textFlow = null; |
| |
| invalidateProperties(); |
| invalidateSize(); |
| invalidateDisplayList(); |
| |
| dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT)); |
| } |
| |
| //---------------------------------- |
| // displayAsPassword |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _displayAsPassword:Boolean = false; |
| |
| /** |
| * @private |
| */ |
| private var displayAsPasswordChanged:Boolean = false; |
| |
| [Inspectable(category="General", defaultValue="false")] |
| |
| /** |
| * @copy flash.text.TextField#displayAsPassword |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get displayAsPassword():Boolean |
| { |
| return _displayAsPassword; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set displayAsPassword(value:Boolean):void |
| { |
| if (value == _displayAsPassword) |
| return; |
| |
| _displayAsPassword = value; |
| displayAsPasswordChanged = true; |
| |
| invalidateProperties(); |
| invalidateSize(); |
| invalidateDisplayList(); |
| } |
| |
| //---------------------------------- |
| // editable |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _editable:Boolean = true; |
| |
| /** |
| * @private |
| */ |
| private var editableChanged:Boolean = false; |
| |
| [Inspectable(category="General", defaultValue="true")] |
| |
| /** |
| * A flag indicating whether the user is allowed |
| * to edit the text in this control. |
| * |
| * <p>If <code>true</code>, the mouse cursor will change to an i-beam |
| * when over the bounds of this control. |
| * If <code>false</code>, the mouse cursor will remain an arrow.</p> |
| * |
| * <p>If this property is <code>true</code>, |
| * the <code>selectable</code> property is ignored.</p> |
| * |
| * @default true |
| * |
| * @see spark.components.RichEditableText#selectable |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get editable():Boolean |
| { |
| return _editable; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set editable(value:Boolean):void |
| { |
| if (value == _editable) |
| return; |
| |
| _editable = value; |
| editableChanged = true; |
| |
| invalidateProperties(); |
| invalidateDisplayList(); |
| } |
| |
| //---------------------------------- |
| // editingMode |
| //---------------------------------- |
| |
| [Inspectable(category="General", defaultValue="readWrite", enumeration="readOnly,readWrite,readSelect")] |
| |
| /** |
| * @private |
| * The editingMode of this component's TextContainerManager. |
| * Note that this is not a public property |
| * and does not use the invalidation mechanism. |
| */ |
| private function get editingMode():String |
| { |
| // Note: this could be called before all properties are committed. |
| |
| if (enabledChanged || editableChanged || selectableChanged) |
| { |
| updateEditingMode(); |
| |
| enabledChanged = false; |
| editableChanged = false; |
| selectableChanged = false; |
| } |
| |
| return _textContainerManager.editingMode; |
| } |
| |
| /** |
| * @private |
| */ |
| private function set editingMode(value:String):void |
| { |
| var lastEditingMode:String = _textContainerManager.editingMode; |
| |
| if (lastEditingMode == value) |
| return; |
| |
| _textContainerManager.editingMode = value; |
| |
| // Make sure the selection manager selection is in sync with the |
| // current selection. |
| if (value != EditingMode.READ_ONLY && |
| _selectionAnchorPosition != -1 && _selectionActivePosition != -1) |
| { |
| var selectionManager:ISelectionManager = |
| _textContainerManager.beginInteraction(); |
| |
| selectionManager.selectRange( |
| _selectionAnchorPosition, _selectionActivePosition); |
| |
| _textContainerManager.endInteraction(); |
| } |
| } |
| |
| //---------------------------------- |
| // enableIME |
| //---------------------------------- |
| |
| /** |
| * A flag that indicates whether the IME should |
| * be enabled when the component receives focus. |
| * |
| * @returns true if the component is editable and it is not displaying a password. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get enableIME():Boolean |
| { |
| return editable && !displayAsPassword; |
| } |
| |
| //---------------------------------- |
| // heightInLines |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _heightInLines:Number = NaN; |
| |
| /** |
| * @private |
| */ |
| private var heightInLinesChanged:Boolean = false; |
| |
| [Inspectable(category="General", minValue="0.0")] |
| |
| /** |
| * The default height of the control, measured in lines. |
| * |
| * <p>The control's formatting styles, such as <code>fontSize</code> |
| * and <code>lineHeight</code>, are used to calculate the line height |
| * in pixels.</p> |
| * |
| * <p>You would, for example, set this property to 5 if you want |
| * the height of the RichEditableText to be sufficient |
| * to display five lines of text.</p> |
| * |
| * <p>If this property is <code>NaN</code> (the default), |
| * then the component's default height will be determined |
| * from the text to be displayed.</p> |
| * |
| * <p>This property will be ignored if you specify an explicit height, |
| * a percent height, or both <code>top</code> and <code>bottom</code> |
| * constraints.</p> |
| * |
| * <p>This property will also be ignored if the <code>typicalText</code> |
| * property is specified.</p> |
| * |
| * <p>RichEditableText's <code>measure()</code> method uses |
| * <code>widthInChars</code> and <code>heightInLines</code> |
| * to determine the <code>measuredWidth</code> |
| * and <code>measuredHeight</code>. |
| * These are similar to the <code>cols</code> and <code>rows</code> |
| * of an HTML TextArea.</p> |
| * |
| * <p>Since both <code>widthInChars</code> and <code>heightInLines</code> |
| * default to <code>NaN</code>, RichTextEditable "autosizes" by default: |
| * it starts out very small if it has no text, grows in width as you |
| * type, and grows in height when you press Enter to start a new line.</p> |
| * |
| * @default NaN |
| * |
| * @see spark.components.RichEditableText#widthInChars |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get heightInLines():Number |
| { |
| return _heightInLines; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set heightInLines(value:Number):void |
| { |
| if (value == _heightInLines) |
| return; |
| |
| _heightInLines = value; |
| heightInLinesChanged = true; |
| |
| heightConstraint = NaN; |
| |
| invalidateProperties(); |
| invalidateSize(); |
| invalidateDisplayList(); |
| } |
| |
| //---------------------------------- |
| // imeMode |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _imeMode:String = null; |
| |
| /** |
| * Specifies the IME (input method editor) mode. |
| * The IME enables users to enter text in Chinese, Japanese, and Korean. |
| * Flex sets the specified IME mode when the control gets the focus, |
| * and sets it back to the previous value when the control loses the focus. |
| * |
| * <p>The flash.system.IMEConversionMode class defines constants for the |
| * valid values for this property. |
| * You can also specify <code>null</code> to specify no IME.</p> |
| * |
| * @default null |
| * |
| * @see flash.system.IMEConversionMode |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get imeMode():String |
| { |
| return _imeMode; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set imeMode(value:String):void |
| { |
| _imeMode = value; |
| } |
| |
| //---------------------------------- |
| // lineBreak |
| //---------------------------------- |
| |
| [Inspectable(environment="none")] |
| |
| /** |
| * @private |
| * |
| * This property is only defined to implement the IEditableText |
| * interface. The lineBreak style should be used instead of this |
| * property. |
| */ |
| public function get lineBreak():String |
| { |
| return getStyle("lineBreak"); |
| } |
| |
| /** |
| * @private |
| */ |
| public function set lineBreak(value:String):void |
| { |
| setStyle("lineBreak", value); |
| } |
| |
| //---------------------------------- |
| // maxChars |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _maxChars:int = 0; |
| |
| [Inspectable(category="General", defaultValue="0")] |
| |
| /** |
| * @copy flash.text.TextField#maxChars |
| * |
| * @default 0 |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get maxChars():int |
| { |
| return _maxChars; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set maxChars(value:int):void |
| { |
| _maxChars = value; |
| } |
| |
| //---------------------------------- |
| // multiline |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _multiline:Boolean = true; |
| |
| [Inspectable(category="General", defaultValue="true")] |
| |
| /** |
| * Determines whether the user can enter multiline text. |
| * |
| * <p>If <code>true</code>, the Enter key starts a new paragraph. |
| * If <code>false</code>, the Enter key doesn't affect the text |
| * but causes the RichEditableText to dispatch an <code>"enter"</code> |
| * event. If you paste text into the RichEditableText with a multiline |
| * value of <code>false</code>, newlines are stripped out of the text. </p> |
| * |
| * @default true |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get multiline():Boolean |
| { |
| return _multiline; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set multiline(value:Boolean):void |
| { |
| _multiline = value; |
| } |
| |
| //---------------------------------- |
| // restrict |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _restrict:String = null; |
| |
| [Inspectable(category="General", defaultValue="null")] |
| |
| /** |
| * @copy flash.text.TextField#restrict |
| * |
| * @default null |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get restrict():String |
| { |
| return _restrict; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set restrict(value:String):void |
| { |
| _restrict = value; |
| } |
| |
| //---------------------------------- |
| // selectable |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _selectable:Boolean = true; |
| |
| /** |
| * @private |
| */ |
| private var selectableChanged:Boolean = false; |
| |
| [Inspectable(category="General", defaultValue="true")] |
| |
| /** |
| * A flag indicating whether the content is selectable |
| * with the mouse, or with the keyboard when the control |
| * has the keyboard focus. |
| * |
| * <p>Making the text selectable lets you copy text from the control.</p> |
| * |
| * <p>This property is ignored if the <code>editable</code> |
| * property is <code>true</code>.</p> |
| * |
| * @default true |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get selectable():Boolean |
| { |
| return _selectable; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set selectable(value:Boolean):void |
| { |
| if (value == _selectable) |
| return; |
| |
| _selectable = value; |
| selectableChanged = true; |
| |
| invalidateProperties(); |
| invalidateDisplayList(); |
| } |
| |
| //---------------------------------- |
| // selectionActivePosition |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _selectionActivePosition:int = -1; |
| |
| [Bindable("selectionChange")] |
| [Inspectable(category="General", defaultValue="-1")] |
| |
| /** |
| * A character position, relative to the beginning of the |
| * <code>text</code> String, specifying the end of the selection |
| * that moves when the selection is extended with the arrow keys. |
| * |
| * <p>The active position may be either the start |
| * or the end of the selection.</p> |
| * |
| * <p>For example, if you drag-select from position 12 to position 8, |
| * then <code>selectionAnchorPosition</code> will be 12 |
| * and <code>selectionActivePosition</code> will be 8, |
| * and when you press Left-Arrow <code>selectionActivePosition</code> |
| * will become 7.</p> |
| * |
| * <p>A value of -1 indicates "not set".</p> |
| * |
| * @default -1 |
| * |
| * @see spark.components.RichEditableText#selectionAnchorPosition |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get selectionActivePosition():int |
| { |
| return _selectionActivePosition; |
| } |
| |
| //---------------------------------- |
| // selectionAnchorPosition |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _selectionAnchorPosition:int = -1; |
| |
| [Bindable("selectionChange")] |
| [Inspectable(category="General", defaultValue="-1")] |
| |
| /** |
| * A character position, relative to the beginning of the |
| * <code>text</code> String, specifying the end of the selection |
| * that stays fixed when the selection is extended with the arrow keys. |
| * |
| * <p>The anchor position may be either the start |
| * or the end of the selection.</p> |
| * |
| * <p>For example, if you drag-select from position 12 to position 8, |
| * then <code>selectionAnchorPosition</code> will be 12 |
| * and <code>selectionActivePosition</code> will be 8, |
| * and when you press Left-Arrow <code>selectionActivePosition</code> |
| * will become 7.</p> |
| * |
| * <p>A value of -1 indicates "not set".</p> |
| * |
| * @default -1 |
| * |
| * @see spark.components.RichEditableText#selectionActivePosition |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get selectionAnchorPosition():int |
| { |
| return _selectionAnchorPosition; |
| } |
| |
| //---------------------------------- |
| // selectionHighlighting |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _selectionHighlighting:String = |
| TextSelectionHighlighting.WHEN_FOCUSED; |
| |
| /** |
| * @private |
| * To indicate either selection highlighting or selection styles have |
| * changed. |
| */ |
| private var selectionFormatsChanged:Boolean = false; |
| |
| [Inspectable(category="General", enumeration="always,whenActive,whenFocused", defaultValue="whenFocused")] |
| |
| /** |
| * Determines when the text selection is highlighted. |
| * |
| * <p>The allowed values are specified by the |
| * spark.components.TextSelectionHighlighting class. |
| * Possible values are <code>TextSelectionHighlighting.WHEN_FOCUSED</code>, |
| * <code>TextSelectionHighlighting.WHEN_ACTIVE</code>, |
| * and <code>TextSelectionHighlighting.ALWAYS</code>.</p> |
| * |
| * <p><code>WHEN_FOCUSED</code> shows the text selection |
| * only when the component has keyboard focus.</p> |
| * |
| * <p><code>WHEN_ACTIVE</code> shows the text selection whenever |
| * the component's window is active, even if the component |
| * doesn't have the keyboard focus.</p> |
| * |
| * <p><code>ALWAYS</code> shows the text selection, |
| * even if the component doesn't have the keyboard focus |
| * or if the component's window isn't the active window.</p> |
| * |
| * @default TextSelectionHighlighting.WHEN_FOCUSED |
| * |
| * @see spark.components.TextSelectionHighlighting |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get selectionHighlighting():String |
| { |
| return _selectionHighlighting; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set selectionHighlighting(value:String):void |
| { |
| if (value == _selectionHighlighting) |
| return; |
| |
| _selectionHighlighting = value; |
| selectionFormatsChanged = true; |
| |
| invalidateProperties(); |
| invalidateDisplayList(); |
| } |
| |
| //---------------------------------- |
| // text |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _text:String = ""; |
| |
| /** |
| * @private |
| */ |
| private var textChanged:Boolean = false; |
| |
| [Bindable("change")] |
| [Inspectable(category="General", defaultValue="")] |
| |
| /** |
| * The text String displayed by this component. |
| * |
| * <p>Setting this property affects the <code>textFlow</code> property |
| * and vice versa.</p> |
| * |
| * <p>If you set the <code>text</code> to a String such as |
| * <code>"Hello World"</code> and get the <code>textFlow</code>, |
| * it will be a TextFlow containing a single ParagraphElement |
| * with a single SpanElement.</p> |
| * |
| * <p>If you set the <code>text</code> to null, it will be |
| * set to the default value which is an empty string.</p> |
| * |
| * <p>If the text contains explicit line breaks -- |
| * CR ("\r"), LF ("\n"), or CR+LF ("\r\n") -- |
| * then the content will be set to a TextFlow |
| * which contains multiple paragraphs, each with one span.</p> |
| * |
| * <p>If you set the <code>textFlow</code> and get the <code>text</code>, |
| * the text in each paragraph will be separated by a single |
| * LF ("\n").</p> |
| * |
| * <p>Setting this property also affects the properties |
| * specifying the control's scroll position and the text selection. |
| * It resets the <code>horizontalScrollPosition</code> |
| * and <code>verticalScrollPosition</code> to 0. |
| * Starting with Flex 4.6, the <code>selectionAnchorPosition</code> and |
| * <code>selectionActivePosition</code> are preserved. |
| * Previously, the <code>selectionAnchorPosition</code> |
| * and <code>selectionActivePosition</code> were set |
| * to -1 to clear the selection.</p> |
| * |
| * @default "" |
| * |
| * @see spark.components.RichEditableText#textFlow |
| * @see spark.components.RichEditableText#horizontalScrollPosition |
| * @see spark.components.RichEditableText#verticalScrollPosition |
| * @see spark.components.RichEditableText#selectionAnchorPosition |
| * @see spark.components.RichEditableText#selectionActivePosition |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get text():String |
| { |
| // Note: if displayAsPassword, _text will contain the actual text and the text flow will |
| // contain the same number of passwordChars. |
| |
| // Go to the source if there isn't a pending change. getText has its own buffering and |
| // only extracts the text from the TextFlow when it is damaged. |
| if (_textContainerManager && !textChanged && !textFlowChanged && !contentChanged && !displayAsPassword) |
| return _textContainerManager.getText("\n"); |
| |
| // Extracting the plaintext from a TextFlow is somewhat expensive, |
| // as it involves iterating over the leaf FlowElements in the TextFlow. |
| // Therefore we do this extraction only when necessary, namely when |
| // you first set the 'content' or the 'textFlow' |
| // (or mutate the TextFlow), and then get the 'text'. |
| if (_text == null) |
| { |
| // If 'content' was last set, |
| // we have to first turn that into a TextFlow. |
| if (_content != null) |
| _textFlow = createTextFlowFromContent(_content); |
| |
| // Once we have a TextFlow, we can export its plain text. |
| _text = staticPlainTextExporter.export( |
| _textFlow, ConversionType.STRING_TYPE) as String; |
| } |
| |
| return _text; |
| } |
| |
| /** |
| * @private |
| * This will create a TextFlow with a single paragraph with a single span |
| * with exactly the text specified. If there is whitespace and line |
| * breaks in the text, they will remain, regardless of the settings of |
| * the lineBreak and whiteSpaceCollapse styles. |
| * |
| * Similiar to TextField, the selection is preserved when the text is set. |
| * If the new text length is less than the existing text length, the selection |
| * may change. |
| */ |
| public function set text(value:String):void |
| { |
| // Treat setting the 'text' to null |
| // as if it were set to the empty String |
| // (which is the default state). |
| if (value == null) |
| value = ""; |
| |
| // If value is the same as _text, make sure if was not produced from |
| // setting 'textFlow' or 'content'. For example, if you set a TextFlow |
| // corresponding to "Hello <span color="OxFF0000">World</span>" |
| // and then get the 'text', it will be the String "Hello World" |
| // But if you then set the 'text' to "Hello World" |
| // this represents a change: the "World" should no longer be red. |
| // |
| // Note: this is needed to stop two-binding from recursing. |
| if (source == "text" && text == value) |
| return; |
| |
| _text = value; |
| textChanged = true; |
| source = "text"; |
| |
| // Of 'text', 'textFlow', and 'content', the last one set wins. |
| textFlowChanged = false; |
| contentChanged = false; |
| |
| // The other two are now invalid and must be recalculated when needed. |
| _textFlow = null; |
| _content = null; |
| |
| invalidateProperties(); |
| invalidateSize(); |
| invalidateDisplayList(); |
| |
| dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT)); |
| } |
| |
| //---------------------------------- |
| // textContainerManager |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _textContainerManager:RichEditableTextContainerManager; |
| |
| /** |
| * @private |
| * The TLF TextContainerManager instance that displays, |
| * scrolls, and edits the text in this component. |
| */ |
| mx_internal function get textContainerManager():TextContainerManager |
| { |
| return _textContainerManager; |
| } |
| |
| //---------------------------------- |
| // textFlow |
| //---------------------------------- |
| |
| /** |
| * @private |
| * Storage for the textFlow property. |
| */ |
| private var _textFlow:TextFlow; |
| |
| /** |
| * @private |
| */ |
| private var textFlowChanged:Boolean = false; |
| |
| /** |
| * The TextFlow representing the rich text displayed by this component. |
| * |
| * <p>A TextFlow is the most important class |
| * in the Text Layout Framework (TLF). |
| * It is the root of a tree of FlowElements |
| * representing rich text content.</p> |
| * |
| * <p>You normally create a TextFlow from TLF markup |
| * using the <code>TextFlowUtil.importFromString()</code> |
| * or <code>TextFlowUtil.importFromXML()</code> methods. |
| * Alternately, you can use TLF's TextConverter class |
| * (which can import a subset of HTML) or build a TextFlow |
| * using methods like <code>addChild()</code> on TextFlow.</p> |
| * |
| * <p>Setting this property affects the <code>text</code> property |
| * and vice versa.</p> |
| * |
| * <p>If you set the <code>textFlow</code> and get the <code>text</code>, |
| * the text in each paragraph will be separated by a single |
| * LF ("\n").</p> |
| * |
| * <p>If you set the <code>text</code> to a String such as |
| * <code>"Hello World"</code> and get the <code>textFlow</code>, |
| * it will be a TextFlow containing a single ParagraphElement |
| * with a single SpanElement.</p> |
| * |
| * <p>If the text contains explicit line breaks -- |
| * CR ("\r"), LF ("\n"), or CR+LF ("\r\n") -- |
| * then the content will be set to a TextFlow |
| * which contains multiple paragraphs, each with one span.</p> |
| * |
| * <p>Setting this property also affects the properties |
| * specifying the control's scroll position and the text selection. |
| * It resets the <code>horizontalScrollPosition</code> |
| * and <code>verticalScrollPosition</code> to 0, |
| * and it sets the <code>selectionAnchorPosition</code> |
| * and <code>selectionActivePosition</code> |
| * to -1 to clear the selection.</p> |
| * |
| * <p>To turn a TextFlow object into TLF markup, |
| * use the <code>TextFlowUtil.export()</code> markup.</p> |
| * |
| * <p>A single TextFlow cannot be shared by multiple instances |
| * of RichEditableText. |
| * To display the same text in a second instance, you must create |
| * a second TextFlow, either by using <code>TextFlowUtil.export()</code> |
| * and <code>TextFlowUtil.importFromXML()</code> or by using |
| * the <code>deepCopy()</code> method on TextFlow.</p> |
| * |
| * @see spark.utils.TextFlowUtil#importFromString() |
| * @see spark.utils.TextFlowUtil#importFromXML() |
| * @see spark.components.RichEditableText#text |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 2.5 |
| * @productversion Flex 4.5 |
| */ |
| public function get textFlow():TextFlow |
| { |
| // Note: this could be called before all properties are committed. |
| |
| // We might not have a valid _textFlow for two reasons: |
| // either because the 'text' was set (which is the state |
| // after construction) or because the 'content' was set. |
| if (!_textFlow) |
| { |
| if (_content != null) |
| { |
| _textFlow = createTextFlowFromContent(_content); |
| _content = null; |
| } |
| else |
| { |
| _textFlow = staticPlainTextImporter.importToFlow(_text); |
| } |
| textFlowChanged = true; |
| } |
| |
| // Make sure the interactionManager and controller are added to this textFlow. |
| if (textChanged || contentChanged || textFlowChanged) |
| { |
| _textContainerManager.setTextFlow(_textFlow); |
| textChanged = contentChanged = textFlowChanged = false; |
| } |
| |
| // If not read-only, make sure the textFlow has a composer in |
| // place so that it can be modified by the caller if desired. |
| if (editingMode != EditingMode.READ_ONLY) |
| { |
| _textContainerManager.beginInteraction(); |
| _textContainerManager.endInteraction(); |
| } |
| |
| return _textFlow; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set textFlow(value:TextFlow):void |
| { |
| // Treat setting the 'textFlow' to null |
| // as if 'text' were being set to the empty String |
| // (which is the default state). |
| if (value == null) |
| { |
| text = ""; |
| return; |
| } |
| |
| if (value == _textFlow) |
| return; |
| |
| _textFlow = value; |
| textFlowChanged = true; |
| source = "textFlow"; |
| |
| // Of 'text', 'textFlow', and 'content', the last one set wins. |
| textChanged = false; |
| contentChanged = false; |
| |
| // The other two are now invalid and must be recalculated when needed. |
| _text = null |
| _content = null; |
| |
| invalidateProperties(); |
| invalidateSize(); |
| invalidateDisplayList(); |
| |
| dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT)); |
| } |
| |
| //---------------------------------- |
| // typicalText |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var typicalTextChanged:Boolean; |
| |
| /** |
| * @private |
| */ |
| private var _typicalText:String; |
| |
| /** |
| * @private |
| * Used when _typicalText is multiline |
| */ |
| private var _typicalTextFlow:TextFlow; |
| |
| [Inspectable(category="General", defaultValue="null")] |
| |
| /** |
| * Text that is used to determine |
| * the default width and height of the control, |
| * without actually being displayed. |
| * |
| * <p>This property will be ignored if you specify an explicit width, |
| * a percent width, or both <code>left</code> and <code>right</code> |
| * constraints.</p> |
| * |
| * <p>Use of this property causes the <code>widthInChars</code> |
| * and <code>heightInLines</code> properties to be ignored. </p> |
| * |
| * @default null |
| * |
| * @see spark.primitives.heightInLines |
| * @see spark.primitives.widthInChars |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10.2 |
| * @playerversion AIR 2.0 |
| * @productversion Flex 4.5 |
| */ |
| public function get typicalText():String |
| { |
| return _typicalText; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set typicalText(value:String):void |
| { |
| if (value == _typicalText) |
| return; |
| |
| _typicalText = value; |
| |
| typicalTextChanged = true; |
| |
| invalidateProperties(); |
| invalidateSize(); |
| invalidateDisplayList(); |
| } |
| |
| //---------------------------------- |
| // widthInChars |
| //---------------------------------- |
| |
| /** |
| * @private |
| * These are measured in ems. |
| */ |
| private var _widthInChars:Number = NaN; |
| |
| /** |
| * @private |
| */ |
| private var widthInCharsChanged:Boolean = true; |
| |
| [Inspectable(category="General", minValue="0.0")] |
| |
| /** |
| * The default width of the control, measured in em units. |
| * |
| * <p>An em is a unit of typographic measurement |
| * equal to the point size. |
| * It is not necessarily exactly the width of the "M" character, |
| * but in many fonts the "M" is about one em wide. |
| * The control's <code>fontSize</code> style is used, |
| * to calculate the em unit in pixels.</p> |
| * |
| * <p>You would, for example, set this property to 20 if you want |
| * the width of the RichEditableText to be sufficient |
| * to display about 20 characters of text.</p> |
| * |
| * <p>If this property is <code>NaN</code> (the default), |
| * then the component's default width will be determined |
| * from the text to be displayed.</p> |
| * |
| * <p>This property will be ignored if you specify an explicit width, |
| * a percent width, or both <code>left</code> and <code>right</code> |
| * constraints.</p> |
| * |
| * <p>This property will also be ignored if the <code>typicalText</code> |
| * property is specified.</p> |
| * |
| * <p>RichEditableText's <code>measure()</code> method uses |
| * <code>widthInChars</code> and <code>heightInLines</code> |
| * to determine the <code>measuredWidth</code> |
| * and <code>measuredHeight</code>. |
| * These are similar to the <code>cols</code> and <code>rows</code> |
| * of an HTML TextArea.</p> |
| * |
| * <p>Since both <code>widthInChars</code> and <code>heightInLines</code> |
| * default to <code>NaN</code>, RichTextEditable "autosizes" by default: |
| * it starts out very small if it has no text, grows in width as you |
| * type, and grows in height when you press Enter to start a new line.</p> |
| * |
| * @default NaN |
| * |
| * @see spark.primitives.heightInLines |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function get widthInChars():Number |
| { |
| return _widthInChars; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set widthInChars(value:Number):void |
| { |
| if (value == _widthInChars) |
| return; |
| |
| _widthInChars = value; |
| widthInCharsChanged = true; |
| |
| widthConstraint = NaN; |
| |
| invalidateProperties(); |
| invalidateSize(); |
| invalidateDisplayList(); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Overridden Methods: UIComponent |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| override protected function initializeAccessibility():void |
| { |
| if (RichEditableText.createAccessibilityImplementation != null) |
| RichEditableText.createAccessibilityImplementation(this); |
| } |
| |
| /** |
| * @private |
| */ |
| override public function parentChanged(p:DisplayObjectContainer):void |
| { |
| if (focusManager) |
| { |
| focusManager.removeEventListener(FlexEvent.FLEX_WINDOW_ACTIVATE, |
| _textContainerManager.activateHandler) |
| focusManager.removeEventListener(FlexEvent.FLEX_WINDOW_DEACTIVATE, |
| _textContainerManager.deactivateHandler) |
| } |
| |
| super.parentChanged(p); |
| |
| if (focusManager) |
| { |
| addActivateHandlers(); |
| } |
| else |
| { |
| // if no focusmanager yet, add capture phase to detect when it |
| // gets added |
| if (systemManager) |
| systemManager.getSandboxRoot().addEventListener(FlexEvent.ADD_FOCUS_MANAGER, |
| addFocusManagerHandler, true, 0, true) |
| else |
| // no systemManager yet? Check again when added to stage |
| addEventListener(Event.ADDED_TO_STAGE, addedToStageHandler); |
| } |
| |
| } |
| |
| /** |
| * @private |
| */ |
| override public function removeChild(child:DisplayObject):DisplayObject |
| { |
| // not sure why this happens but it does if you just change |
| // the embeddedFont context |
| if (!child.parent) |
| return child; |
| |
| if (child.parent == this) |
| return super.removeChild(child); |
| |
| return child.parent.removeChild(child); |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function commitProperties():void |
| { |
| super.commitProperties(); |
| |
| updateStylesIfChanged(); |
| |
| var oldAnchorPosition:int = _selectionAnchorPosition; |
| var oldActivePosition:int = _selectionActivePosition; |
| |
| // EditingMode needs to be current before attempting to set a |
| // selection below. |
| if (enabledChanged || selectableChanged || editableChanged) |
| { |
| updateEditingMode(); |
| |
| enabledChanged = false; |
| editableChanged = false; |
| selectableChanged = false; |
| } |
| |
| // Only one of textChanged, textFlowChanged, and contentChanged |
| // will be true; the other two will be false because each setter |
| // guarantees this. |
| |
| if (textChanged) |
| { |
| // If the text has linebreaks (CR, LF, or CF+LF) |
| // create a multi-paragraph TextFlow from it |
| // and use the TextFlowTextLineFactory to render it. |
| // Otherwise the StringTextLineFactory will put |
| // all of the lines into a single paragraph |
| // and FTE performance will degrade on a large paragraph. |
| if (_text.indexOf("\n") != -1 || _text.indexOf("\r") != -1) |
| { |
| _textFlow = staticPlainTextImporter.importToFlow(_text); |
| _textContainerManager.setTextFlow(_textFlow); |
| } |
| else |
| { |
| _textContainerManager.setText(_text); |
| } |
| } |
| else if (textFlowChanged) |
| { |
| _textContainerManager.setTextFlow(_textFlow); |
| } |
| else if (contentChanged) |
| { |
| _textFlow = createTextFlowFromContent(_content); |
| _textContainerManager.setTextFlow(_textFlow); |
| |
| // Content converted to textFlow. |
| _content = null; |
| } |
| |
| if (textChanged || textFlowChanged || contentChanged) |
| { |
| lastGeneration = _textFlow ? _textFlow.generation : 0; |
| lastContentBoundsGeneration = 0; |
| |
| // Handle the case where the initial text, textFlow or content |
| // is displayed as a password. |
| if (displayAsPassword) |
| displayAsPasswordChanged = true; |
| |
| // New text so remove any leftover constraints. |
| // Used if an item renderer is being recycled. |
| widthConstraint = NaN; |
| heightConstraint = NaN; |
| |
| textChanged = false; |
| textFlowChanged = false; |
| contentChanged = false; |
| invalidateSize(); |
| invalidateDisplayList(); |
| } |
| |
| // If displayAsPassword changed, it only applies to the display, |
| // not the underlying text. |
| if (displayAsPasswordChanged) |
| { |
| // If there is any text, convert it to the passwordChar. |
| if (displayAsPassword) |
| { |
| // Make sure _text is set with the actual text before we |
| // change the displayed text. |
| _text = _textContainerManager.getText("\n"); |
| |
| // Paragraph terminators are lost during this substitution. |
| var textToDisplay:String = StringUtil.repeat( |
| passwordChar, _text.length); |
| |
| _textContainerManager.setText(textToDisplay); |
| } |
| else |
| { |
| // Text was displayed as password. Now display as plain text. |
| _textContainerManager.setText(_text); |
| } |
| |
| // When TLF text is set above, TLF's textFlow is recreated so reset |
| // our copy of the textFlow and the generation. |
| _textFlow = null; |
| lastGeneration = 0; |
| lastContentBoundsGeneration = 0; |
| |
| displayAsPasswordChanged = false; |
| } |
| |
| if (clipAndEnableScrollingChanged) |
| { |
| // The TLF code seems to check for !off. |
| _textContainerManager.horizontalScrollPolicy = "auto"; |
| _textContainerManager.verticalScrollPolicy = "auto"; |
| |
| clipAndEnableScrollingChanged = false; |
| } |
| |
| if (horizontalScrollPositionChanged) |
| { |
| var oldHorizontalScrollPosition:Number = |
| _textContainerManager.horizontalScrollPosition; |
| |
| _textContainerManager.horizontalScrollPosition = |
| _horizontalScrollPosition; |
| |
| dispatchPropertyChangeEvent("horizontalScrollPosition", |
| oldHorizontalScrollPosition, _horizontalScrollPosition); |
| |
| horizontalScrollPositionChanged = false; |
| } |
| |
| if (verticalScrollPositionChanged) |
| { |
| var oldVerticalScrollPosition:Number = |
| _textContainerManager.verticalScrollPosition; |
| |
| _textContainerManager.verticalScrollPosition = |
| _verticalScrollPosition; |
| |
| dispatchPropertyChangeEvent("verticalScrollPosition", |
| oldVerticalScrollPosition, _verticalScrollPosition); |
| |
| verticalScrollPositionChanged = false; |
| } |
| |
| // Updating the text programatically removes all child TextLine elements |
| // before rebuilding the TextFlow, effectively removing all visual elements |
| // from the display list. This causes any accessibilityImplementation that |
| // was assigned to the component to be removed. The following line restores |
| // the accessibilityImplementation if it no longer exists. |
| if (!accessibilityImplementation) |
| initializeAccessibility(); |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function canSkipMeasurement():Boolean |
| { |
| autoSize = false; |
| return super.canSkipMeasurement(); |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function measure():void |
| { |
| var bounds:Rectangle; |
| |
| // If the damage handler is called while measuring text, this means |
| // the text lines are damaged and the display needs to be updated. |
| // This flag tells the handler to invalidate just the display list. |
| inMeasureMethod = true; |
| |
| lastMeasuredWidth = measuredWidth; |
| lastMeasuredHeight = measuredHeight; |
| |
| super.measure(); |
| |
| // Styles can be changed in event handlers while in the middle |
| // of the component lifecycle. Make sure they are not stale when |
| // composing text. |
| updateStylesIfChanged(); |
| |
| // percentWidth and/or percentHeight will come back in as constraints |
| // on the remeasure if we're autoSizing. |
| |
| // TODO:(cframpto) implement blockProgression rl for autoSize |
| |
| if (isMeasureFixed()) |
| { |
| autoSize = false; |
| |
| if (typicalText) |
| { |
| if (typicalTextChanged) |
| { |
| // If the text has linebreaks (CR, LF, or CF+LF) |
| // create a multi-paragraph TextFlow from it |
| // and use the TextFlowTextLineFactory to render it. |
| // Otherwise the StringTextLineFactory will put |
| // all of the lines into a single paragraph |
| // and FTE performance will degrade on a large paragraph. |
| if (_typicalText.indexOf("\n") != -1 || _typicalText.indexOf("\r") != -1) |
| { |
| _typicalTextFlow = staticPlainTextImporter.importToFlow(_typicalText); |
| // this helped get the factory to generate the same bounds as the |
| // composer |
| _typicalTextFlow.hostFormat = _textContainerManager.hostFormat; |
| } |
| else |
| _typicalTextFlow = null; |
| typicalTextChanged = false; |
| } |
| // if multiline... |
| if (_typicalTextFlow) |
| { |
| // create the factory if needed |
| if (!staticTextFlowFactory) |
| { |
| staticTextFlowFactory = new TextFlowTextLineFactory(); |
| // set bounds to natural bounds |
| staticTextFlowFactory.compositionBounds = unbounded; |
| } |
| if (_typicalTextFlow.flowComposer) |
| { |
| _typicalTextFlow.flowComposer.swfContext = |
| ISWFContext(embeddedFontContext); |
| } |
| staticTextFlowFactory.swfContext = ISWFContext(embeddedFontContext); |
| // create the textlines |
| staticTextFlowFactory.createTextLines(tossTextLine, _typicalTextFlow); |
| // get the bounds |
| bounds = staticTextFlowFactory.getContentBounds(); |
| } |
| else // single line |
| { |
| // create the factory if needed |
| if (!staticStringFactory) |
| { |
| staticStringFactory = new StringTextLineFactory(); |
| // set bounds to natural bounds |
| staticStringFactory.compositionBounds = unbounded; |
| } |
| // create the textlines |
| staticStringFactory.text = _typicalText; |
| staticStringFactory.textFlowFormat = _textContainerManager.hostFormat; |
| staticStringFactory.swfContext = ISWFContext(embeddedFontContext); |
| staticStringFactory.createTextLines(tossTextLine); |
| // get the bounds |
| bounds = staticStringFactory.getContentBounds(); |
| } |
| |
| measuredWidth = Math.ceil(bounds.width); |
| measuredHeight = Math.ceil(bounds.height); |
| } |
| else |
| { |
| // Go large. For performance reasons, want to avoid a scrollRect |
| // whenever possible in drawBackgroundAndSetScrollRect(). This is |
| // particularly true for 1 line TextInput components. |
| measuredWidth = !isNaN(explicitWidth) ? explicitWidth : |
| Math.ceil(calculateWidthInChars()); |
| measuredHeight = !isNaN(explicitHeight) ? explicitHeight : |
| Math.ceil(calculateHeightInLines()); |
| } |
| } |
| else |
| { |
| var composeWidth:Number; |
| var composeHeight:Number; |
| |
| // If we're here, then at one or both of the width and height can |
| // grow to fit the text. It is important to figure out whether |
| // or not autoSize should be allowed to continue. If in |
| // updateDisplayList(), autoSize is true, then the |
| // compositionHeight is NaN to allow the text to grow. |
| autoSize = true; |
| |
| if (!isNaN(widthConstraint) || !isNaN(explicitWidth) || |
| !isNaN(widthInChars)) |
| { |
| // width specified but no height |
| // if no text, start at one line high and grow |
| |
| if (!isNaN(widthConstraint)) |
| composeWidth = widthConstraint; |
| else if (!isNaN(explicitWidth)) |
| composeWidth = explicitWidth; |
| else |
| composeWidth = Math.ceil(calculateWidthInChars()); |
| |
| // The composeWidth may be adjusted for minWidth/maxWidth |
| // except if we're using the explicitWidth. |
| bounds = measureTextSize(composeWidth); |
| |
| // The measured width shouldn’t be pinned to the composeWidth if |
| // the composeWidth is set by %, otherwise the measuredWidth |
| // can keep stretching |
| if (!isNaN(explicitWidth) || !isNaN(widthInChars)) |
| measuredWidth = textContainerManager.compositionWidth; |
| else |
| measuredWidth = Math.ceil(bounds.width); |
| measuredHeight = Math.ceil(bounds.bottom); |
| } |
| else if (!isNaN(heightConstraint) || !isNaN(explicitHeight) || |
| !isNaN(_heightInLines)) |
| { |
| // if no text, 1 char wide with specified height and grow |
| |
| if (!isNaN(heightConstraint)) |
| composeHeight = heightConstraint; |
| else if (!isNaN(explicitHeight)) |
| composeHeight = explicitHeight; |
| else |
| composeHeight = calculateHeightInLines(); |
| |
| // The composeWidth may be adjusted for minWidth/maxWidth. |
| bounds = measureTextSize(NaN, composeHeight); |
| |
| measuredWidth = Math.ceil(bounds.right); |
| measuredHeight = composeHeight; |
| |
| // Have we already hit the limit with the existing text? If we |
| // are beyond the composeHeight we can assume we've maxed out on |
| // the compose width as well (or the composeHeight isn't |
| // large enough for even one line of text). |
| if (bounds.bottom > composeHeight) |
| autoSize = false; |
| } |
| else |
| { |
| // The composeWidth may be adjusted for minWidth/maxWidth. |
| bounds = measureTextSize(NaN); |
| |
| measuredWidth = Math.ceil(bounds.right); |
| measuredHeight = Math.ceil(bounds.bottom); |
| } |
| |
| // Clamp the height, except if we're using the explicitHeight. |
| if (isNaN(explicitHeight)) |
| { |
| if (!isNaN(explicitMinHeight) && measuredHeight < explicitMinHeight) |
| measuredHeight = explicitMinHeight; |
| |
| // Reached max height so can't grow anymore. |
| if (!isNaN(explicitMaxHeight) && measuredHeight > explicitMaxHeight) |
| { |
| measuredHeight = explicitMaxHeight; |
| autoSize = false; |
| } |
| } |
| |
| // Make sure we weren't previously scrolled. |
| if (autoSize && getStyle("lineBreak") == "toFit") |
| { |
| _textContainerManager.horizontalScrollPosition = 0; |
| _textContainerManager.verticalScrollPosition = 0; |
| } |
| |
| // If we remeasured, we composed and cleared the display. We need to update the |
| // display if the size didn't change, since validateSize will not do it for us. |
| // This code path can be used by itemRenderer's since setLayoutBounds(), which is |
| // where the constraints are set, is not always called. |
| if (remeasuringText && |
| lastMeasuredWidth == measuredWidth && lastMeasuredHeight == measuredHeight) |
| { |
| _textContainerManager.updateContainer(); |
| } |
| } |
| |
| remeasuringText = false; |
| inMeasureMethod = false; |
| |
| //trace("measure", measuredWidth, measuredHeight, "autoSize", autoSize); |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function updateDisplayList(unscaledWidth:Number, |
| unscaledHeight:Number):void |
| { |
| inUpdateDLMethod = true; |
| |
| //trace("updateDisplayList", unscaledWidth, unscaledHeight, "autoSize", autoSize); |
| |
| // Styles can be changed in event handlers while in the middle |
| // of the component lifecycle. Make sure they are not stale when |
| // composing text. |
| updateStylesIfChanged(); |
| |
| // Check if the auto-size text is constrained in some way and needs |
| // to be remeasured. If one of the dimension changes, the text may |
| // compose differently and have a different size which the layout |
| // manager needs to know. |
| // Don't exit early if we have changed size. We may have to run |
| // drawBackgroundAndSetScrollRect |
| if (autoSize && |
| lastUnscaledHeight == unscaledHeight && lastUnscaledWidth == unscaledWidth && |
| remeasureText(unscaledWidth, unscaledHeight)) |
| { |
| inUpdateDLMethod = false; |
| return; |
| } |
| |
| super.updateDisplayList(unscaledWidth, unscaledHeight); |
| |
| // If we're autoSizing we're telling the layout manager one set of |
| // values and TLF another set of values so there is room for the text |
| // to grow. |
| |
| // TODO:(cframpto) compositionWidth can be NaN when |
| // autoSize for blockProgression=="rl" is implemented |
| if (!autoSize) |
| { |
| _textContainerManager.compositionWidth = unscaledWidth; |
| _textContainerManager.compositionHeight = unscaledHeight; |
| } |
| |
| // If scrolling, always compose with the composer so we get consistent |
| // measurements. The factory and the composer produce slightly |
| // different results which can confuse the scroller. If there isn't a |
| // composer, this calls updateContainer so do it here now that the |
| // composition sizes are set so the results can be used. |
| if (clipAndEnableScrolling && |
| _textContainerManager.composeState != |
| TextContainerManager.COMPOSE_COMPOSER) |
| { |
| _textContainerManager.convertToTextFlowWithComposer(); |
| } |
| |
| // The EditManager calls updateAllControllers() directly when there |
| // is interactive input such as typing or cut/paste. This bypasses |
| // our update cycle which matters if we are auto-sizing. |
| // compositionWidth/Height are NaN and the background is drawn with |
| // the old width/height because measureTextSize hasn't had a |
| // chance to update the layout manager yet. Once the layoutManager |
| // has been updated, the compositionWidth/Height are still NaN |
| // so TLF doesn't think there is anything to compose. The text |
| // hasn't changed shape, but the background has. |
| if (autoSize && !isNaN(lastUnscaledWidth) && |
| (lastUnscaledWidth != unscaledWidth || |
| lastUnscaledHeight != unscaledHeight)) |
| { |
| if (_textContainerManager.composeState == TextContainerManager.COMPOSE_COMPOSER) |
| _textContainerManager.getTextFlow().flowComposer.getControllerAt(0).shapesInvalid = true; |
| else if (!_textContainerManager.isDamaged()) |
| _textContainerManager.drawBackgroundAndSetScrollRect(0,0); |
| } |
| |
| _textContainerManager.updateContainer(); |
| |
| lastUnscaledWidth = unscaledWidth; |
| lastUnscaledHeight = unscaledHeight; |
| |
| inUpdateDLMethod = false; |
| } |
| |
| /** |
| * @private |
| * This is called by the layout manager the first time this |
| * component is measured, or later if its size changes. This |
| * is not always called before updateDisplayList(). For example, |
| * for recycled item renderers this is not called if the measured |
| * size doesn't change. |
| * |
| * width and height are NaN unless there are constraints on them. |
| */ |
| override public function setLayoutBoundsSize( |
| width:Number, height:Number, |
| postLayoutTransform:Boolean = true):void |
| { |
| //trace("setLayoutBoundsSize", width, height); |
| |
| // Save these so when we are auto-sizing we know which dimensions |
| // are constrained. Without this it is not possible to differentiate |
| // between a measured width/height that is the same as the |
| // constrained width/height to know whether that dimension can |
| // be sized or must be fixed at the constrained value. |
| heightConstraint = height; |
| |
| super.setLayoutBoundsSize(width, height, postLayoutTransform); |
| |
| // Did we already constrain the width? |
| if (widthConstraint == width) |
| return; |
| |
| // No reflow for explicit lineBreak |
| if (getStyle("lineBreak") == "explicit") |
| return; |
| |
| // If we don't measure. |
| // Call super so we don't call the override |
| // and set autoSize to false; |
| if (super.canSkipMeasurement()) |
| return; |
| |
| if (!isNaN(explicitHeight)) |
| return; |
| |
| // We support reflow only in the case of constrained width and |
| // unconstrained height. Note that we compare with measuredWidth, |
| // as for example the RichEditableText can be |
| // constrained by the layout with "left" and "right", but the |
| // container width itself may not be constrained and it would depend |
| // on the element's measuredWidth. |
| var constrainedWidth:Boolean = !isNaN(width) && (width != measuredWidth) && (width != 0); |
| if (!constrainedWidth) |
| return; |
| |
| // We support reflow only when we don't have a transform. |
| // We could add support for scale, but not skew or rotation. |
| if (postLayoutTransform && hasComplexLayoutMatrix) |
| return; |
| |
| widthConstraint = width; |
| |
| invalidateSize(); |
| |
| } |
| |
| /** |
| * @inheritDoc |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| override public function stylesInitialized():void |
| { |
| super.stylesInitialized(); |
| |
| ascent = NaN; |
| descent = NaN; |
| |
| hostFormatChanged = true; |
| } |
| |
| /** |
| * @inheritDoc |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| override public function styleChanged(styleProp:String):void |
| { |
| super.styleChanged(styleProp); |
| |
| // If null or "styleName" is passed it indicates that |
| // multiple styles may have changed. Otherwise it is a single style |
| // so mark whether it is the selectionFormat that changed or the |
| // hostFormat that changed. |
| if (styleProp == null || styleProp == "styleName") |
| { |
| hostFormatChanged = true; |
| selectionFormatsChanged = true; |
| ascent = NaN; |
| descent = NaN; |
| } |
| else if (styleProp == "focusedTextSelectionColor" || |
| styleProp == "unfocusedTextSelectionColor" || |
| styleProp == "inactiveTextSelectionColor") |
| { |
| selectionFormatsChanged = true; |
| } |
| else |
| { |
| hostFormatChanged = true; |
| |
| if (styleProp.indexOf("font") == 0 || styleProp == "cffHinting") |
| { |
| // Regenerate font swfContext and metrics as well. |
| ascent = NaN; |
| descent = NaN; |
| } |
| } |
| |
| // Need to create new format(s). |
| invalidateProperties(); |
| } |
| |
| /** |
| * @private |
| */ |
| override public function setFocus():void |
| { |
| // We are about to set focus on this component. If it is due to |
| // a programmatic focus change we have to programatically do what the |
| // mouseOverHandler and the mouseDownHandler do so that the user can |
| // type in this component without using the mouse first. We need to |
| // put a textFlow with a composer in place. |
| if (editingMode != EditingMode.READ_ONLY && |
| _textContainerManager.composeState != |
| TextContainerManager.COMPOSE_COMPOSER) |
| { |
| _textContainerManager.beginInteraction(); |
| _textContainerManager.endInteraction(); |
| } |
| |
| super.setFocus(); |
| } |
| |
| /** |
| * @private |
| */ |
| override public function drawFocus(isFocused:Boolean):void |
| { |
| if (isFocused) |
| { |
| // For some composite components, the focused object may not |
| // be "this". If so, we don't want to draw the focus. This |
| // replaces the parentDrawsFocus variable used in halo. |
| var fm:IFocusManager = focusManager; |
| if (fm && fm.getFocus() != this) |
| return; |
| } |
| |
| super.drawFocus(isFocused); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Methods: IViewport |
| // |
| //-------------------------------------------------------------------------- |
| |
| //---------------------------------- |
| // horizontalScrollPositionDelta |
| //---------------------------------- |
| |
| /** |
| * @inheritDoc |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function getHorizontalScrollPositionDelta(navigationUnit:uint):Number |
| { |
| var scrollR:Rectangle = scrollRect; |
| if (!scrollR) |
| return 0; |
| |
| // maxDelta is the horizontalScrollPosition delta required |
| // to scroll to the RIGHT and minDelta scrolls to LEFT. |
| var maxDelta:Number = contentWidth - scrollR.right; |
| var minDelta:Number = -scrollR.left; |
| |
| // Scroll by a "character" which is 1 em (matches widthInChars()). |
| var em:Number = getStyle("fontSize"); |
| |
| switch (navigationUnit) |
| { |
| case NavigationUnit.LEFT: |
| return (scrollR.left <= 0) ? 0 : Math.max(minDelta, -em); |
| |
| case NavigationUnit.RIGHT: |
| return (scrollR.right >= contentWidth) ? 0 : Math.min(maxDelta, em); |
| |
| case NavigationUnit.PAGE_LEFT: |
| return Math.max(minDelta, -scrollR.width); |
| |
| case NavigationUnit.PAGE_RIGHT: |
| return Math.min(maxDelta, scrollR.width); |
| |
| case NavigationUnit.HOME: |
| return minDelta; |
| |
| case NavigationUnit.END: |
| return maxDelta; |
| |
| default: |
| return 0; |
| } |
| } |
| |
| //---------------------------------- |
| // verticalScrollPositionDelta |
| //---------------------------------- |
| |
| /** |
| * @inheritDoc |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function getVerticalScrollPositionDelta(navigationUnit:uint):Number |
| { |
| var scrollR:Rectangle = scrollRect; |
| if (!scrollR) |
| return 0; |
| |
| // maxDelta is the horizontalScrollPosition delta required |
| // to scroll to the END and minDelta scrolls to HOME. |
| var maxDelta:Number = contentHeight - scrollR.bottom; |
| var minDelta:Number = -scrollR.top; |
| |
| switch (navigationUnit) |
| { |
| case NavigationUnit.UP: |
| return _textContainerManager.getScrollDelta(-1); |
| |
| case NavigationUnit.DOWN: |
| return _textContainerManager.getScrollDelta(1); |
| |
| case NavigationUnit.PAGE_UP: |
| return Math.max(minDelta, -scrollR.height); |
| |
| case NavigationUnit.PAGE_DOWN: |
| return Math.min(maxDelta, scrollR.height); |
| |
| case NavigationUnit.HOME: |
| return minDelta; |
| |
| case NavigationUnit.END: |
| return maxDelta; |
| |
| default: |
| return 0; |
| } |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Inserts the specified text into the RichEditableText |
| * as if you had typed it. |
| * |
| * <p>If a range was selected, the new text replaces the selected text. |
| * If there was an insertion point, the new text is inserted there.</p> |
| * |
| * <p>An insertion point is then set after the new text. |
| * If necessary, the text will scroll to ensure |
| * that the insertion point is visible.</p> |
| * |
| * @param text The text to be inserted. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function insertText(text:String):void |
| { |
| handleInsertText(text); |
| } |
| |
| /** |
| * Appends the specified text to the end of the RichEditableText, |
| * as if you had clicked at the end and typed. |
| * |
| * <p>An insertion point is then set after the new text. |
| * If necessary, the text will scroll to ensure |
| * that the insertion point is visible.</p> |
| * |
| * @param text The text to be appended. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function appendText(text:String):void |
| { |
| handleInsertText(text, true); |
| } |
| |
| /** |
| * @copy flashx.textLayout.container.ContainerController#scrollToRange() |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function scrollToRange(anchorPosition:int, activePosition:int):void |
| { |
| // Make sure the properties are commited since the text could change. |
| validateProperties(); |
| |
| // Scrolls so that the text position is visible in the container. |
| textContainerManager.scrollToRange(anchorPosition, activePosition); |
| } |
| |
| /** |
| * Selects a specified range of characters. |
| * |
| * <p>If either position is negative, it will deselect the text range.</p> |
| * |
| * @param anchorPosition The character position specifying the end |
| * of the selection that stays fixed when the selection is extended. |
| * |
| * @param activePosition The character position specifying the end |
| * of the selection that moves when the selection is extended. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function selectRange(anchorPosition:int, |
| activePosition:int):void |
| { |
| // Make sure the properties are commited since the text could change. |
| validateProperties(); |
| |
| if (editingMode == EditingMode.READ_ONLY) |
| { |
| var selectionState:SelectionState = |
| new SelectionState(textFlow, anchorPosition, activePosition); |
| |
| var selectionEvent:SelectionEvent = |
| new SelectionEvent(SelectionEvent.SELECTION_CHANGE, |
| false, false, selectionState); |
| |
| textContainerManager_selectionChangeHandler(selectionEvent); |
| } |
| else |
| { |
| var im:ISelectionManager = _textContainerManager.beginInteraction(); |
| |
| im.selectRange(anchorPosition, activePosition); |
| |
| // Refresh the selection. This does not cause a damage event. |
| im.refreshSelection(); |
| |
| _textContainerManager.endInteraction(); |
| } |
| |
| // Remember if the current selection is a range which was set |
| // programatically. |
| hasProgrammaticSelectionRange = (anchorPosition != activePosition); |
| } |
| |
| /** |
| * Selects all of the text. This does not include the final paragraph |
| * terminator. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function selectAll():void |
| { |
| selectRange(0, int.MAX_VALUE); |
| } |
| |
| /** |
| * Returns a TextLayoutFormat object specifying the computed formats |
| * for the specified range of characters. |
| * |
| * <p>If a format is not consistently set across the entire range, |
| * its value will be <code>undefined</code>.</p> |
| * |
| * <p>You can specify a Vector of Strings containing the names of the |
| * formats that you care about; if you don't, all formats |
| * will be computed.</p> |
| * |
| * <p>If you don't specify a range, the selected range is used.</p> |
| * |
| * @param requestedFormats A Vector of Strings specifying the names |
| * of the requested formats, or <code>null</code> to request all formats. |
| * |
| * @param anchorPosition A character position specifying |
| * the fixed end of the selection. |
| * |
| * @param activePosition A character position specifying |
| * the movable end of the selection. |
| * |
| * @return A TextLayoutFormat object. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function getFormatOfRange(requestedFormats:Vector.<String> = null, |
| anchorPosition:int = -1, |
| activePosition:int = -1):TextLayoutFormat |
| { |
| var format:TextLayoutFormat = new TextLayoutFormat(); |
| |
| // Make sure all properties are committed. |
| validateProperties(); |
| |
| // This internal TLF object maps the names of format properties |
| // to Property instances. |
| // Each Property instance has a category property which tells |
| // whether it is container-, paragraph-, or character-level. |
| var description:Object = TextLayoutFormat.description; |
| |
| var p:String; |
| var category:String; |
| |
| // Based on which formats have been requested, determine which |
| // of the getCommonXXXFormat() methods we need to call. |
| |
| var needContainerFormat:Boolean = false; |
| var needParagraphFormat:Boolean = false; |
| var needCharacterFormat:Boolean = false; |
| |
| if (!requestedFormats) |
| { |
| requestedFormats = new Vector.<String>; |
| for (p in description) |
| requestedFormats.push(p); |
| |
| needContainerFormat = true; |
| needParagraphFormat = true; |
| needCharacterFormat = true; |
| } |
| else |
| { |
| for each (p in requestedFormats) |
| { |
| if (!(p in description)) |
| continue; |
| |
| category = description[p].category; |
| |
| if (category == Category.CONTAINER) |
| needContainerFormat = true; |
| else if (category == Category.PARAGRAPH) |
| needParagraphFormat = true; |
| else if (category == Category.CHARACTER) |
| needCharacterFormat = true; |
| } |
| } |
| |
| // Get the common formats. |
| |
| var containerFormat:ITextLayoutFormat; |
| var paragraphFormat:ITextLayoutFormat; |
| var characterFormat:ITextLayoutFormat; |
| |
| if (anchorPosition == -1 && activePosition == -1) |
| { |
| anchorPosition = _selectionAnchorPosition; |
| activePosition = _selectionActivePosition; |
| } |
| |
| if (needContainerFormat) |
| { |
| containerFormat = |
| _textContainerManager.getCommonContainerFormat(); |
| } |
| |
| if (needParagraphFormat) |
| { |
| paragraphFormat = |
| _textContainerManager.getCommonParagraphFormat( |
| anchorPosition, activePosition); |
| } |
| |
| if (needCharacterFormat) |
| { |
| characterFormat = |
| _textContainerManager.getCommonCharacterFormat( |
| anchorPosition, activePosition); |
| } |
| |
| // Extract the requested formats to return. |
| for each (p in requestedFormats) |
| { |
| if (!(p in description)) |
| continue; |
| |
| category = description[p].category; |
| |
| if (category == Category.CONTAINER && containerFormat) |
| format[p] = containerFormat[p]; |
| else if (category == Category.PARAGRAPH && paragraphFormat) |
| format[p] = paragraphFormat[p]; |
| else if (category == Category.CHARACTER && characterFormat) |
| format[p] = characterFormat[p]; |
| } |
| |
| return format; |
| } |
| |
| /** |
| * Applies the specified formats to each element in the specified |
| * range that correspond to the given format. |
| * It applies the character formats to the text in the specified range |
| * (no change is made if the specified range is a single point). |
| * It applies the paragraph formats to any paragraphs at least |
| * partially within the range (or a single paragraph if the range is a |
| * single point). |
| * It applies the container formats to the container. |
| * |
| * <p>The supported formats are those in TextLayoutFormat. |
| * A value of <code>undefined</code> does not get applied. |
| * If you don't specify a range, the selected range is used.</p> |
| * |
| * <p>The following example sets the <code>fontSize</code> and <code>color</code> of the selection: |
| * <pre> |
| * var textLayoutFormat:TextLayoutFormat = new TextLayoutFormat(); |
| * textLayoutFormat.fontSize = 12; |
| * textLayoutFormat.color = 0xFF0000; |
| * myRET.setFormatOfRange(textLayoutFormat); |
| * </pre> |
| * </p> |
| * |
| * <p>If you use the results of <code>getFormatOfRange()</code> to |
| * specify the <code>format</code>, note that every format in the |
| * <code>TextLayoutFormat</code> has a |
| * computed value, which will be applied to each element that |
| * corresponds to the given format.</p> |
| * |
| * <p>If you would like to specify a format to be applied to all the text |
| * it would be better to use <code>setStyle(format, value)</code> |
| * on the component itself.</p> |
| * |
| * <p>The following example sets the <code>fontSize</code> and <code>color</code> of all the text: |
| * <pre> |
| * myRET.setStyle("fontSize", 12); |
| * myRET.setStyle("color", 0xFF0000); |
| * </pre> |
| * </p> |
| * |
| * @param format The TextLayoutFormat to apply to the selection. |
| * |
| * @param anchorPosition A character position, relative to the beginning of the |
| * text String, specifying the end of the selection that stays fixed when the |
| * selection is extended with the arrow keys. |
| * |
| * @param activePosition A character position, relative to the beginning of the |
| * text String, specifying the end of the selection that moves when the |
| * selection is extended with the arrow keys. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @productversion Flex 4 |
| */ |
| public function setFormatOfRange(format:TextLayoutFormat, |
| anchorPosition:int=-1, |
| activePosition:int=-1):void |
| { |
| // Make sure all properties are committed. The damage handler for the |
| // applyTextFormat op will cause the remeasure and display update. |
| validateProperties(); |
| |
| // Assign each specified attribute to one of three format objects, |
| // depending on whether it is container-, paragraph-, |
| // or character-level. Note that these can remain null. |
| var containerFormat:TextLayoutFormat; |
| var paragraphFormat:TextLayoutFormat; |
| var characterFormat:TextLayoutFormat; |
| |
| // This internal TLF object maps the names of format properties |
| // to Property instances. |
| // Each Property instance has a category property which tells |
| // whether it is container-, paragraph-, or character-level. |
| var description:Object = TextLayoutFormat.description; |
| |
| for (var p:String in description) |
| { |
| if (format[p] === undefined) |
| continue; |
| |
| var category:String = description[p].category; |
| |
| if (category == Category.CONTAINER) |
| { |
| if (!containerFormat) |
| containerFormat = new TextLayoutFormat(); |
| containerFormat[p] = format[p]; |
| } |
| else if (category == Category.PARAGRAPH) |
| { |
| if (!paragraphFormat) |
| paragraphFormat = new TextLayoutFormat(); |
| paragraphFormat[p] = format[p]; |
| } |
| else if (category == Category.CHARACTER) |
| { |
| if (!characterFormat) |
| characterFormat = new TextLayoutFormat(); |
| characterFormat[p] = format[p]; |
| } |
| } |
| |
| // If the selection isn't specified, use the current one. |
| if (anchorPosition == -1 && activePosition == -1) |
| { |
| anchorPosition = _selectionAnchorPosition; |
| activePosition = _selectionActivePosition; |
| } |
| |
| // Apply the three format objects to the current selection if |
| // selectionState is null, else the specified selection. |
| _textContainerManager.applyFormatOperation( |
| characterFormat, paragraphFormat, containerFormat, |
| anchorPosition, activePosition); |
| } |
| |
| /** |
| * @private |
| */ |
| mx_internal function createTextContainerManager():RichEditableTextContainerManager |
| { |
| return new RichEditableTextContainerManager(this); |
| } |
| |
| /** |
| * @private |
| */ |
| private function updateStylesIfChanged():void |
| { |
| |
| if (hostFormatChanged) |
| { |
| // Side-effect is it marks the text as damaged. |
| _textContainerManager.hostFormat = new CSSTextLayoutFormat(this); |
| |
| hostFormatChanged = false; |
| } |
| |
| if (isNaN(ascent) || isNaN(descent)) |
| { |
| // If the CSS styles for this component specify an embedded font, |
| // embeddedFontContext will be set to the module factory that |
| // should create TextLines (since they must be created in the |
| // SWF where the embedded font is.) |
| // Otherwise, this will be null. |
| embeddedFontContext = getEmbeddedFontContext(); |
| |
| _textContainerManager.swfContext = |
| ISWFContext(embeddedFontContext); |
| |
| // Note: CSSTextLayoutFormat has special processing |
| // for the fontLookup style. If it is "auto", |
| // the fontLookup format is set to either |
| // "device" or "embedded" depending on whether |
| // embeddedFontContext is null or non-null. |
| |
| // Recalcuate the ascent and descent. |
| calculateFontMetrics(); |
| } |
| |
| if (selectionFormatsChanged) |
| { |
| _textContainerManager.invalidateSelectionFormats(); |
| |
| selectionFormatsChanged = false; |
| } |
| } |
| |
| /** |
| * @private |
| * Uses the component's CSS styles to determine the module factory |
| * that should creates its TextLines. |
| */ |
| private function getEmbeddedFontContext():IFlexModuleFactory |
| { |
| var fontContext:IFlexModuleFactory; |
| |
| var fontLookup:String = getStyle("fontLookup"); |
| if (fontLookup != FontLookup.DEVICE) |
| { |
| var font:String = getStyle("fontFamily"); |
| var bold:Boolean = getStyle("fontWeight") == "bold"; |
| var italic:Boolean = getStyle("fontStyle") == "italic"; |
| |
| fontContext = getFontContext(font, bold, italic, true); |
| } |
| |
| return fontContext; |
| } |
| |
| /** |
| * @private |
| * Return true if there is a width and height to use for the measure. |
| */ |
| mx_internal function isMeasureFixed():Boolean |
| { |
| // This can be called from RET EditManager when hostFormat is null |
| // because a style changed and commitProperties hasn't run yet. |
| if (!_textContainerManager.hostFormat) |
| updateStylesIfChanged(); |
| |
| if (_textContainerManager.hostFormat.blockProgression != |
| BlockProgression.TB) |
| { |
| return true; |
| } |
| |
| if (typicalText != null) |
| return true; |
| |
| // Is there some sort of width and some sort of height? |
| return (!isNaN(explicitWidth) || !isNaN(_widthInChars) || |
| !isNaN(widthConstraint)) && |
| (!isNaN(explicitHeight) || !isNaN(_heightInLines) || |
| !isNaN(heightConstraint)); |
| } |
| |
| /** |
| * @private |
| * Returns the bounds of the measured text. The initial composeWidth may |
| * be adjusted for minWidth or maxWidth. The value used for the compose |
| * is in _textContainerManager.compositionWidth. |
| */ |
| private function measureTextSize(composeWidth:Number, |
| composeHeight:Number=NaN):Rectangle |
| { |
| // Adjust for explicit min/maxWidth so the measurement is accurate. |
| // If no explicitWidth or widthInChars |
| if (isNaN(explicitWidth) && isNaN(widthInChars)) |
| { |
| // then if there is an explicit minWidth and |
| // a specified composeWidth and the composeWidth |
| // is less than the minWidth, use the minWidth |
| if (!isNaN(explicitMinWidth) && |
| !isNaN(composeWidth) && composeWidth < minWidth) |
| { |
| composeWidth = minWidth; |
| } |
| // if composeWidth is NaN, just compose and see what happens |
| // and fix up the measurements afterwards. See final check |
| // at the end of the method |
| |
| // On the other hand, if there is an explicit maxWidth and |
| // no specified composeWidth then use maxWidth, |
| // or if there is a a specified composeWidth |
| // and the composeWidth is greater than maxWidth, use the maxWidth. |
| if (!isNaN(explicitMaxWidth) && |
| isNaN(composeWidth) || |
| composeWidth > maxWidth) |
| { |
| composeWidth = maxWidth; |
| } |
| } |
| |
| // If the width is NaN it can grow up to TextLine.MAX_LINE_WIDTH wide. |
| // If the height is NaN it can grow to allow all the text to fit. |
| _textContainerManager.compositionWidth = composeWidth; |
| _textContainerManager.compositionHeight = composeHeight; |
| |
| // If scrolling, always compose with the composer so we get consistent |
| // measurements. The factory and the composer produce slightly |
| // different results which can confuse the scroller. If there isn't a |
| // composer, this calls updateContainer so do it here now that the |
| // composition sizes are set so the results can be used. |
| if (clipAndEnableScrolling && |
| _textContainerManager.composeState != |
| TextContainerManager.COMPOSE_COMPOSER) |
| { |
| _textContainerManager.convertToTextFlowWithComposer(); |
| } |
| |
| // Compose only. The display should not be updated. |
| _textContainerManager.compose(); |
| |
| // Adjust width and height for text alignment. |
| var bounds:Rectangle = _textContainerManager.getContentBounds(); |
| |
| // If it's an empty text flow, there is one line with one |
| // character so the height is good for the line but we |
| // need to give it some width other than optional padding. |
| |
| if (_textContainerManager.getText("\n").length == 0) |
| { |
| // Empty text flow. One Em wide so there |
| // is a place to put the insertion cursor. |
| bounds.width = bounds.width + getStyle("fontSize"); |
| } |
| |
| //trace("measureTextSize", composeWidth, "->", bounds.width, composeHeight, "->", bounds.height); |
| |
| // one final check: If there is no explicitWidth... |
| if (isNaN(explicitWidth) && isNaN(widthInChars)) |
| { |
| // but there is a minWidth, and no specified composeWidth |
| // and we measure out to be less than the minWidth |
| // report the minWidth anyway. |
| if (!isNaN(explicitMinWidth) && |
| isNaN(composeWidth) && |
| bounds.width < minWidth) |
| { |
| bounds.width = minWidth; |
| } |
| } |
| return bounds; |
| } |
| |
| |
| /** |
| * @private |
| * If auto-sizing text, it may need to be remeasured if it is |
| * constrained in one dimension by the layout manager. If it is |
| * constrained in both dimensions there is no need to remeasure. |
| * Changing one dimension may change the size of the measured text |
| * and the layout manager needs to know this. |
| */ |
| private function remeasureText(width:Number, height:Number):Boolean |
| { |
| // Neither dimensions changed. If auto-sizing we're still auto-sizing. |
| if (width == measuredWidth && height == measuredHeight) |
| return false; |
| |
| // Either constraints are preventing auto-sizing or we need to |
| // remeasure which will reset autoSize. |
| autoSize = false; |
| |
| // If no width or height, there is nothing to remeasure since |
| // there is no room for text. |
| if (width == 0 || height == 0) |
| return false; |
| |
| // If we're using typical text, no need to remeasure. |
| if (typicalText != null) |
| return false; |
| |
| // No reflow for explicit lineBreak |
| if (_textContainerManager.hostFormat.lineBreak == "explicit") |
| return false; |
| |
| if (!isNaN(widthConstraint)) |
| { |
| // Do we have a constrained width and an explicit height? |
| // If so, the sizes are set so no need to remeasure now. |
| if (!isNaN(explicitHeight) || !isNaN(_heightInLines) || |
| !isNaN(heightConstraint)) |
| { |
| return false; |
| } |
| } |
| |
| if (!isNaN(heightConstraint)) |
| { |
| // Do we have a constrained height and an explicit width? |
| // If so, the sizes are set so no need to remeasure now. |
| if (!isNaN(explicitWidth) || !isNaN(_widthInChars)) |
| return false; |
| } |
| |
| // Width or height is different than what was measured. Since we're |
| // auto-sizing, need to remeasure, so the layout manager leaves the |
| // correct amount of space for the component. |
| invalidateSize(); |
| |
| // Need to make sure the container is updated after the text is re-composed |
| // to measure it. |
| remeasuringText = true; |
| |
| return true; |
| } |
| |
| /** |
| * @private |
| * This method is called when anything affecting the |
| * default font, size, weight, etc. changes. |
| * It calculates the 'ascent', 'descent', and |
| * instance variables, which are used in measure(). |
| */ |
| private function calculateFontMetrics():void |
| { |
| var fontDescription:FontDescription = new FontDescription(); |
| |
| var s:String; |
| |
| s = getStyle("cffHinting"); |
| if (s != null) |
| fontDescription.cffHinting = s; |
| |
| s = getStyle("fontFamily"); |
| if (s != null) |
| fontDescription.fontName = s; |
| |
| s = getStyle("fontLookup"); |
| if (s != null) |
| { |
| // FTE understands only "device" and "embeddedCFF" |
| // for fontLookup. But Flex allows this style to be |
| // set to "auto", in which case we automatically |
| // determine it based on whether the CSS styles |
| // specify an embedded font. |
| if (s != "device") |
| { |
| s = TextUtil.resolveFontLookup( |
| _textContainerManager.swfContext, |
| _textContainerManager.hostFormat); |
| } |
| fontDescription.fontLookup = s; |
| } |
| |
| s = getStyle("fontStyle"); |
| if (s != null) |
| fontDescription.fontPosture = s; |
| |
| s = getStyle("fontWeight"); |
| if (s != null) |
| fontDescription.fontWeight = s; |
| |
| var elementFormat:ElementFormat = new ElementFormat(); |
| elementFormat.fontDescription = fontDescription; |
| elementFormat.fontSize = getStyle("fontSize"); |
| |
| var textElement:TextElement = new TextElement(); |
| textElement.elementFormat = elementFormat; |
| textElement.text = "M"; |
| |
| var textBlock:TextBlock = new TextBlock(); |
| textBlock.content = textElement; |
| |
| var swfContext:ISWFContext = ISWFContext(embeddedFontContext); |
| |
| var textLine:TextLine; |
| if (swfContext) |
| { |
| textLine = swfContext.callInContext( |
| textBlock.createTextLine, textBlock, |
| [ null, 1000 ]); |
| } |
| else |
| textLine = textBlock.createTextLine(null, 1000); |
| |
| ascent = textLine.ascent; |
| descent = textLine.descent; |
| } |
| |
| /** |
| * @private |
| */ |
| private function calculateWidthInChars():Number |
| { |
| var em:Number = getStyle("fontSize"); |
| |
| var effectiveWidthInChars:int; |
| |
| // If both height and width are NaN use 10 chars. Otherwise if only |
| // width is NaN, use 1. |
| if (isNaN(_widthInChars)) |
| effectiveWidthInChars = isNaN(_heightInLines) ? 10 : 1; |
| else |
| effectiveWidthInChars = _widthInChars; |
| |
| // Without the explicit casts, if padding values are non-zero, the |
| // returned width is a very large number. |
| return getStyle("paddingLeft") + |
| effectiveWidthInChars * em + |
| getStyle("paddingRight"); |
| } |
| |
| /** |
| * @private |
| * Calculates the height needed for heightInLines lines using the default |
| * font. |
| */ |
| private function calculateHeightInLines():Number |
| { |
| var height:Number = getStyle("paddingTop") + getStyle("paddingBottom"); |
| |
| if (_heightInLines == 0) |
| return height; |
| |
| var effectiveHeightInLines:int; |
| |
| // If both height and width are NaN use 10 lines. Otherwise if |
| // only height is NaN, use 1. |
| if (isNaN(_heightInLines)) |
| effectiveHeightInLines = isNaN(_widthInChars) ? 10 : 1; |
| else |
| effectiveHeightInLines = _heightInLines; |
| |
| // Position of the baseline of first line in the container. |
| value = getStyle("firstBaselineOffset"); |
| if (value == lineHeight) |
| height += lineHeight; |
| else if (value is Number) |
| height += Number(value); |
| else |
| height += ascent; |
| |
| // Distance from baseline to baseline. Can be +/- number or |
| // or +/- percent (in form "120%") or "undefined". |
| if (effectiveHeightInLines > 1) |
| { |
| var value:Object = getStyle("lineHeight"); |
| var lineHeight:Number = |
| RichEditableText.getNumberOrPercentOf(value, getStyle("fontSize")); |
| |
| // Default is 120% |
| if (isNaN(lineHeight)) |
| lineHeight = getStyle("fontSize") * 1.2; |
| |
| height += (effectiveHeightInLines - 1) * lineHeight; |
| } |
| |
| // Add in descent of last line. |
| height += descent; |
| |
| return height; |
| } |
| |
| /** |
| * @private |
| */ |
| private function createTextFlowFromContent(content:Object):TextFlow |
| { |
| var textFlow:TextFlow ; |
| |
| if (content is TextFlow) |
| { |
| textFlow = content as TextFlow; |
| } |
| else if (content is Array) |
| { |
| textFlow = new TextFlow(); |
| textFlow.whiteSpaceCollapse = getStyle("whiteSpaceCollapse"); |
| textFlow.mxmlChildren = content as Array; |
| textFlow.whiteSpaceCollapse = undefined; |
| } |
| else |
| { |
| textFlow = new TextFlow(); |
| textFlow.whiteSpaceCollapse = getStyle("whiteSpaceCollapse"); |
| textFlow.mxmlChildren = [ content ]; |
| textFlow.whiteSpaceCollapse = undefined; |
| } |
| |
| return textFlow; |
| } |
| |
| /** |
| * @private |
| */ |
| private function updateEditingMode():void |
| { |
| var newEditingMode:String = EditingMode.READ_ONLY; |
| |
| if (enabled) |
| { |
| if (_editable) |
| newEditingMode = EditingMode.READ_WRITE; |
| else if (_selectable) |
| newEditingMode = EditingMode.READ_SELECT; |
| } |
| |
| editingMode = newEditingMode; |
| } |
| |
| /** |
| * @private |
| * |
| * This is used when text is either inserted or appended via the API. |
| */ |
| private function handleInsertText(newText:String, isAppend:Boolean=false):void |
| { |
| // Make sure all properties are committed. The damage handler for the |
| // insert will cause the remeasure and display update. |
| validateProperties(); |
| |
| if (isAppend) |
| { |
| // Set insertion pt to the end of the current text. |
| _selectionAnchorPosition = text.length; |
| _selectionActivePosition = _selectionAnchorPosition; |
| } |
| else |
| { |
| // Insert requires a selection, or it is a noop. |
| if (_selectionAnchorPosition == -1 || _selectionActivePosition == -1) |
| return; |
| } |
| |
| // This will update the selection after the operation is done. |
| var success:Boolean = |
| _textContainerManager.insertTextOperation( |
| newText, _selectionAnchorPosition, _selectionActivePosition); |
| |
| if (success) |
| dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT)); |
| } |
| |
| /** |
| * @private |
| */ |
| private function handlePasteOperation(op:PasteOperation):void |
| { |
| var hasConstraints:Boolean = |
| restrict || maxChars || displayAsPassword; |
| |
| // If there are no constraints and multiline text is allowed |
| // there is nothing that needs to be done. |
| if (!hasConstraints && multiline) |
| return; |
| |
| // Make sure the clipboard has something to paste. |
| if (op.textScrap == null || op.textScrap.textFlow == null) |
| return; |
| |
| // If copied/cut from displayAsPassword field the pastedText |
| // is '*' characters but this is correct. |
| var pastedText:String = staticPlainTextExporter.export( |
| op.textScrap.textFlow, ConversionType.STRING_TYPE) as String; |
| |
| // If there are no constraints and no newlines there is nothing |
| // more to do. |
| if (!hasConstraints && pastedText.search(ALL_NEWLINES_REGEXP) == -1) |
| return; |
| |
| // Save this in case we modify the pasted text. We need to know |
| // how much text to delete. |
| var textLength:int = pastedText.length; |
| |
| // Keep _text in sync with the text flow. If there was a selection |
| // for the paste, delete that text, then insert the pasted |
| // text. |
| if (_displayAsPassword) |
| { |
| _text = splice(_text, op.absoluteStart, op.absoluteEnd, ""); |
| _text = splice(_text, op.absoluteStart, op.absoluteStart, pastedText); |
| } |
| |
| // If multiline is false, strip newlines out of pasted text |
| // This will not strip newlines out of displayAsPassword fields |
| // since the text is the passwordChar and newline won't be found. |
| if (!multiline) |
| pastedText = pastedText.replace(ALL_NEWLINES_REGEXP, ""); |
| |
| // We know it's an EditManager or we wouldn't have gotten here. |
| var editManager:IEditManager = |
| EditManager(_textContainerManager.beginInteraction()); |
| |
| // Generate a CHANGING event for the PasteOperation but not for the |
| // DeleteTextOperation or the InsertTextOperation which are also part |
| // of the paste. |
| dispatchChangeAndChangingEvents = false; |
| |
| // Replace the same text, the same place where the paste was done. |
| // This will go thru the InsertPasteOperation and do the right |
| // things with restrict, maxChars, displayAsPassword and _text. |
| var selectionState:SelectionState = new SelectionState( |
| op.textFlow, op.absoluteStart, op.absoluteStart + textLength); |
| editManager.insertText(pastedText, selectionState); |
| |
| // All done with the edit manager. |
| _textContainerManager.endInteraction(); |
| |
| dispatchChangeAndChangingEvents = true; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Event handlers |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * find the right time to listen to the focusmanager |
| */ |
| private function addedToStageHandler(event:Event):void |
| { |
| if (event.target == this) |
| { |
| removeEventListener(Event.ADDED_TO_STAGE, addedToStageHandler); |
| callLater(addActivateHandlers); |
| } |
| } |
| |
| /** |
| * @private |
| * add listeners to focusManager |
| */ |
| private function addActivateHandlers():void |
| { |
| if (!focusManager) |
| return; |
| |
| focusManager.addEventListener(FlexEvent.FLEX_WINDOW_ACTIVATE, |
| _textContainerManager.activateHandler, false, 0, true) |
| focusManager.addEventListener(FlexEvent.FLEX_WINDOW_DEACTIVATE, |
| _textContainerManager.deactivateHandler, false, 0, true) |
| } |
| |
| /** |
| * @private |
| * Called when a FocusManager is added to an IFocusManagerContainer. |
| * We need to check that it belongs |
| * to us before listening to it. |
| * Because we listen to sandboxroot, you cannot assume the type of |
| * the event. |
| */ |
| private function addFocusManagerHandler(event:Event):void |
| { |
| if (focusManager == event.target["focusManager"]) |
| { |
| systemManager.getSandboxRoot().removeEventListener(FlexEvent.ADD_FOCUS_MANAGER, |
| addFocusManagerHandler, true) |
| addActivateHandlers(); |
| } |
| } |
| |
| /** |
| * @private |
| * RichEditableTextContainerManager overrides focusInHandler and calls |
| * this before executing its own focusInHandler. |
| */ |
| mx_internal function focusInHandler(event:FocusEvent):void |
| { |
| // When TCM is simulating a focusIn event, event will be null. |
| // Ignore these and wait for the actual focus in event. |
| if (event == null) |
| return; |
| |
| //trace("focusIn handler"); |
| |
| var fm:IFocusManager = focusManager; |
| if (fm && editingMode == EditingMode.READ_WRITE) |
| fm.showFocusIndicator = true; |
| |
| // showFocusIndicator must be set before this is called. |
| super.focusInHandler(event); |
| |
| if (editingMode == EditingMode.READ_WRITE) |
| { |
| // If the focusIn was because of a mouseDown event, let TLF |
| // handle the selection. Otherwise it was because we tabbed in |
| // or we programatically set the focus. |
| if (!mouseDown) |
| { |
| var selectionManager:ISelectionManager = |
| _textContainerManager.beginInteraction(); |
| |
| if (multiline) |
| { |
| if (!selectionManager.hasSelection()) |
| selectionManager.selectRange(0, 0); |
| } |
| else if (!hasProgrammaticSelectionRange) |
| { |
| selectionManager.selectAll(); |
| } |
| |
| selectionManager.refreshSelection(); |
| |
| _textContainerManager.endInteraction(); |
| } |
| |
| if (_imeMode != null) |
| { |
| // When IME.conversionMode is unknown it cannot be |
| // set to anything other than unknown(English) |
| try |
| { |
| if (IME.conversionMode != IMEConversionMode.UNKNOWN) |
| { |
| IME.conversionMode = _imeMode; |
| } |
| } |
| catch(e:Error) |
| { |
| // on Windows, setting a Japanese IME mode when in |
| // english throws an error (on Mac it doesn't) |
| // so ignore errors we get. |
| } |
| } |
| } |
| |
| if (focusManager && multiline) |
| focusManager.defaultButtonEnabled = false; |
| } |
| |
| /** |
| * @private |
| * RichEditableTextContainerManager overrides focusOutHandler and calls |
| * this before executing its own focusOutHandler. |
| */ |
| mx_internal function focusOutHandler(event:FocusEvent):void |
| { |
| //trace("focusOut handler"); |
| |
| super.focusOutHandler(event); |
| |
| // By default, we clear the undo history when a RichEditableText loses |
| // focus. |
| if (clearUndoOnFocusOut && undoManager) |
| undoManager.clearAll(); |
| |
| if (focusManager) |
| focusManager.defaultButtonEnabled = true; |
| |
| |
| dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT)); |
| } |
| |
| /** |
| * @private |
| * RichEditableTextContainerManager overrides keyDownHandler and calls |
| * this before executing its own keyDownHandler. |
| */ |
| mx_internal function keyDownHandler(event:KeyboardEvent):void |
| { |
| if (editingMode != EditingMode.READ_WRITE) |
| return; |
| |
| // Handle the ENTER key here, if multiline text is not allowed or |
| // if there isn't enough room for more characters to be added. |
| if (event.keyCode == Keyboard.ENTER) |
| { |
| if (!multiline) |
| { |
| dispatchEvent(new FlexEvent(FlexEvent.ENTER)); |
| event.preventDefault(); |
| return; |
| } |
| |
| // Multiline. Make sure there is room before acting on it. |
| if (_maxChars != 0 && text.length >= _maxChars) |
| { |
| event.preventDefault(); |
| return; |
| } |
| |
| // Let the TLF EditManager handle the ENTER key. If editing |
| // a list, depending on the position, it will either add a new |
| // list item or close off the list. Otherwise it will split |
| // the paragraph. |
| } |
| } |
| |
| /** |
| * @private |
| */ |
| mx_internal function mouseDownHandler(event:MouseEvent):void |
| { |
| mouseDown = true; |
| |
| // Need to get called even if mouse events are dispatched |
| // outside of this component. For example, when the user does |
| // a mouse down in RET, drags the mouse outside of the |
| /// component, and then releases the mouse. |
| systemManager.getSandboxRoot().addEventListener( |
| MouseEvent.MOUSE_UP, |
| systemManager_mouseUpHandler, true /*useCapture*/); |
| } |
| |
| /** |
| * @private |
| */ |
| private function systemManager_mouseUpHandler(event:MouseEvent):void |
| { |
| mouseDown = false; |
| |
| systemManager.getSandboxRoot().removeEventListener( |
| MouseEvent.MOUSE_UP, |
| systemManager_mouseUpHandler, true /*useCapture*/); |
| } |
| |
| /** |
| * @private |
| * call a TLF method so we don't leak |
| */ |
| private function removedFromStageHandler(event:Event):void |
| { |
| if (event.target == this) |
| { |
| TextContainerManager.releaseReferences(); |
| } |
| } |
| |
| /** |
| * @private |
| * If the textFlow hasn't changed the generation remains the same. |
| * Changing the composition width and/or height does not change the |
| * generation. The bounds can change as a result of different |
| * composition dimensions or as a result of more of the text flow |
| * being composed. Only as much of the text flow as is displayed is |
| * composed. If not all of the text flow is composed, its content height |
| * is estimated. Until the entire text flow is composed its content |
| * height can increase or decrease while scrolling thru the flow. |
| * |
| * If the following conditions are met with the contentWidth and the |
| * contentHeight reported to the scroller, the scroller can avoid the |
| * situation we've seen where it tries to add a scroll bar which causes the |
| * text to reflow, which changes the content bounds, which causes the |
| * scroller to react, and potentially loop indefinately. |
| * |
| * if width is reduced the height should grow or stay the same |
| * if height is reduced the width should grow or stay the same |
| * if width and height are reduced then either the width or height |
| * should grow or stay the same. |
| * |
| * toFit |
| * width height |
| * |
| * smaller smaller height pinned to old height |
| * smaller larger ok |
| * larger larger ok |
| * larger smaller ok |
| * |
| * explicit |
| * width height |
| * smaller smaller width pinned to old width |
| * smaller larger width pinned to old width |
| * larger larger ok |
| * larger smaller ok |
| */ |
| private function adjustContentBoundsForScroller(bounds:Rectangle):void |
| { |
| // Already reported bounds at least once for this generation of |
| // the text flow so we have to be careful to mantain consistency |
| // for the scroller. |
| if (_textFlow.generation == lastContentBoundsGeneration) |
| { |
| if (bounds.width <= _contentWidth) |
| { |
| if (_textContainerManager.hostFormat.lineBreak == "toFit") |
| { |
| if (bounds.height < _contentHeight) |
| bounds.height = _contentHeight; |
| } |
| else |
| { |
| // The width may get smaller if the compose height is |
| // reduced and fewer lines are composed. Use the old |
| // content width which is more accurate. |
| bounds.width = _contentWidth; |
| } |
| } |
| } |
| |
| lastContentBoundsGeneration = _textFlow.generation; |
| } |
| |
| /** |
| * @private |
| * Called when the TextContainerManager dispatches a 'compositionComplete' |
| * event when it has recomposed the text into TextLines. |
| */ |
| private function textContainerManager_compositionCompleteHandler( |
| event:CompositionCompleteEvent):void |
| { |
| //trace("compositionComplete"); |
| |
| var oldContentWidth:Number = _contentWidth; |
| var oldContentHeight:Number = _contentHeight; |
| |
| var newContentBounds:Rectangle = |
| _textContainerManager.getContentBounds(); |
| |
| // If x and/or y are not 0, adjust for what is visible. For example, if there is an |
| // image which is wider than the composeWidth and float="right", x will be negative |
| // and the part of the image between x and 0 will not be visible so it should |
| // not be included in the reported width. This will avoid a scrollbar that does |
| // nothing. |
| newContentBounds.width += newContentBounds.x; |
| newContentBounds.height += newContentBounds.y; |
| |
| // Try to prevent the scroller from getting into a loop while |
| // adding/removing scroll bars. |
| if (_textFlow && clipAndEnableScrolling) |
| adjustContentBoundsForScroller(newContentBounds); |
| |
| var newContentWidth:Number = newContentBounds.width; |
| var newContentHeight:Number = newContentBounds.height; |
| |
| // TODO:(cframpto) handle blockProgression == RL |
| |
| if (newContentWidth != oldContentWidth) |
| { |
| _contentWidth = newContentWidth; |
| |
| //trace("composeWidth", _textContainerManager.compositionWidth, "contentWidth", oldContentWidth, newContentWidth); |
| |
| // If there is a scroller, this triggers the scroller layout. |
| dispatchPropertyChangeEvent( |
| "contentWidth", oldContentWidth, newContentWidth); |
| } |
| |
| if (newContentHeight != oldContentHeight) |
| { |
| _contentHeight = newContentHeight; |
| |
| //trace("composeHeight", _textContainerManager.compositionHeight, "contentHeight", oldContentHeight, newContentHeight); |
| |
| // If there is a scroller, this triggers the scroller layout. |
| dispatchPropertyChangeEvent( |
| "contentHeight", oldContentHeight, newContentHeight); |
| } |
| } |
| |
| /** |
| * @private |
| * Called when the TextContainerManager dispatches a 'damage' event. |
| * The TextFlow could have been modified interactively or programatically. |
| */ |
| private function textContainerManager_damageHandler(event:DamageEvent):void |
| { |
| if (event.damageLength == 0) |
| return; |
| |
| if (inUpdateDLMethod) |
| return; |
| |
| // Text that is being measured is damaged so update the display. |
| if (inMeasureMethod) |
| { |
| invalidateDisplayList(); |
| return; |
| } |
| |
| //trace("damageHandler", "generation", _textFlow ? _textFlow.generation : -1, "lastGeneration", lastGeneration); |
| |
| // The following textContainerManager functions can trigger a damage |
| // event: |
| // setText/setTextFlow |
| // set hostFormat |
| // set compositionWidth/compositionHeight |
| // set horizontalScrollPosition/veriticalScrollPosition |
| // set swfContext |
| // updateContainer or compose: always if TextFlowFactory, sometimes |
| // if flowComposer |
| // or the textFlow can be modified directly. |
| |
| // If no changes, don't recompose/update. The TextFlowFactory |
| // createTextLines dispatches damage events every time the textFlow |
| // is composed, even if there are no changes. |
| if (_textFlow && _textFlow.generation == lastGeneration) |
| return; |
| |
| // If there are pending changes, don't wipe them out. We have |
| // not gotten to commitProperties() yet. |
| if (textChanged || textFlowChanged || contentChanged) |
| return; |
| |
| // In this case we always maintain _text with the underlying text and |
| // display the appropriate number of passwordChars. If there are any |
| // interactive editing operations _text is updated during the operation. |
| if (!displayAsPassword) |
| _text = null; |
| |
| _content = null; |
| _textFlow = _textContainerManager.getTextFlow(); |
| |
| lastGeneration = _textFlow.generation; |
| |
| // We don't need to call invalidateProperties() |
| // because the hostFormat and the _textFlow are still valid. |
| |
| // If the textFlow content is modified directly or if there is a style |
| // change by changing the textFlow directly the size could change. |
| invalidateSize(); |
| |
| invalidateDisplayList(); |
| } |
| |
| /** |
| * @private |
| * Called when the TextContainerManager dispatches a 'scroll' event |
| * as it autoscrolls. |
| */ |
| private function textContainerManager_scrollHandler(event:Event):void |
| { |
| var oldHorizontalScrollPosition:Number = _horizontalScrollPosition; |
| var newHorizontalScrollPosition:Number = |
| _textContainerManager.horizontalScrollPosition; |
| |
| if (newHorizontalScrollPosition != oldHorizontalScrollPosition) |
| { |
| //trace("hsp scroll", oldHorizontalScrollPosition, "->", newHorizontalScrollPosition); |
| |
| _horizontalScrollPosition = newHorizontalScrollPosition; |
| |
| dispatchPropertyChangeEvent("horizontalScrollPosition", |
| oldHorizontalScrollPosition, newHorizontalScrollPosition); |
| } |
| |
| var oldVerticalScrollPosition:Number = _verticalScrollPosition; |
| var newVerticalScrollPosition:Number = |
| _textContainerManager.verticalScrollPosition; |
| |
| if (newVerticalScrollPosition != oldVerticalScrollPosition) |
| { |
| //trace("vsp scroll", oldVerticalScrollPosition, "->", newVerticalScrollPosition); |
| |
| _verticalScrollPosition = newVerticalScrollPosition; |
| |
| dispatchPropertyChangeEvent("verticalScrollPosition", |
| oldVerticalScrollPosition, newVerticalScrollPosition); |
| } |
| } |
| |
| /** |
| * @private |
| * Called when the TextContainerManager dispatches a 'selectionChange' event. |
| */ |
| private function textContainerManager_selectionChangeHandler( |
| event:SelectionEvent):void |
| { |
| var oldAnchor:int = _selectionAnchorPosition; |
| var oldActive:int = _selectionActivePosition; |
| |
| var selectionState:SelectionState = event.selectionState; |
| |
| if (selectionState) |
| { |
| _selectionAnchorPosition = selectionState.anchorPosition; |
| _selectionActivePosition = selectionState.activePosition; |
| } |
| else |
| { |
| _selectionAnchorPosition = -1; |
| _selectionActivePosition = -1; |
| } |
| |
| // Selection changed so reset. |
| hasProgrammaticSelectionRange = false; |
| |
| // Only dispatch the event if the selection has really changed. |
| var changed:Boolean = oldAnchor != _selectionAnchorPosition || |
| oldActive != _selectionActivePosition; |
| |
| if (changed) |
| { |
| //trace("selectionChangeHandler", _selectionAnchorPosition, _selectionActivePosition); |
| dispatchEvent(new FlexEvent(FlexEvent.SELECTION_CHANGE)); |
| } |
| } |
| |
| /** |
| * @private |
| * Called when the TextContainerManager dispatches an 'operationBegin' |
| * event before an editing operation. |
| */ |
| private function textContainerManager_flowOperationBeginHandler( |
| event:FlowOperationEvent):void |
| { |
| //trace("flowOperationBegin", "level", event.level); |
| |
| var op:FlowOperation = event.operation; |
| |
| // The text flow's generation will be incremented if the text flow |
| // is modified in any way by this operation. |
| |
| if (op is InsertTextOperation) |
| { |
| var insertTextOperation:InsertTextOperation = |
| InsertTextOperation(op); |
| |
| var textToInsert:String = insertTextOperation.text; |
| |
| // Note: Must process restrict first, then maxChars, |
| // then displayAsPassword last. |
| |
| // The text deleted by this operation. If we're doing our |
| // own manipulation of the textFlow or thisis a result of a paste operation |
| // we have to take the deleted text into account as well as the inserted text. |
| var delSelOp:SelectionState = |
| insertTextOperation.deleteSelectionState; |
| |
| var delLen:int = (delSelOp == null) ? 0 : |
| delSelOp.absoluteEnd - delSelOp.absoluteStart; |
| |
| if (_restrict != null) |
| { |
| textToInsert = StringUtil.restrict(textToInsert, restrict); |
| // If restrict is the result of a paste the text has already been inserted |
| // into the buffer and has to be removed so don't short-circuit the operation. |
| if (textToInsert.length == 0 && delLen == 0) |
| { |
| event.preventDefault(); |
| return; |
| } |
| } |
| |
| if (maxChars != 0) |
| { |
| var length1:int = text.length - delLen; |
| var length2:int = textToInsert.length; |
| if (length1 + length2 > maxChars) |
| textToInsert = textToInsert.substr(0, maxChars - length1); |
| } |
| |
| if (_displayAsPassword) |
| { |
| // Remove deleted text. |
| if (delLen > 0) |
| { |
| _text = splice(_text, delSelOp.absoluteStart, |
| delSelOp.absoluteEnd, ""); |
| } |
| |
| // Add in the inserted text. |
| if (textToInsert.length > 0) |
| { |
| _text = splice(_text, insertTextOperation.absoluteStart, |
| insertTextOperation.absoluteStart, textToInsert); |
| |
| // Display the passwordChar rather than the actual text. |
| textToInsert = StringUtil.repeat(passwordChar, |
| textToInsert.length); |
| } |
| } |
| |
| insertTextOperation.text = textToInsert; |
| } |
| else if (op is CopyOperation) |
| { |
| if (_displayAsPassword) |
| { |
| // For security, don't allow passwords to be copied. |
| event.preventDefault(); |
| return; |
| } |
| } |
| else if (op is PasteOperation) |
| { |
| // Paste is implemented in operationEnd. The basic idea is to allow |
| // the paste to go through unchanged, but group it together with a |
| // second operation that modifies text as part of the same |
| // transaction. This is vastly simpler for TLF to manage. |
| // But first, we do a multi-line check and remove newlines if not |
| if (!multiline) |
| { |
| var po:PasteOperation = PasteOperation(op); |
| |
| // Make sure the clipboard has something to paste. |
| if (po.textScrap == null || po.textScrap.textFlow == null) |
| return; |
| |
| // If copied/cut from displayAsPassword field the pastedText |
| // is '*' characters but this is correct. |
| var pastedText:String = staticPlainTextExporter.export( |
| po.textScrap.textFlow, ConversionType.STRING_TYPE) as String; |
| |
| // If there are no newlines there is nothing |
| // more to do. |
| if (pastedText.search(ALL_NEWLINES_REGEXP) != -1) |
| { |
| pastedText = pastedText.replace(ALL_NEWLINES_REGEXP, ""); |
| po.textScrap = |
| TextClipboard.importToScrap(pastedText, |
| TextConverter.PLAIN_TEXT_FORMAT); |
| } |
| // don't do anything if nothing left on clipboard |
| if (pastedText.length == 0) |
| event.preventDefault(); |
| } |
| } |
| else if (op is DeleteTextOperation || op is CutOperation) |
| { |
| var flowTextOperation:FlowTextOperation = |
| FlowTextOperation(op); |
| |
| // Eat 0-length deletion. This can happen when insertion point is |
| // at start of container when a backspace is entered |
| // or when the insertion point is at the end of the |
| // container and a delete key is entered. |
| if (flowTextOperation.absoluteStart == |
| flowTextOperation.absoluteEnd) |
| { |
| event.preventDefault(); |
| return; |
| } |
| |
| if (_displayAsPassword) |
| { |
| if (op is DeleteTextOperation) |
| { |
| _text = splice(_text, flowTextOperation.absoluteStart, |
| flowTextOperation.absoluteEnd, ""); |
| } |
| else |
| { |
| // For security, don't allow passwords to be cut. |
| event.preventDefault(); |
| return; |
| } |
| } |
| } |
| |
| // Dispatch a 'changing' event from the RichEditableText |
| // as notification that an editing operation is about to occur. |
| // The level will be 0 for single operations, and at the start |
| // of a composite operation. |
| if (dispatchChangeAndChangingEvents && event.level == 0) |
| { |
| var newEvent:TextOperationEvent = |
| new TextOperationEvent(TextOperationEvent.CHANGING); |
| newEvent.operation = op; |
| dispatchEvent(newEvent); |
| |
| // If the event dispatched from this RichEditableText is canceled, |
| // cancel the one from the EditManager, which will prevent |
| // the editing operation from being processed. |
| if (newEvent.isDefaultPrevented()) |
| event.preventDefault(); |
| } |
| } |
| |
| /** |
| * @private |
| * Called when the TextContainerManager dispatches an 'operationEnd' event |
| * after an editing operation. |
| */ |
| private function textContainerManager_flowOperationEndHandler( |
| event:FlowOperationEvent):void |
| { |
| //trace("flowOperationEnd", "level", event.level); |
| |
| // Paste is a special case. Any mods have to be made to the text |
| // which includes what was pasted. |
| if (event.operation is PasteOperation) |
| handlePasteOperation(PasteOperation(event.operation)); |
| } |
| |
| /** |
| * @private |
| * Called when the TextContainerManager dispatches an 'operationComplete' |
| * event after an editing operation. |
| */ |
| private function textContainerManager_flowOperationCompleteHandler( |
| event:FlowOperationEvent):void |
| { |
| //trace("flowOperationComplete", "level", event.level); |
| |
| // Dispatch a 'change' event from the RichEditableText |
| // as notification that an editing operation has occurred. |
| // The flow is now in a state that it can be manipulated. |
| // The level will be 0 for single operations, and at the end |
| // of a composite operation. |
| if (dispatchChangeAndChangingEvents && event.level == 0) |
| { |
| var newEvent:TextOperationEvent = |
| new TextOperationEvent(TextOperationEvent.CHANGE); |
| newEvent.operation = event.operation; |
| dispatchEvent(newEvent); |
| } |
| } |
| |
| /** |
| * @private |
| * Called when an InlineGraphicElement is asynchronously loaded. |
| * The event status could be "sizePending", "loaded" or "error". |
| * Must call updateAllContainers() to cause transition to the next |
| * graphic status. |
| */ |
| private function textContainerManager_inlineGraphicStatusChangeHandler ( |
| event:StatusChangeEvent):void |
| { |
| if (event.status == InlineGraphicElementStatus.READY) |
| { |
| // Now that the actual size of the graphic is available need to |
| // optionally remeasure and updateContainer. |
| if (autoSize) |
| invalidateSize(); |
| } |
| |
| // Call updateAllContainers(). |
| invalidateDisplayList(); |
| } |
| |
| /** |
| * @private |
| * release the textline as it is temporary. |
| */ |
| private function tossTextLine(textLine:DisplayObject):void |
| { |
| TextUtil.recycleTextLine(textLine as TextLine); |
| } |
| } |
| |
| } |