| <?xml version="1.0" encoding="utf-8"?> |
| <!-- |
| 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. |
| --> |
| |
| |
| <!-- Demonstrate some example controls. This example does not attempt to create a control for every property in the TextLayoutFramework --> |
| |
| <mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml" label="Text Editor Example" initialize="init()" backgroundColor="#FFFFFF" horizontalScrollPolicy="off" verticalScrollPolicy="off"> |
| <mx:Script> |
| <![CDATA[ |
| |
| import flashx.textLayout.container.ContainerController; |
| import flashx.textLayout.conversion.TextConverter; |
| import flashx.textLayout.edit.EditManager; |
| import flashx.textLayout.edit.IEditManager; |
| import flashx.textLayout.edit.ElementRange; |
| import flashx.textLayout.edit.SelectionState; |
| import flashx.textLayout.elements.Configuration; |
| import flashx.textLayout.elements.FlowElement; |
| import flashx.textLayout.elements.TextFlow; |
| import flashx.textLayout.elements.ParagraphElement; |
| import flashx.textLayout.elements.SpanElement; |
| import flashx.textLayout.events.StatusChangeEvent; |
| import flashx.textLayout.events.SelectionEvent; |
| import flashx.textLayout.formats.ITextLayoutFormat; |
| import flashx.textLayout.formats.TextLayoutFormat; |
| import flashx.textLayout.formats.TextAlign; |
| import flashx.textLayout.formats.VerticalAlign; |
| import flashx.textLayout.formats.BlockProgression; |
| import flashx.textLayout.formats.Direction; |
| import flashx.textLayout.operations.ApplyElementIDOperation; |
| import flashx.textLayout.operations.ApplyElementStyleNameOperation; |
| import flashx.textLayout.operations.FlowOperation; |
| import flashx.textLayout.tlf_internal; |
| |
| use namespace tlf_internal; |
| |
| import flashx.undo.UndoManager; |
| |
| import flash.display.Sprite; |
| import flash.system.Capabilities; |
| |
| // the textFlow being worked on |
| private var _textFlow:TextFlow = null; |
| // container to hold the text |
| private var _container:Sprite = null; |
| |
| // /////////////////////////////////////// |
| // data providers for enumerated list boxes |
| // /////////////////////////////////////// |
| static private const textAlignData:Array = [ |
| { label:"Justify", data:TextAlign.JUSTIFY}, |
| { label:"Left", data:TextAlign.LEFT}, |
| { label:"Right", data:TextAlign.RIGHT}, |
| { label:"Center", data:TextAlign.CENTER}, |
| { label:"End", data:TextAlign.END}, |
| { label:"Start", data:TextAlign.START} |
| ]; |
| static private const verticalAlignData:Array = [ |
| { label:"Bottom", data:VerticalAlign.BOTTOM }, |
| { label:"Justify", data:VerticalAlign.JUSTIFY }, |
| { label:"Middle", data:VerticalAlign.MIDDLE }, |
| { label:"Top", data:VerticalAlign.TOP } |
| ]; |
| |
| static private const blockProgressionData:Array = [ |
| { label:"TopToBottom", data:BlockProgression.TB }, |
| { label:"RightToleft", data:BlockProgression.RL } |
| ]; |
| |
| static private const directionData:Array = [ |
| { label:"LeftToRight", data:Direction.LTR }, |
| { label:"RightToleft", data:Direction.RTL } |
| ]; |
| |
| static private const styleLevelData:Array = [ |
| { label:"TextFlow", data:TextFlow }, |
| { label:"Paragraph", data:ParagraphElement}, |
| { label:"Span", data:SpanElement} |
| ]; |
| |
| /** |
| * initialization |
| */ |
| private function init():void |
| { |
| // create a sprite to hold the TextLines |
| _container = new Sprite(); |
| textArea.rawChildren.addChild(_container); |
| |
| fontFamily.dataProvider = populateFontFamily(); |
| |
| versionInfo.text = "Vellum: " + flashx.textLayout.TextLayoutVersion.BUILD_NUMBER + (Configuration.tlf_internal::debugCodeEnabled ? " Debug" : " Release") |
| + ", Flex: " + mx_internal::VERSION |
| + ", Player: " + Capabilities.version; |
| } |
| |
| /** |
| * Create an array of available font families |
| */ |
| static private function populateFontFamily():Array |
| { |
| // really this returns an array of fonts - would be nice to strip it down to just the families |
| var fonts:Array = Font.enumerateFonts(true); |
| var fontfamily:Array = new Array(); |
| fonts.sortOn("fontName", Array.CASEINSENSITIVE); |
| for(var i:int = 0; i< fonts.length; i++) |
| { |
| // trace(fonts[i].fontName); |
| fontfamily.push({label: fonts[i].fontName, data: fonts[i].fontName}); |
| } |
| return fontfamily; |
| } |
| |
| /** called to set the size of this panel */ |
| public function setSize(w:int,h:int):void |
| { |
| this.width = w; |
| this.height = h; |
| textArea.width = width; |
| textArea.height = height > bottomTabs.height ? this.height-bottomTabs.height : 0; |
| if (_textFlow) |
| { |
| _textFlow.flowComposer.getControllerAt(0).setCompositionSize(textArea.width,textArea.height); |
| _textFlow.flowComposer.updateAllControllers(); |
| } |
| } |
| |
| /** called when the bottom tabs finally gets sized. */ |
| private function bottomTabsResize():void |
| { |
| setSize(width,height); |
| } |
| |
| /** The TextFlow to edit. */ |
| public function get textFlow():TextFlow |
| { return _textFlow; } |
| |
| public function set textFlow(newFlow:TextFlow):void |
| { |
| // clear any old flow if present |
| if (_textFlow) |
| { |
| _textFlow.flowComposer = null; |
| _textFlow = null; |
| } |
| _textFlow = newFlow; |
| if (_textFlow) |
| { |
| _textFlow.flowComposer.addController(new ContainerController(_container,textArea.width,textArea.height)); |
| |
| // setup event listeners for selection changed and ILG loaded |
| _textFlow.addEventListener(SelectionEvent.SELECTION_CHANGE,selectionChangeListener,false,0,true); |
| _textFlow.addEventListener(StatusChangeEvent.INLINE_GRAPHIC_STATUS_CHANGE,graphicStatusChangeEvent,false,0,true); |
| |
| // make _textFlow editable with undo |
| _textFlow.interactionManager = new EditManager(new UndoManager()); |
| // initialize with a selection before the first character |
| _textFlow.interactionManager.selectRange(0,0); |
| |
| // compose the new textFlow and give it focus |
| _textFlow.flowComposer.updateAllControllers(); |
| _textFlow.interactionManager.setFocus(); |
| } |
| } |
| |
| /** Receives an event any time an ILG with a computed size finishes loading. */ |
| private function graphicStatusChangeEvent(evt:StatusChangeEvent):void |
| { |
| // recompose if the evt is from an element in this textFlow |
| if (_textFlow && evt.element.getTextFlow() == _textFlow) |
| _textFlow.flowComposer.updateAllControllers(); |
| } |
| |
| /** Receives an event any time the selection is changed. Update the UI */ |
| private function selectionChangeListener(e:SelectionEvent):void |
| { |
| var selectionState:SelectionState = e.selectionState; |
| var selectedElementRange:ElementRange = ElementRange.createElementRange(selectionState.textFlow, selectionState.absoluteStart, selectionState.absoluteEnd); |
| |
| // set display according to the values at the beginning of the selection range. For point selection/characterFormat use getCommonCharacterFormat as that tracks pending attributes waiting for the next character |
| var characterFormat:ITextLayoutFormat = _textFlow.interactionManager.activePosition == _textFlow.interactionManager.anchorPosition ? _textFlow.interactionManager.getCommonCharacterFormat() : selectedElementRange.characterFormat; |
| var paragraphFormat:ITextLayoutFormat = selectedElementRange.paragraphFormat; |
| var containerFormat:ITextLayoutFormat = selectedElementRange.containerFormat; |
| |
| updateComboBox(fontFamily,characterFormat.fontFamily); |
| fontSize.text = characterFormat.fontSize.toString(); |
| lineHeight.text = characterFormat.lineHeight.toString(); |
| |
| updateComboBox(textAlign,paragraphFormat.textAlign); |
| textIndent.text = paragraphFormat.textIndent.toString(); |
| |
| columnCount.text = containerFormat.columnCount.toString(); |
| columnGap.text = containerFormat.columnGap.toString(); |
| updateComboBox(verticalAlign,containerFormat.verticalAlign); |
| |
| updateComboBox(blockProgression,_textFlow.computedFormat.blockProgression); |
| updateComboBox(directionBox,_textFlow.computedFormat.direction); |
| |
| updateStyling(); |
| } |
| |
| /** Helper function to update a comboBox in the UI */ |
| private function updateComboBox(box:ComboBox,val:String):void |
| { |
| for (var i:int = 0; i < box.dataProvider.length; i++) |
| { |
| if (box.dataProvider[i].data == val) |
| { |
| box.selectedIndex = i; |
| return; |
| } |
| } |
| box.text = val; |
| } |
| |
| /** |
| * These functions are helpers for the various widgets to actually perform the operations on the TextFlow |
| */ |
| private function changeFontFamily(newFontFamily:String):void |
| { |
| if (_textFlow && _textFlow.interactionManager is IEditManager) |
| { |
| var cf:TextLayoutFormat = new TextLayoutFormat(); |
| cf.fontFamily = newFontFamily; |
| IEditManager(_textFlow.interactionManager).applyLeafFormat(cf); |
| _textFlow.interactionManager.setFocus(); |
| } |
| } |
| private function changeFontSize(newFontSize:String):void |
| { |
| if (_textFlow && _textFlow.interactionManager is IEditManager) |
| { |
| var cf:TextLayoutFormat = new TextLayoutFormat(); |
| cf.fontSize = newFontSize; |
| IEditManager(_textFlow.interactionManager).applyLeafFormat(cf); |
| _textFlow.interactionManager.setFocus(); |
| } |
| } |
| private function changeLeading(newLeading:String):void |
| { |
| if (_textFlow && _textFlow.interactionManager is IEditManager) |
| { |
| var cf:TextLayoutFormat = new TextLayoutFormat(); |
| cf.lineHeight = newLeading; |
| IEditManager(_textFlow.interactionManager).applyLeafFormat(cf); |
| _textFlow.interactionManager.setFocus(); |
| } |
| } |
| |
| private function changeTextAlign(newAlign:String):void |
| { |
| if (_textFlow && _textFlow.interactionManager is IEditManager) |
| { |
| var pf:TextLayoutFormat = new TextLayoutFormat(); |
| pf.textAlign = newAlign; |
| IEditManager(_textFlow.interactionManager).applyParagraphFormat(pf); |
| _textFlow.interactionManager.setFocus(); |
| } |
| } |
| |
| private function changeTextIndent(newIndent:String):void |
| { |
| if (_textFlow && _textFlow.interactionManager is IEditManager) |
| { |
| var pf:TextLayoutFormat = new TextLayoutFormat(); |
| pf.textIndent = newIndent; |
| IEditManager(_textFlow.interactionManager).applyParagraphFormat(pf); |
| _textFlow.interactionManager.setFocus(); |
| } |
| } |
| |
| private function changeColumnCount(newCount:String):void |
| { |
| if (_textFlow && _textFlow.interactionManager is IEditManager) |
| { |
| var cf:TextLayoutFormat = new TextLayoutFormat(); |
| cf.columnCount = newCount; |
| IEditManager(_textFlow.interactionManager).applyContainerFormat(cf); |
| _textFlow.interactionManager.setFocus(); |
| } |
| } |
| |
| private function changeColumnGap(newGap:String):void |
| { |
| if (_textFlow && _textFlow.interactionManager is IEditManager) |
| { |
| var cf:TextLayoutFormat = new TextLayoutFormat(); |
| cf.columnGap = newGap; |
| IEditManager(_textFlow.interactionManager).applyContainerFormat(cf); |
| _textFlow.interactionManager.setFocus(); |
| } |
| } |
| |
| private function changeVerticalAlign(newAlign:String):void |
| { |
| if (_textFlow && _textFlow.interactionManager is IEditManager) |
| { |
| var cf:TextLayoutFormat = new TextLayoutFormat(); |
| cf.verticalAlign = newAlign; |
| IEditManager(_textFlow.interactionManager).applyContainerFormat(cf); |
| _textFlow.interactionManager.setFocus(); |
| } |
| } |
| |
| private function changeBlockProgression(newProgression:String):void |
| { |
| if (_textFlow && _textFlow.interactionManager is IEditManager) |
| { |
| var cf:TextLayoutFormat = new TextLayoutFormat(); |
| cf.blockProgression = newProgression; |
| IEditManager(_textFlow.interactionManager).applyFormatToElement(_textFlow,cf); |
| _textFlow.interactionManager.setFocus(); |
| } |
| } |
| |
| /** Set direction on the rootElement. This effects both columnDirection and default reading order. */ |
| private function changeDirection(newDirection:String):void |
| { |
| if (_textFlow && _textFlow.interactionManager is IEditManager) |
| { |
| var pf:TextLayoutFormat = new TextLayoutFormat(); |
| pf.direction = newDirection; |
| IEditManager(_textFlow.interactionManager).applyFormatToElement(_textFlow,pf); |
| _textFlow.interactionManager.setFocus(); |
| } |
| } |
| |
| /** Currently selected level to display */ |
| private var currentStyleLevel:Class = TextFlow; |
| |
| /** Returns the current FlowELement element at the currentStyleLevel */ |
| private function currentStyleElement(absolutePosition:int):FlowElement |
| { |
| var e:FlowElement; |
| switch(currentStyleLevel) |
| { |
| case TextFlow: |
| e = _textFlow; |
| break; |
| case ParagraphElement: |
| e = _textFlow.findLeaf(absolutePosition).getParagraph(); |
| break; |
| default: |
| e = _textFlow.findLeaf(absolutePosition); |
| } |
| return e; |
| } |
| |
| /** Update the displayed styles. */ |
| private function updateStyling():void |
| { |
| var e:FlowElement = currentStyleElement(_textFlow.interactionManager.anchorPosition); |
| styleId.text = e.id; |
| styleName.text = e.styleName; |
| } |
| |
| /** Sets the level for the current style element to view. */ |
| private function changeStyleLevel(newLevel:Class):void |
| { |
| currentStyleLevel = newLevel; |
| updateStyling(); |
| } |
| |
| |
| /** Function to apply the id or styleName to a range. Its a bit tricker than I'd like it to be. */ |
| private function performStyleOperation(newData:String,operationClass:Class):void |
| { |
| if (_textFlow && _textFlow.interactionManager is IEditManager) |
| { |
| var op:FlowOperation; |
| var target:FlowElement; |
| |
| // if its a point selection change or TextFlow change the current element - else change the range |
| if (_textFlow.interactionManager.isRangeSelection() && currentStyleLevel != TextFlow) |
| { |
| // possibly multiple targets over the range |
| var targetList:Array = []; |
| var absStart:int = _textFlow.interactionManager.getSelectionState().absoluteStart; |
| var absEnd:int = _textFlow.interactionManager.getSelectionState().absoluteEnd; |
| while (absStart < absEnd) |
| { |
| target = currentStyleElement(absStart); |
| targetList.push(currentStyleElement(absStart)); |
| absStart = target.getAbsoluteStart() + target.textLength; |
| } |
| |
| absStart = _textFlow.interactionManager.absoluteStart; |
| IEditManager(_textFlow.interactionManager).beginCompositeOperation(); |
| var selectionEndDelta:int = 0; |
| for each (target in targetList) |
| { |
| //if we are extending the style name then normalize which is called in doOp will cause the element to |
| //mergeToPrev. The result is an invalid index. Example: |
| // |
| //Initial State: |
| //<span styleID="a">foo</span><span styleID="b">bar</span><span>moreFooBar</span> |
| // |
| //Selection from "oo" of "foo" to "more" and new styleID="b" |
| //"foo" will be split: |
| //<span styleID="a">f</span><span styleID="b">oo</span>... |
| // |
| //During operation, normalize will merge "bar" into "oo": |
| //span styleID="a">f</span><span styleID="b">oobar</span> |
| // |
| //And most importantly, the element of "bar" is removed from the flow and ready for deletion. |
| //In this case, skip to the next element. |
| // |
| //Watson 2291333. - gak 03.13.09 |
| if(target.parent == null) |
| { |
| absStart += target.textLength; |
| continue; |
| } |
| |
| // it using the anchhor position to find the style element. need to give it a range of just the first selected element. otherwise need to iterate over many |
| var operationEnd:int = absEnd < target.getAbsoluteStart()+target.textLength ? absEnd : target.getAbsoluteStart()+target.textLength; |
| var textFlowLength:int = _textFlow.textLength; |
| // parameters passed to operation class constructor |
| // selectionState - ending selection |
| // target - element to be modified |
| // newData - id or styleName |
| // relativeStart - target relative position to begin the modificaiton |
| // relativeEnd - target relative position to end the modification |
| op = new operationClass(_textFlow.interactionManager.getSelectionState(),target,newData,absStart-target.getAbsoluteStart(),operationEnd-target.getAbsoluteStart()); |
| IEditManager(_textFlow.interactionManager).doOperation(op); |
| // new paragraphs might get created due to splitting so adjust for the additional paragraph terminators that were inserted |
| if (textFlowLength < _textFlow.textLength) |
| { |
| absStart += _textFlow.textLength-textFlowLength; |
| absEnd += _textFlow.textLength-textFlowLength; |
| operationEnd += _textFlow.textLength-textFlowLength; |
| selectionEndDelta += _textFlow.textLength-textFlowLength; |
| } |
| absStart = operationEnd; |
| } |
| IEditManager(_textFlow.interactionManager).endCompositeOperation(); |
| |
| // finally adjust the selection to include any extra chars added by split paragraphs |
| if (selectionEndDelta > 0) |
| { |
| var anchor:int = _textFlow.interactionManager.anchorPosition; |
| var active:int = _textFlow.interactionManager.activePosition; |
| if (anchor < active) |
| active += selectionEndDelta; |
| else |
| anchor += selectionEndDelta; |
| _textFlow.interactionManager.selectRange(anchor,active); |
| _textFlow.interactionManager.refreshSelection(); |
| } |
| } |
| else // change the element containing the point selection |
| { |
| target = currentStyleElement(_textFlow.interactionManager.anchorPosition); |
| op = new operationClass(_textFlow.interactionManager.getSelectionState(),target,newData); |
| IEditManager(_textFlow.interactionManager).doOperation(op); |
| } |
| _textFlow.interactionManager.setFocus(); |
| } |
| } |
| |
| private function changeStyleId(newId:String):void |
| { |
| performStyleOperation(newId,ApplyElementIDOperation); |
| } |
| private function changeStyleStyleName(newStyleName:String):void |
| { |
| performStyleOperation(newStyleName,ApplyElementStyleNameOperation); |
| } |
| |
| ]]> |
| </mx:Script> |
| <!-- <mx:VBox horizontalScrollPolicy="off" verticalScrollPolicy="off" width="100%" height="100%"> --> |
| <mx:Canvas id="textArea" width="520" height="400"/> |
| <mx:TabNavigator id="bottomTabs" width="100%" creationPolicy="all" paddingLeft="4" paddingBottom="8" backgroundColor="#D9D9D9" color="#202020" horizontalScrollPolicy="off" verticalScrollPolicy="off" resize="bottomTabsResize()"> |
| <mx:HBox label="Text" backgroundColor="#D9D9D9" width="496" horizontalScrollPolicy="off" verticalScrollPolicy="off" > |
| <mx:Label text="Font:"/> |
| <mx:ComboBox id="fontFamily" editable="true" enter="changeFontFamily(fontFamily.text)" close="changeFontFamily(fontFamily.text)" width="200"/> |
| <mx:Label text="Size:"/> |
| <mx:TextInput id="fontSize" enter="changeFontSize(fontSize.text)" width="40"/> |
| <mx:Label text="LineHeight:"/> |
| <mx:TextInput id="lineHeight" enter="changeLeading(lineHeight.text)" width="40"/> |
| </mx:HBox> |
| <mx:HBox label="Para" backgroundColor="#D9D9D9" width="496"> |
| <mx:Label text="Alignment:"/> |
| <mx:ComboBox id="textAlign" close="changeTextAlign(textAlign.selectedItem.data)" dataProvider="{textAlignData}"/> |
| <mx:Label text="FirstLineIdent:"/> |
| <mx:TextInput id="textIndent" enter="changeTextIndent(textIndent.text)" width="40"/> |
| </mx:HBox> |
| <mx:HBox label="Container" backgroundColor="#D9D9D9" width="496"> |
| <mx:Label text="Columns:"/> |
| <mx:TextInput id="columnCount" toolTip="auto or a number" enter="changeColumnCount(columnCount.text)" width="40"/> |
| <mx:Label text="Gap:"/> |
| <mx:TextInput id="columnGap" toolTip="a number" enter="changeColumnGap(columnGap.text)" width="40"/> |
| <mx:Label text="VerticalAlignment:"/> |
| <mx:ComboBox id="verticalAlign" close="changeVerticalAlign(verticalAlign.selectedItem.data)" dataProvider="{verticalAlignData}"/> |
| </mx:HBox> |
| <mx:HBox label="Flow" backgroundColor="#D9D9D9" width="496"> |
| <mx:Label text="Progression:"/> |
| <mx:ComboBox id="blockProgression" close="changeBlockProgression(blockProgression.selectedItem.data)" dataProvider="{blockProgressionData}"/> |
| <mx:Label text="Direction:"/> |
| <mx:ComboBox id="directionBox" close="changeDirection(directionBox.selectedItem.data)" dataProvider="{directionData}"/> |
| </mx:HBox> |
| <mx:HBox label="Style" backgroundColor="#D9D9D9" width="496"> |
| <mx:Label text="Level:"/> |
| <mx:ComboBox id="styleLevel" close="changeStyleLevel(styleLevel.selectedItem.data)" dataProvider="{styleLevelData}" selectedIndex="0"/> |
| <mx:Label text="id:"/> |
| <mx:TextInput id="styleId" enter="changeStyleId(styleId.text)" width="40"/> |
| <mx:Label text="stlyleName:"/> |
| <mx:TextInput id="styleName" enter="changeStyleStyleName(styleName.text)" width="40"/> |
| </mx:HBox> |
| <mx:HBox label="Version" backgroundColor="#D9D9D9" width="496"> |
| <mx:TextInput id="versionInfo" editable="false" width="100%"/> |
| </mx:HBox> |
| </mx:TabNavigator> |
| <!-- </mx:VBox> --> |
| </mx:VBox> |