| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // Licensed to the Apache Software Foundation (ASF) under one or more |
| // contributor license agreements. See the NOTICE file distributed with |
| // this work for additional information regarding copyright ownership. |
| // The ASF licenses this file to You under the Apache License, Version 2.0 |
| // (the "License"); you may not use this file except in compliance with |
| // the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| package flashx.textLayout.ui.rulers |
| { |
| import bxf.ui.inspectors.DynamicPropertyEditorBase; |
| |
| import flash.display.DisplayObject; |
| import flash.display.GradientType; |
| import flash.events.MouseEvent; |
| import flash.geom.Matrix; |
| import flash.geom.Point; |
| import flash.geom.Rectangle; |
| import flash.text.engine.TabAlignment; |
| |
| import flashx.textLayout.container.ContainerController; |
| import flashx.textLayout.edit.EditManager; |
| import flashx.textLayout.edit.ElementRange; |
| import flashx.textLayout.edit.SelectionState; |
| import flashx.textLayout.elements.ContainerFormattedElement; |
| import flashx.textLayout.elements.ParagraphElement; |
| import flashx.textLayout.elements.TextFlow; |
| import flashx.textLayout.compose.TextFlowLine; |
| import flashx.textLayout.events.SelectionEvent; |
| import flashx.textLayout.formats.ITextLayoutFormat; |
| import flashx.textLayout.formats.ITabStopFormat; |
| import flashx.textLayout.formats.TextLayoutFormat; |
| import flashx.textLayout.formats.TabStopFormat; |
| import flashx.textLayout.formats.BlockProgression; |
| import flashx.textLayout.tlf_internal; |
| import flashx.textLayout.ui.inspectors.TabPropertyEditor; |
| import flashx.textLayout.ui.inspectors.TextInspectorController; |
| |
| import mx.containers.Canvas; |
| import mx.core.ScrollPolicy; |
| import mx.core.UIComponent; |
| import mx.events.PropertyChangeEvent; |
| import mx.events.ResizeEvent; |
| use namespace tlf_internal; |
| |
| public class RulerBar extends Canvas |
| { |
| public static const RULER_HORIZONTAL:String = "horizontal"; |
| public static const RULER_VERTICAL:String = "vertical"; |
| |
| public function RulerBar() |
| { |
| super(); |
| horizontalScrollPolicy = ScrollPolicy.OFF; |
| verticalScrollPolicy = ScrollPolicy.OFF; |
| mDefaultTabStop = new TabStopFormat(TabStopFormat.defaultFormat); |
| addEventListener(MouseEvent.MOUSE_DOWN, onRulerMouseDown); |
| selectMarker(null); |
| TextInspectorController.Instance().AddRuler(this); |
| curParagraphFormat = null; |
| } |
| |
| override public function initialize():void |
| { |
| super.initialize(); |
| adjustForActive(); |
| } |
| |
| public function creationComplete():void |
| { |
| if (mSyncToPanel) |
| { |
| mSyncToPanel.addEventListener(ResizeEvent.RESIZE, onSyncPanelResize); |
| } |
| SyncRulerToPanel(); |
| mIndentMarker = addParagraphPropertyMarker(TextLayoutFormat.textIndentProperty.name); |
| mLeftMarginMarker = addParagraphPropertyMarker(TextLayoutFormat.paragraphStartIndentProperty.name); |
| mRightMarginMarker = addParagraphPropertyMarker(TextLayoutFormat.paragraphEndIndentProperty.name); |
| } |
| |
| public function set activeFlow(inFlow:TextFlow):void |
| { |
| if (inFlow && !inFlow.interactionManager is EditManager) |
| throw new Error("Can't set the active flow to a flow without an EditManager."); |
| if (mActiveFlow) |
| { |
| mActiveFlow.removeEventListener(SelectionEvent.SELECTION_CHANGE, onTextSelectionChanged); |
| mEditManager = null; |
| } |
| mActiveFlow = inFlow; |
| mLastSelActiveIdx = -1; |
| mLastSelAnchorIdx = -1; |
| mTabSet = null; |
| RemoveTabMarkers(); |
| selectMarker(null); |
| |
| if (mActiveFlow) |
| { |
| mEditManager = mActiveFlow.interactionManager as EditManager; |
| mActiveFlow.addEventListener(SelectionEvent.SELECTION_CHANGE, onTextSelectionChanged); |
| } |
| else |
| onTextSelectionChanged(null); |
| } |
| |
| public function get activeFlow():TextFlow |
| { |
| return mActiveFlow; |
| } |
| |
| public function set active(inActive:Boolean):void |
| { |
| mActive = inActive; |
| selectMarker(null); |
| adjustForActive(); |
| } |
| |
| public function get active():Boolean |
| { |
| return mActive; |
| } |
| |
| private function set rightRuler(inActive:Boolean):void |
| { |
| mRightRuler = inActive; |
| adjustForActive(); |
| } |
| |
| private function get rightRuler():Boolean |
| { |
| return mRightRuler; |
| } |
| |
| private function adjustForActive():void |
| { |
| if (parent) |
| { |
| if (mActive && mRightRuler) |
| { |
| parent.visible = true; |
| if (parent is Canvas) |
| (parent as Canvas).includeInLayout = true; |
| } |
| else |
| { |
| parent.visible = false; |
| if (parent is Canvas) |
| (parent as Canvas).includeInLayout = false; |
| } |
| } |
| } |
| |
| public function set orientation(inOrientation:String):void |
| { |
| if (inOrientation != mOrientation && (inOrientation == RULER_HORIZONTAL || inOrientation == RULER_VERTICAL)) |
| { |
| mOrientation = inOrientation; |
| } |
| } |
| |
| public function set syncToPanel(inPanel:UIComponent):void |
| { |
| mSyncToPanel = inPanel; |
| } |
| |
| public function set tabPropertyEditor(inEditor:TabPropertyEditor):void |
| { |
| mPropertyEditor = inEditor; |
| mPropertyEditor.addEventListener(DynamicPropertyEditorBase.MODELCHANGED_EVENT, onFormatValueChanged, false, 0, true); |
| mPropertyEditor.addEventListener(DynamicPropertyEditorBase.MODELEDITED_EVENT, onFormatValueChanged, false, 0, true); |
| selectMarker(mSelectedMarker); |
| } |
| |
| private function onSyncPanelResize(evt:ResizeEvent):void |
| { |
| RedrawRuler(); |
| } |
| |
| public function RedrawRuler():void |
| { |
| SyncRulerToPanel(); |
| if (curParagraphFormat != null) { |
| ShowTabs(curParagraphFormat); |
| } |
| } |
| |
| private function SyncRulerToPanel():void |
| { |
| if (mActiveFlow && mActiveFlow.flowComposer && rightRuler) |
| { |
| var selStart:int = Math.min(mActiveFlow.interactionManager.activePosition, mActiveFlow.interactionManager.anchorPosition); |
| var line:TextFlowLine = selStart != -1 ? mActiveFlow.flowComposer.findLineAtPosition(selStart) : null; |
| if (line) |
| { |
| var controller:ContainerController; |
| var containerDO:DisplayObject; |
| if (line.controller) |
| { |
| controller = line.controller; |
| containerDO = controller.container as DisplayObject; |
| } |
| else |
| { |
| // get the last container |
| controller = mActiveFlow.flowComposer.getControllerAt(mActiveFlow.flowComposer.numControllers-1); |
| containerDO = controller.container as DisplayObject; |
| } |
| var localOrigin:Point = parent.globalToLocal(containerDO.parent.localToGlobal(new Point(containerDO.x, containerDO.y))); |
| var columnBounds:Rectangle; |
| var columnIndex:int = line.columnIndex; |
| if (columnIndex == -1) |
| columnBounds = controller.columnState.getColumnAt(controller.columnState.columnCount - 1); |
| else |
| { |
| // columnIndex is an index into all the columns in the flow, so to get the actual |
| // column bounds |
| var idx:int = 0; |
| var ch:ContainerController = mActiveFlow.flowComposer.getControllerAt(idx); |
| while (ch && ch != controller) |
| { |
| columnIndex -= ch.columnState.columnCount; |
| idx++; |
| ch = idx == mActiveFlow.flowComposer.numControllers ? null : mActiveFlow.flowComposer.getControllerAt(idx); |
| } |
| // Pin the column number to the actual range of column indices. I have found this |
| // is needed when the insertion point is inside a table (because the line's container |
| // is not in the flow's list of containers) or when the insertion point is in regular |
| // text after a table (the column number doesn't make sense, and I think it's a bug, which |
| // I have written to Robin about. |
| columnIndex = Math.max(0, Math.min(line.columnIndex, controller.columnState.columnCount - 1)); |
| columnBounds = controller.columnState.getColumnAt(columnIndex); |
| } |
| |
| if (columnBounds) |
| { |
| if (mOrientation == RULER_HORIZONTAL) |
| { |
| x = localOrigin.x + columnBounds.x; |
| y = 0; |
| height = parent.height; |
| width = columnBounds.width; |
| } |
| else |
| { |
| x = parent.width; |
| y = localOrigin.y + columnBounds.y; |
| rotation = 90; |
| height = parent.width; |
| width = columnBounds.height; |
| } |
| } |
| } |
| } |
| } |
| |
| private function onTextSelectionChanged(e:SelectionEvent):void |
| { |
| curParagraphFormat = null; |
| if (mEditManager && (mEditManager.activePosition != mLastSelActiveIdx || mEditManager.anchorPosition != mLastSelAnchorIdx)) |
| { |
| mLastSelActiveIdx = mActiveFlow.interactionManager.activePosition; |
| mLastSelAnchorIdx = mActiveFlow.interactionManager.anchorPosition; |
| selectMarker(null); |
| } |
| if (e) |
| { |
| var selState:SelectionState = e.selectionState; |
| var selectedElementRange:ElementRange = selState ? ElementRange.createElementRange(selState.textFlow, selState.absoluteStart, selState.absoluteEnd) : null; |
| if (selectedElementRange) |
| { |
| var rootElement:ContainerFormattedElement = selectedElementRange.firstLeaf.getAncestorWithContainer(); |
| if ((rootElement.computedFormat.blockProgression == BlockProgression.RL) == (mOrientation == RULER_VERTICAL)) |
| { |
| // should be active |
| if (rightRuler != true) |
| { |
| mTabSet = null; |
| } |
| if (!rightRuler) |
| rightRuler = true; |
| } |
| else |
| { |
| // should be inactive |
| if (rightRuler != false) |
| { |
| mTabSet = null; |
| } |
| if (rightRuler) |
| rightRuler = false; |
| } |
| |
| curParagraphFormat = new TextLayoutFormat(selectedElementRange.firstParagraph.computedFormat); |
| setRightToLeft(curParagraphFormat.direction == flashx.textLayout.formats.Direction.RTL); |
| ShowTabs(curParagraphFormat); |
| } |
| else |
| ShowTabs(null); |
| } |
| else |
| ShowTabs(null); |
| } |
| |
| |
| private function RemoveTabMarkers():void |
| { |
| var markers:Array = getChildren(); |
| for each (var marker:UIComponent in markers) |
| if (marker is TabMarker) |
| this.removeChild(marker); |
| } |
| |
| |
| private function ShowTabs(inFormat:ITextLayoutFormat):void |
| { |
| SyncRulerToPanel(); |
| var tabs:Array = inFormat ? ((inFormat.tabStops && (inFormat.tabStops.length > 0)) ? inFormat.tabStops as Array : null) : null; |
| if (isNewTabSet(tabs)) |
| { |
| mTabSet = tabs; |
| if (mUpdateFromSelection) |
| { |
| RemoveTabMarkers(); |
| var oldSel:RulerMarker = mSelectedMarker; |
| selectMarker(null); |
| if (mTabSet) |
| for each(var tab:TabStopFormat in mTabSet) |
| { |
| var tabMarker:TabMarker = addTabMarker(tab); |
| if (oldSel && oldSel.pos == tabMarker.pos) |
| selectMarker(tabMarker); |
| } |
| } |
| } |
| if (inFormat) |
| { |
| if(mIndentMarker) |
| { |
| mIndentMarker.rightToLeftPar = mRightToLeft; |
| mIndentMarker.pos = Number(inFormat.textIndent); |
| mIndentMarker.relativeToPosition = inFormat.paragraphStartIndent; |
| } |
| |
| if(mLeftMarginMarker) |
| { |
| mLeftMarginMarker.rightToLeftPar = mRightToLeft; |
| mLeftMarginMarker.pos = rightToLeft ? Number(inFormat.paragraphEndIndent): Number(inFormat.paragraphStartIndent); |
| } |
| |
| if(mRightMarginMarker) |
| { |
| mRightMarginMarker.rightToLeftPar = mRightToLeft; |
| mRightMarginMarker.pos = rightToLeft ? Number(inFormat.paragraphStartIndent): Number(inFormat.paragraphEndIndent); |
| } |
| } |
| } |
| |
| |
| private function addTabMarker(tabAttrs:ITabStopFormat):TabMarker |
| { |
| var tabMarker:TabMarker = new TabMarker(this, tabAttrs); |
| tabMarker.addEventListener(MouseEvent.MOUSE_DOWN, onMarkerMouseDown); |
| addChild(tabMarker); |
| return tabMarker; |
| } |
| |
| |
| private function addParagraphPropertyMarker(inProperty:String):ParagraphPropertyMarker |
| { |
| var propMarker:ParagraphPropertyMarker = new ParagraphPropertyMarker(this, inProperty); |
| propMarker.addEventListener(MouseEvent.MOUSE_DOWN, onMarkerMouseDown); |
| addChild(propMarker); |
| return propMarker; |
| } |
| |
| |
| private function isNewTabSet(inTabs:Array):Boolean |
| { |
| if (inTabs == mTabSet) |
| return false; |
| if ((inTabs == null) != (mTabSet == null)) |
| return true; |
| if (inTabs) |
| { |
| if (inTabs.length == mTabSet.length) |
| { |
| var n:int = inTabs.length; |
| for (var i:int = 0; i < n; ++i) |
| { |
| if (inTabs[i] != mTabSet[i]) |
| return true; |
| } |
| return false; |
| } |
| else |
| return true; |
| } |
| return false; |
| } |
| |
| |
| override protected function updateDisplayList(w:Number, h:Number):void |
| { |
| super.updateDisplayList(w, h); |
| |
| graphics.clear(); |
| var m:Matrix = new Matrix(); |
| m.createGradientBox(height, height, Math.PI / 2); |
| graphics.beginGradientFill(GradientType.LINEAR, [0xffffff, 0xe0e0e0], [1, 1], [0, 255], m); |
| graphics.drawRect(0, 0, w, h); |
| graphics.endFill(); |
| |
| graphics.lineStyle(1, 0x404040, 1.0, true); |
| for (var x:int = 0; x < w; x += 10) |
| { |
| var rulerX:Number = rightToLeft ? w - x - 1 : x; |
| if (x % 100 == 0) |
| graphics.moveTo(rulerX, 12); |
| else if (x % 50 == 0) |
| graphics.moveTo(rulerX, 9); |
| else |
| graphics.moveTo(rulerX, 5); |
| graphics.lineTo(rulerX, 0); |
| } |
| } |
| |
| private function onMarkerMouseDown(e:MouseEvent):void |
| { |
| if (mEditManager) |
| { |
| var cookie:Object; |
| if (e.target is TabMarker) |
| { |
| var tabMarker:TabMarker = e.target as TabMarker; |
| selectMarker(tabMarker); |
| e.stopPropagation(); |
| cookie = new Object(); |
| cookie["marker"] = tabMarker; |
| cookie["offset"] = e.localX; |
| cookie["onRuler"] = true; |
| mUpdateFromSelection = false; |
| new RulerDragTracker(this.parentApplication as UIComponent, this, cookie).BeginTracking(e, false); |
| } |
| else if (e.target is ParagraphPropertyMarker) |
| { |
| var propMarker:ParagraphPropertyMarker = e.target as ParagraphPropertyMarker; |
| selectMarker(null); |
| e.stopPropagation(); |
| cookie = new Object(); |
| cookie["marker"] = propMarker; |
| cookie["offset"] = e.localX; |
| new RulerDragTracker(this.parentApplication as UIComponent, this, cookie).BeginTracking(e, false); |
| } |
| } |
| } |
| |
| private function onRulerMouseDown(e:MouseEvent):void |
| { |
| if (e.target is RulerBar && mEditManager) |
| { |
| var tabMarker:TabMarker = addTabMarker(mDefaultTabStop); |
| tabMarker.markerLeft = e.localX + tabMarker.hOffset; |
| selectMarker(tabMarker); |
| mUpdateFromSelection = false; |
| setFormatFromRuler(); |
| e.stopPropagation(); |
| var cookie:Object = new Object(); |
| cookie["marker"] = tabMarker; |
| cookie["offset"] = -tabMarker.hOffset; |
| cookie["onRuler"] = true; |
| new RulerDragTracker(this.parentApplication as UIComponent, this, cookie, 0).BeginTracking(e, false); |
| } |
| } |
| |
| public function TrackDrag(inCurPos:Point, inCookie:Object, inCommit:Boolean):void |
| { |
| if (inCookie) |
| { |
| if (inCookie["marker"] is TabMarker) |
| { |
| var tabMarker:TabMarker = inCookie["marker"] as TabMarker; |
| var wasOnRuler:Boolean = inCookie["onRuler"]; |
| if (inCookie["onRuler"] && inCurPos.y > height + 16) |
| { |
| inCookie["onRuler"] = false; |
| removeChild(tabMarker); |
| selectMarker(null); |
| } |
| else if (!inCookie["onRuler"] && inCurPos.y <= height + 16) |
| { |
| inCookie["onRuler"] = true; |
| addChild(tabMarker); |
| selectMarker(tabMarker); |
| } |
| |
| tabMarker.markerLeft = inCurPos.x - inCookie["offset"]; |
| if (wasOnRuler || inCookie["onRuler"]) |
| setFormatFromRuler(); |
| } |
| else if (inCookie["marker"] is ParagraphPropertyMarker) |
| { |
| var propMarker:ParagraphPropertyMarker = inCookie["marker"] as ParagraphPropertyMarker; |
| propMarker.markerLeft = inCurPos.x - inCookie["offset"]; |
| var pa:TextLayoutFormat = new TextLayoutFormat(); |
| pa[propMarker.property] = propMarker.pos; |
| mEditManager.applyParagraphFormat(pa); |
| } |
| } |
| if (inCommit) |
| mUpdateFromSelection = true; |
| } |
| |
| public function DragCancelled():void |
| { |
| mUpdateFromSelection = true; |
| } |
| |
| private function selectMarker(inMarker:RulerMarker):void |
| { |
| if (mSelectedMarker) |
| mSelectedMarker.setStyle("selected", false); |
| mSelectedMarker = inMarker; |
| if (mSelectedMarker) |
| mSelectedMarker.setStyle("selected", true); |
| updatePropertyEditor(); |
| } |
| |
| private function updatePropertyEditor():void |
| { |
| if (mRightRuler && mPropertyEditor && mTabPanelActive) |
| { |
| mPropertyEditor.reset(); |
| mPropertyEditor.properties["rulervisible"] = TextInspectorController.Instance().rulerVisible; |
| if (TextInspectorController.Instance().rulerVisible) |
| { |
| var tab:ITabStopFormat = mSelectedMarker as ITabStopFormat; |
| if (!tab) |
| tab = mDefaultTabStop as ITabStopFormat; |
| if (tab) |
| { |
| mPropertyEditor.properties["alignment"] = tab.alignment; |
| if (tab != mDefaultTabStop) |
| mPropertyEditor.properties["position"] = tab.position; |
| if (tab.alignment == flash.text.engine.TabAlignment.DECIMAL) |
| mPropertyEditor.properties["decimalAlignmentToken"] = tab.decimalAlignmentToken; |
| } |
| } |
| mPropertyEditor.rebuildUI(); |
| } |
| } |
| |
| private function onFormatValueChanged(e:PropertyChangeEvent):void |
| { |
| if (mRightRuler) |
| { |
| var property:String = e.property as String; |
| if (property == "rulervisible") |
| TextInspectorController.Instance().rulerVisible = (e.newValue == "true" ? true : false); |
| else |
| { |
| if (e.type == DynamicPropertyEditorBase.MODELEDITED_EVENT) |
| mUpdateFromSelection = false; |
| var tab:Object = mSelectedMarker; |
| if (!tab) |
| tab = mDefaultTabStop; |
| var newValue:Object = e.newValue; |
| if (property == "position") |
| newValue = Number(newValue); |
| tab[property] = newValue; |
| if (property == "alignment" && newValue == flash.text.engine.TabAlignment.DECIMAL && tab["decimalAlignmentToken"] == null) |
| tab["decimalAlignmentToken"] = ""; |
| if (mSelectedMarker) |
| setFormatFromRuler(); |
| if (e.type == DynamicPropertyEditorBase.MODELCHANGED_EVENT) |
| mUpdateFromSelection = true; |
| updatePropertyEditor(); |
| } |
| } |
| } |
| |
| private function setFormatFromRuler():void |
| { |
| var newTabs:Array = []; |
| if (mSelectedMarker && mSelectedMarker.parent) |
| newTabs.push(new TabStopFormat(mSelectedMarker as ITabStopFormat)); |
| var markers:Array = getChildren(); |
| for each (var marker:UIComponent in markers) |
| if (marker is TabMarker) |
| { |
| var tab:TabMarker = marker as TabMarker; |
| if (isUniquePosition(newTabs, tab.pos)) |
| newTabs.push(new TabStopFormat(tab)); |
| |
| } |
| newTabs.sortOn("position", Array.NUMERIC); |
| var pa:TextLayoutFormat = new TextLayoutFormat(); |
| pa.tabStops = newTabs; |
| mEditManager.applyParagraphFormat(pa); |
| updatePropertyEditor(); |
| } |
| |
| private static function isUniquePosition(inTabFormat:Array, inNewPosition:Number):Boolean |
| { |
| for each (var tab:TabStopFormat in inTabFormat) |
| if (tab.position == inNewPosition) |
| return false; |
| return true; |
| } |
| |
| public function set tabPanelActive(inActive:Boolean):void |
| { |
| if (mTabPanelActive != inActive) |
| { |
| mTabPanelActive = inActive; |
| if (mTabPanelActive) |
| updatePropertyEditor(); |
| } |
| } |
| |
| public function get tabPanelActive():Boolean |
| { |
| return mTabPanelActive; |
| } |
| |
| public function get rightToLeft():Boolean |
| { |
| return mRightToLeft; |
| } |
| |
| private function setRightToLeft(inRTL:Boolean):void |
| { |
| if (inRTL != mRightToLeft) |
| { |
| mTabSet = null; |
| mRightToLeft = inRTL; |
| invalidateDisplayList(); |
| } |
| } |
| |
| private var mActive:Boolean = true; |
| private var mActiveFlow:TextFlow = null; |
| private var mEditManager:EditManager = null; |
| private var mTabSet:Array = null; |
| private var mSelectedMarker:RulerMarker = null; |
| private var mUpdateFromSelection:Boolean = true; |
| private var mDefaultTabStop:TabStopFormat; |
| private var mPropertyEditor:TabPropertyEditor = null; |
| private var mOrientation:String = RULER_HORIZONTAL; |
| private var mSyncToPanel:UIComponent = null; |
| private var mRightRuler:Boolean = true; |
| private var mLastSelAnchorIdx:int = -1; |
| private var mLastSelActiveIdx:int = -1; |
| private var mIndentMarker:ParagraphPropertyMarker = null; |
| private var mLeftMarginMarker:ParagraphPropertyMarker = null; |
| private var mRightMarginMarker:ParagraphPropertyMarker = null; |
| private var mTabPanelActive:Boolean = false; |
| private var mRightToLeft:Boolean = false; |
| private var curParagraphFormat:TextLayoutFormat = null; |
| |
| } |
| } |