content/tourdeflex/spark/tlf/flashx/textLayout/ui/rulers/RulerBar.as (614 lines of code) (raw):

//////////////////////////////////////////////////////////////////////////////// // // 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; } }